- );
-}
\ No newline at end of file
diff --git a/apps/sn/app/(with-base-layout)/oper/dredging/page.tsx b/apps/sn/app/(with-base-layout)/oper/dredging/page.tsx
index 7e041a273..3565bacbf 100644
--- a/apps/sn/app/(with-base-layout)/oper/dredging/page.tsx
+++ b/apps/sn/app/(with-base-layout)/oper/dredging/page.tsx
@@ -425,7 +425,7 @@ const Page = () => {
return buildElapsedYearRows(filteredRecords);
return filteredRecords.map((record, index) => ({
- rowNumber: filteredRecords.length - index,
+ rowNumber: index + 1,
...record,
pipeLengthM: Number(record.pipeLengthM.toFixed(2)),
pipeMaterial: resolvePipeMaterialName(record.pipeMaterial),
diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx
index 5867e70cc..98561a9e3 100644
--- a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx
+++ b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx
@@ -17,13 +17,13 @@
* - 첨부파일 삭제및 Soft Delete 삭제: DELETE /api/v1/atfl/attachments/{atflId} (attach-service)
*/
"use client";
+import { getSnRowNumber } from "@/shared/lib/row-number";
import { deleteData, useCookieAuth } from "@/components/lib/api";
import { searchData } from "@/components/lib/common/apiHelpers";
import { formatDate, handleDownload } from "@/components/lib/utils";
import LoadingOverlay from "@/components/ui/LoadingBar";
import {
StandardDataTable,
- getStandardRowNumber,
} from "@common/business_components";
import { Button, Input, Label } from "@common/components";
import { useEffect, useMemo, useState } from "react";
@@ -192,7 +192,7 @@ const Page = () => {
const dataWithRowNumber = (data || []).map(
(item: any, index: number) => ({
...item,
- rowNumber: getStandardRowNumber({
+ rowNumber: getSnRowNumber({
total,
rowIndex: index,
pageIndex: page - 1,
diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx
index 55d9f6667..e5c040ba1 100644
--- a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx
+++ b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx
@@ -5,6 +5,7 @@
*/
"use client";
+import { getSnRowNumber } from "@/shared/lib/row-number";
import { useEffect, useMemo, useState } from "react";
import {
getCoreRowModel,
@@ -22,7 +23,6 @@ import { openWindowPopup } from "@/features/windowPopup/lib/openWindowPopup";
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
import { useAlertStore } from "@/shared/lib";
import {
- getStandardRowNumber,
StandardDataTable,
} from "@common/business_components";
import { Button, Input, Label } from "@common/components";
@@ -125,7 +125,7 @@ const Page = () => {
setRows(
(result.data ?? []).map((item: OldPpRow, rowIndex: number) => ({
...item,
- rowNumber: getStandardRowNumber({
+ rowNumber: getSnRowNumber({
total: result.total,
rowIndex,
pageIndex: targetPage - 1,
diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx b/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx
index 1d0524114..0471872fb 100644
--- a/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx
+++ b/apps/sn/app/(with-base-layout)/oper/oldPp/tableModal.tsx
@@ -104,7 +104,7 @@ function filterEmptyRows(data: ExcelRow[]) {
function withDescendingRowNumber(rows: ExcelRow[]) {
return rows.map((row, index) => ({
...row,
- rowNumber: rows.length - index,
+ rowNumber: index + 1,
}));
}
diff --git a/apps/sn/app/layout.tsx b/apps/sn/app/layout.tsx
index b8b4897c8..41fdb4057 100644
--- a/apps/sn/app/layout.tsx
+++ b/apps/sn/app/layout.tsx
@@ -17,6 +17,7 @@ import { cookies } from "next/headers";
import { AppProvider } from "./providers";
import "../src/app/styles/base.css";
import "../src/app/styles/components.css";
+import "../src/app/styles/grid-ellipsis.css";
export const metadata: Metadata = {
title: "디지털 하수관로",
@@ -35,7 +36,7 @@ export default async function RootLayout({
return (
-
+
{children}
diff --git a/apps/sn/docs/작업계획/SN-그리드말줄임표/001-fe-SN-그리드말줄임표-결과.md b/apps/sn/docs/작업계획/SN-그리드말줄임표/001-fe-SN-그리드말줄임표-결과.md
new file mode 100644
index 000000000..ea49e8572
--- /dev/null
+++ b/apps/sn/docs/작업계획/SN-그리드말줄임표/001-fe-SN-그리드말줄임표-결과.md
@@ -0,0 +1,38 @@
+# SN 전체 목록 그리드 말줄임표
+
+> 2026-09-23 후속 정리: 사용자 요청에 따라 `scripts/grid-cell-tooltip.test.cjs`를 삭제했다. 아래 테스트 명령과 결과는 삭제 전 실행 이력이며 테스트 소스 보존 문구는 더 이상 적용하지 않는다. 구현 코드와 이 최종 보고서는 유지한다.
+
+2026-09-23 사용자 요청: 노후 하수관로 정비사업 화면처럼 긴 내용이 행 높이를 늘리는 현상을 SN 전체 그리드에서 정리한다.
+기존 원본 화면의 표 컬럼·업무값은 유지하고 이번 요청을 표시 방식 변경 근거로 적용했다.
+
+## 변경
+
+- app/layout.tsx: SN 전용 body 속성과 grid-ellipsis.css 등록.
+- src/app/styles/grid-ellipsis.css: 헤더가 있는 목록 표의 일반 데이터 셀을 한 줄·overflow hidden·text-overflow ellipsis로 처리. 중첩 span/div의 whitespace-normal/pre-line도 정리.
+- src/shared/ui/grid-cell-tooltip.tsx: 넘치는 셀에 pointerover/focusin 시 원문 title 제공. 기존 셀·자식 title은 우선 보존. 동적으로 교체된 셀의 이전 생성 title은 다시 계산.
+- src/app/providers/app-provider.tsx: SN 문서와 포털의 표에서 툴팁 처리를 공통 사용.
+- scripts/grid-cell-tooltip.test.cjs: 정식 회귀 테스트 3개.
+
+table 레이아웃·열 너비·데이터·정렬·엑셀 생성은 변경하지 않았다. 원문을 잘라 저장하지 않고 CSS로만 생략한다.
+thead가 없는 폼/상세 표, 입력창·버튼·체크박스·이미지·중첩 표를 가진 셀, rowspan/colspan 병합 셀, tfoot은 기존 표시를 유지한다.
+예외적으로 원래 줄바꿈이 필요한 셀은 data-sn-cell-wrap 속성으로 제외할 수 있다.
+공용 패키지와 다른 서비스는 수정하지 않았다. 앞선 순번·레이어 토글 변경도 보존했다.
+
+## 검증
+
+- TypeScript 검사 및 git diff --check 통과.
+- 단위 테스트: CSS/JS 대상 셀 일치·입력 예외, 원문 툴팁 및 셀 재사용 시 갱신·정리, 기존 title 보존 — 3개 통과.
+- 실행 중인 사용자 로컬 서버(3002)를 재시작하거나 종료하지 않고 HMR로 검증했다.
+- 실제 StandardDataTable과 일반 HTML 표에 사진과 유사한 장문 테스트 데이터를 연결한 브라우저 확인:
+ - 긴 셀과 짧은 셀의 행 높이 동일(약 40.59px).
+ - 일반 td 및 block/whitespace-normal span 모두 한 줄 말줄임표 확인.
+ - 948자 원문이 DOM에 보존되고 생성 title이 원문과 일치함을 확인.
+ - 입력값 변경·버튼 클릭 횟수 증가 및 병합 합계 셀 보존 확인.
+ - 중첩 셀 렌더러의 테스트 표도 추가해 표시 경로를 확인했으나 전체 개별 화면을 실데이터로 전수 검증한 것은 아니다.
+- 원본 데이터·API·DB 및 엑셀 출력 코드 변경 없음. 실제 업무 화면·엑셀 전체 회귀는 별도 확인이 필요하다.
+
+## 정리 및 복구
+
+임시 검증 페이지 1개를 삭제했다. 원시 캡처·응답 파일은 만들지 않았으며 정식 테스트·본 문서는 보존한다.
+사용자 서버는 계속 실행 중이며 커밋·push·배포는 하지 않았다.
+복구 시 이번 CSS import·body 속성·GridCellTooltip 등록 및 신규 파일만 제거한다. 다른 작업 변경은 되돌리지 않는다.
diff --git a/apps/sn/docs/작업계획/SN-그리드순번정순통일/001-fe-SN-그리드순번정순통일-결과.md b/apps/sn/docs/작업계획/SN-그리드순번정순통일/001-fe-SN-그리드순번정순통일-결과.md
new file mode 100644
index 000000000..62ee7635a
--- /dev/null
+++ b/apps/sn/docs/작업계획/SN-그리드순번정순통일/001-fe-SN-그리드순번정순통일-결과.md
@@ -0,0 +1,69 @@
+# SN 그리드 표시 순번 오름차순 통일
+
+> 2026-09-23 후속 정리: 사용자 요청에 따라 `scripts/row-number.test.cjs`를 삭제했다. 아래 테스트 명령과 결과는 삭제 전 실행 이력이며 테스트 소스 보존 문구는 더 이상 적용하지 않는다. 구현 코드와 이 최종 보고서는 유지한다.
+
+## 목적 및 기준
+
+2026-09-23 사용자 지시: 하수관로 SN 전체 그리드의 표시 순번을 1, 2, 3…으로 통일한다.
+서버 데이터 정렬, 업무 ID, 저장용 순번과 검색·페이지 계약은 변경하지 않는다.
+
+- 페이지 목록: `(현재 페이지 - 1) × 페이지 크기 + 행 인덱스 + 1`.
+- 0부터 시작하는 서버 페이지: `page × pageSize + index + 1`.
+- 비페이지 목록·전체 데이터: `index + 1`.
+- GIS 오류의 원본 행번호, 사업순번, 시설물 식별자, 합계·소계 및 병합 표의 빈 셀은 기존 의미를 보존한다.
+
+원본 요구사항정의서.xlsx의 시트 구조와 화면설계서.pptx의 관련 화면 텍스트를 열람했다.
+대표 근거는 슬라이드 35의 SFR-012-002 / SN_MN010302 GIS 관리 업로드 이력 목록, 슬라이드 43의 SFR-010-004 조사결과 등록이다.
+원본 화면의 목록·업무 항목은 유지하며, 전체 표에 대한 오름차순 표시 정책은 이번 사용자 명시 지시를 적용한다.
+
+## 수정 범위
+
+앱의 기존 파일 26개와 SN 전용 순번 함수·정식 회귀 테스트 각 1개를 변경·추가했다.
+공용 business_components의 기존 getStandardRowNumber 구현 및 다른 앱은 수정하지 않았다.
+
+| 구분 | 변경 대상 |
+| --- | --- |
+| SN 전용 공통 계산 | src/shared/lib/row-number.ts의 getSnRowNumber |
+| 공통 표 연결부 | MapPopupDataTable, ExmnStandardDataTable |
+| 조사관리 | ExmnRegistrationPage, ExmnStatusDetailPanel, TrtdstManagementPage |
+| 운영 목록·상세 | oldPp page/tableModal, floodPre page, dredging page, ObsltPplListPage, CtyFldngPrvtnBizListPage, CfmtnBgtPopupContent |
+| I/I 분석 | ii/result, ii/rdiiResult, InftwtResultPopupPage, RdiiResultPopupPage |
+| 지도 검색·상세 | useSearchModal, useOperationTab, useBasicTab, reportTab |
+| 지도 분석 | floodDngrMapTypes, sbmrgDngrMapTypes, AgedPipeStatusByCtpvPopupPage, PipeAnalysisPopupPage, PipeAnalysisSummaryTable |
+| GIS 관리 | 공통 표 경유 순번 변경 및 오류 원본 행번호와 구분하는 주석 정비 |
+
+### 코드 변경 이유
+
+기존 공통 함수는 `total - (pageIndex * pageSize + rowIndex)`로 전체 건수부터 역순 표시했다.
+공용 패키지의 기본 동작을 바꾸면 다른 앱에 영향을 줄 수 있으므로 SN 전용 함수를 추가하고 SN 호출부 10개 파일을 연결했다.
+
+```ts
+export function getSnRowNumber({ rowIndex, pageIndex = 0, pageSize = 0 }) {
+ return pageIndex * pageSize + rowIndex + 1;
+}
+```
+
+개별 화면에 남은 `total - offset - index`, `rows.length - index` 계산도 정순으로 변경했다.
+이미 정순인 지반침하·침수이력·노후관로 등의 기존 데이터 가공은 유지하면서 공통 표시부에서 덮어쓰던 역순을 제거했다.
+처리분구의 withDescendingRowNumbers는 실제 동작에 맞게 withAscendingRowNumbers로 변경했다.
+
+## 검증
+
+- `node --test scripts/row-number.test.cjs`: 4개 테스트 통과.
+ - 비페이지 1부터 시작, 전체 건수에 비의존.
+ - 1페이지 1~10, 2페이지 11~20, 마지막 부분 페이지 21~23.
+ - 페이지 크기 변경 및 검색 초기화.
+ - SN 전체 app/src 소스에서 공용 역순 함수와 기존 역순 산식 재유입 검사.
+- `pnpm exec tsc --noEmit --incremental false --pretty false`: 변경 후 통과.
+- `git diff --check`: 통과.
+- 실데이터 화면: localhost:3002 로그인 후 침입수 결과 조회를 시도했으나 localhost:8883 백엔드 ECONNREFUSED로 조회 실패.
+- 컴포넌트 브라우저 검증: 실제 MapPopupDataTable·ExmnStandardDataTable에 테스트 데이터 23건을 연결하여 1/2/3페이지 순번 확인. 데이터 순서 반전 시 순번 1부터, 검색 3건 전환 시 1~3 확인.
+- 이 브라우저 시험은 테스트 데이터이며 실제 업무 데이터 전체 검증을 대체하지 않는다. 모든 개별 화면의 실데이터 회귀와 Excel 결과 전체 대조는 미완료다.
+- Next 전체 빌드는 실행하지 않았다. API·DB·인증·환경설정·배포 및 데이터 정렬 조건은 수정하지 않았다.
+
+## 정리 및 복구
+
+- 일회성 브라우저 검증 페이지 1개는 확인 후 삭제했다. 화면 캡처·원시 응답 파일은 생성하지 않았다.
+- 정식 회귀 테스트는 후속 검증용 소스이므로 보존한다.
+- 직접 기동한 SN 개발서버(next dev PID 37840 및 하위 프로세스)는 종료했다. 사용자 서버는 종료하지 않았다.
+- 커밋·push·개발서버 배포는 수행하지 않았다. 복구가 필요하면 이번 SN 변경 diff만 역적용하고 관련 없는 변경은 유지한다.
diff --git a/apps/sn/docs/작업계획/SN-노후관로목록표시변경/001-fe-SN-노후관로목록표시변경-결과.md b/apps/sn/docs/작업계획/SN-노후관로목록표시변경/001-fe-SN-노후관로목록표시변경-결과.md
new file mode 100644
index 000000000..3c295bc70
--- /dev/null
+++ b/apps/sn/docs/작업계획/SN-노후관로목록표시변경/001-fe-SN-노후관로목록표시변경-결과.md
@@ -0,0 +1,57 @@
+# 노후관로 목록 조회조건 및 표시항목 변경
+
+> 2026-09-23 후속 정리: 사용자 요청에 따라 `scripts/aged-pipe-list.test.cjs`를 삭제했다. 아래 테스트 명령과 결과는 삭제 전 실행 이력이며 테스트 소스 보존 문구는 더 이상 적용하지 않는다. 구현 코드와 이 최종 보고서는 유지한다.
+
+## 목적과 근거
+
+- 2026-09-23 사용자 요청: 조회조건을 시도, 시군구, 경과년도 순으로 변경하고 관리기관, 설치일자, 경과년도를 연속 배치한다.
+- 원본 요구사항정의서 `3. (기능)요구사항정의 및 추적표` 87~90행, SFR-011-001~004 확인. 목록과 경과년도 조회는 88행 SFR-011-002와 연결된다.
+- 원본 화면설계서 슬라이드 8, 화면 ID `SN_MN010102`, 노후관로관리 확인. 기존 필터는 경과년도/시도/시군구이며 관리기관 컬럼은 없다. 이번 명시 요청을 우선 적용했다.
+- 원본 요구사항의 기본값 20년과 화면설계/현재 코드의 30년 차이는 기존 사항이다. 이번 요청은 배치와 표시항목 변경이므로 기본값과 계산 기준은 수정하지 않았다.
+
+## 수정 파일과 이유
+
+- `app/(map-layout)/map/components/agedPipe/AgedPipePopupPage.tsx`: 검색 JSX를 시도/시군구/경과년도 순으로 이동했다. 입력 이벤트, 조회조건, 초기화 및 페이지 처리 함수는 유지했다. 기존 `v1/fcltPpl/mngInsts` API를 한 번 조회해 목록 표시용 기관명을 연결한다. 기관 목록 수신 후 표시만 갱신하며 관로 목록을 다시 요청하지 않는다.
+- `app/(map-layout)/map/components/agedPipe/agedPipeUtils.ts`: 헤더에 `mngInstNm`을 추가하고 설치일자 뒤로 경과년도를 옮겼다. 9개 컬럼 폭 합계를 100%로 조정했다. 원본 기관명 우선, 코드에 맞는 `lwrInstNm`, 원본 코드, `-` 순으로 표시한다. 행 식별용 관리기관 코드는 변경하지 않는다.
+- `scripts/aged-pipe-list.test.cjs`: 컬럼 순서/폭, 기관명 표시/대체값, 검색조건 순서 회귀 테스트 3개를 추가했다.
+
+```text
+조회조건: 시도 → 시군구 → 경과년도
+목록: 순번 → 시설관리번호 → 관리기관 → 설치일자 → 경과년도
+ → 관경(mm) → 연장(m) → 용도 → 재질
+```
+
+## 영향 범위
+
+- SN 노후관로 목록 팝업만 변경했다. 다른 앱과 공유 컴포넌트, 인증, 환경, 의존성, 배포 설정은 수정하지 않았다.
+- 기존 조회 API를 재사용하며 백엔드와 DB 변경은 없다. 관리기관 조회 실패 시 안내 후 원본 코드를 표시한다.
+- 엑셀은 기존 전체 속성 다운로드를 유지한다. 지도 행 선택의 식별값과 경과년도 계산도 유지한다.
+
+## 검증
+
+- `node --test scripts/aged-pipe-list.test.cjs`: 3개 통과.
+- `pnpm exec tsc --noEmit --incremental false --pretty false`: 종료 코드 0, 오류 없음. 개발 서버가 실행 중이므로 전체 빌드는 수행하지 않았다.
+- `git diff --check`: 통과(기존 Git 설정의 LF/CRLF 안내만 출력).
+- 실제 로컬 브라우저 `http://localhost:3002/popup/agedPipePopup`에서 확인:
+ - 검색조건 시도/시군구/경과년도, 9개 목록 컬럼 순서 정상.
+ - 서울특별시/대전광역시 관리기관명, 설치일자, 경과년도 실데이터 표시 확인.
+ - 대전광역시/40년 조회 및 시군구 옵션 연계 확인.
+ - 유성구 선택 후 조회된 데이터 없음 상태 확인.
+ - 초기화 시 전체/전체/30년 복귀 및 목록 재조회 확인.
+ - 2페이지 이동 시 순번 11~20 및 관리기관명 유지 확인.
+- 사용자 개발 서버 PID 43172(3002)는 종료·재시작하지 않았다. 별도 서버 및 임시 파일을 생성하지 않았다. 원시 캡처/로그는 파일로 저장하지 않았다. 정식 회귀 테스트와 이 결과 문서는 유지한다.
+
+## 롤백 및 남은 사항
+
+- 두 구현 파일에서 이번 diff만 역적용하면 이전 표시로 복구할 수 있다. 후속 변경이 있으면 파일 전체를 되돌리지 않는다.
+- 실데이터 중 설치일자 `19000000`이 경과년도 126으로 표시되는 기존 날짜 검증 문제가 관찰됐다. 이번 배치 변경에서는 원본 데이터와 날짜 판정 규칙을 임의 수정하지 않았다.
+- 엑셀 파일 생성 및 부모 지도창으로 위치 이동은 이번 변경에서 재검증하지 않았다.
+
+## 후속 변경: 목록 팝업 중앙 배치
+
+- 사용자 요청에 따라 `src/features/windowPopup/model/popupDefinitions.ts`의 `agedPipePopup`에서 `placement`, `alignTop`, `offsetLeft`, `offsetTop` 예외를 제거했다. 공통 열기 함수의 중앙 배치 기본값을 적용한다. 크기 1160×820과 다른 팝업 설정은 유지했다.
+- 기존 화면설계서 슬라이드 8의 목록 기능을 유지하며, 창 위치는 이번 명시 요청을 적용했다.
+- `scripts/aged-pipe-list.test.cjs`에 중앙 배치/크기 유지 검증을 추가했다. 전체 4개 테스트 및 `git diff --check` 통과.
+- 로컬 브라우저에서 로그인 및 지도 진입을 확인했으나, 확인 시점의 지도에는 레이어 목록보기 버튼이 노출되지 않아 실제 새 창 위치는 미검증이다. 코드 기본값과 설정에 대한 자동 검증으로 구분해 기록한다.
+- 이미 열린 이름 지정 팝업은 브라우저가 위치를 유지할 수 있으므로 창을 닫고 다시 열어 확인한다. 배포 및 사용자 서버 재시작은 수행하지 않았다.
+- 롤백은 해당 팝업의 이전 위치 설정 네 항목만 복구한다. 임시 파일 생성·삭제 없음.
diff --git a/apps/sn/docs/작업계획/SN-레이어전체표시/001-fe-SN-레이어전체표시-결과.md b/apps/sn/docs/작업계획/SN-레이어전체표시/001-fe-SN-레이어전체표시-결과.md
new file mode 100644
index 000000000..d159f8dec
--- /dev/null
+++ b/apps/sn/docs/작업계획/SN-레이어전체표시/001-fe-SN-레이어전체표시-결과.md
@@ -0,0 +1,51 @@
+# 하수관망도 레이어 전체 켜기·끄기
+
+> 2026-09-23 후속 정리: 사용자 요청에 따라 `scripts/layer-bulk-selection.test.cjs`를 삭제했다. 아래 테스트 명령과 결과는 삭제 전 실행 이력이며 테스트 소스 보존 문구는 더 이상 적용하지 않는다. 구현 코드와 이 최종 보고서는 유지한다.
+
+2026-09-23 사용자 요청으로 트리의 '레이어 관리' 제목 오른쪽에 전체 표시 토글을 추가했다.
+후속 요청에 따라 두 버튼을 단일 스위치로 바꿨다. 모두 선택이면 ON, 일부 선택이면 OFF 위치와 '일부 켜짐' 문구로 표시한다.
+OFF 또는 부분 선택 상태에서 누르면 전체 켜기, ON 상태에서 누르면 전체 끄기가 실행된다.
+화면설계서의 하수관망도 및 GIS 관리 영역(SN_MN010302, 슬라이드 35)을 유지하고 이번 명시 요청을 기능 추가 근거로 적용한다.
+
+## 변경
+
+- SN map/page.tsx: showBulkLayerToggle=true 전달.
+- business_components Gis.js, GisTree.js: 선택적 옵션 전달, 헤더 버튼, 전체 체크 이벤트.
+- layerBulkSelection.js: exprsYn=Y 노드의 전체 선택을 계산하는 순수 함수.
+- apps/sn/scripts/layer-bulk-selection.test.cjs: 정식 회귀 테스트 3개(전체·부분·꺼짐·빈 목록 상태 포함).
+
+전체 켜기는 표시 허용 노드만 체크하고 숨김 노드는 켜지 않는다. 전체 끄기는 현재 트리 노드의 체크·표시를 해제한다.
+gisCore.setLayerOnMap에 트리 ID만 전달하므로 배경지도나 별도 편집 오버레이를 일괄 끄는 API는 호출하지 않는다.
+전체 이벤트에서 자동재생 타이머를 중지하고 재생 상태를 false로 변경한 뒤 체크 상태·지도 제어·업무 콜백을 한 번에 반영한다.
+기존 개별 체크·트리 펼침·레이어 설정·클릭 동작은 유지한다.
+
+## 다른 서비스 영향
+
+Gis는 FO·WR·DM 및 SN의 다른 지도에서도 사용한다. showBulkLayerToggle 기본값은 false이고 SN 주 지도만 활성화했다.
+공통 패키지의 허용 범위 안에서 수정했으며 다른 앱 코드·서버·DB·API 계약은 변경하지 않았다.
+앞선 SN 순번 정순 변경은 별도 작업이며 유지했다.
+
+## 검증
+
+- TypeScript 검사 통과.
+- 단위 테스트 3개: 표시 허용 부모·자식 선택, 숨김 제외, 전체 해제, 빈 목록 및 입력 비변경, 토글 상태 확인.
+- 단일 토글 브라우저 재검증: 부분 선택 → ON(모두 체크) → OFF(모두 해제), 250px 헤더 배치 확인. 테스트용 지도 stub을 사용했으며 실데이터 네트워크 검증과 구분한다.
+- 실제 GisTree와 GisContext에 지도 제어 stub 및 테스트 트리를 연결한 브라우저 검증:
+ - 초기 관로만 선택 → 전체 켜기 시 부모·관로·맨홀 체크 및 지도 제어 상태 true.
+ - 숨김 노드는 false 유지.
+ - 전체 끄기 시 모든 체크·제어 상태 false.
+ - 전체 끄기 후 관로 개별 체크 정상.
+ - 옵션 false일 때 버튼 미표시 및 기존 체크 유지.
+ - 250px 패널에서 제목 오른쪽 버튼 배치 확인.
+- 테스트 데이터 기반 검증이며 실제 GeoServer·업무별 비동기 레이어 로딩 전체를 검증한 것은 아니다. 앞선 실데이터 검증에서 로컬 백엔드 8883 연결 거부가 확인되어 환경설정은 변경하지 않았다.
+- 기존 개별 체크 경로는 상태 갱신 함수 안에서 업무 콜백을 호출하는 구조이므로 개발 모드 경고가 보일 수 있다. 이번 전체 체크 경로는 해당 방식으로 구현하지 않았다.
+
+## 정리·복구
+
+임시 검증 페이지 2개는 삭제하고 직접 기동한 개발서버는 종료한다. 캡처·원시 로그 파일은 생성하지 않았다.
+토글 후속 검증에서는 개발서버 기동이 포트 사용 중으로 실패한 상태였으나 정리 시 기존 3002 프로세스를 종료하는 실수가 있었다.
+이를 사용자에게 알리고 복원 기동을 시도했으며, 이미 다른 실행에서 3002가 재기동되어 중복 기동은 실패했다.
+최종 확인 시 3002는 PID 47412(Next 명령 부모 PID 48580)로 LISTENING 상태다. 이 프로세스는 추가 종료하지 않았다.
+최종 임시 검증 파일 삭제 후 TypeScript 검사 통과를 확인했다.
+정식 테스트와 본 보고서는 보존한다. 커밋·push·배포는 수행하지 않았다.
+복구하려면 SN의 showBulkLayerToggle 전달을 제거해 버튼 노출부터 해제할 수 있다. 전체 복구 시 이번 공통 GIS 변경만 역적용한다.
diff --git a/apps/sn/docs/작업내용/SN-노후하수관로정비사업/001-fe-SN-노후하수관로정비사업-통합구현기록.md b/apps/sn/docs/작업내용/SN-노후하수관로정비사업/001-fe-SN-노후하수관로정비사업-통합구현기록.md
index 0bf92de3e..3bda67dc1 100644
--- a/apps/sn/docs/작업내용/SN-노후하수관로정비사업/001-fe-SN-노후하수관로정비사업-통합구현기록.md
+++ b/apps/sn/docs/작업내용/SN-노후하수관로정비사업/001-fe-SN-노후하수관로정비사업-통합구현기록.md
@@ -148,3 +148,21 @@
- 목록은 CMS 코드값 또는 코드명과 정확히 일치하는 값만 코드명으로 표시하고 미등록 기존 값은 그대로 표시한다.
- 영문 `lwpoCd` 저장 전환과 기존 데이터 이관은 이번 범위에서 수행하지 않았다.
- 백엔드 API, Excel 입출력과 DB는 변경하지 않았다.
+
+## 2026-09-22 목록 사업코드·추진현황 검색조건 추가
+
+- 사용자 유지보수 요청에 따라 목록 검색조건에 사업코드와 추진현황을 추가했다.
+- 검색조건은 `사업구분 → 시도 → 시군구 → 사업코드 → 추진현황 → 사업명` 순서로 배치했다.
+- 사업코드는 자유 입력으로 부분일치 조회하고, 추진현황은 기존 자유문자를 직접 입력하면서 `BIZPRGRSSTTS` 공통코드 후보를 datalist로 선택할 수 있게 했다.
+- 두 조건을 목록 검색 상태에 포함해 조회 URL, 페이지 이동, 정렬, 상세·등록 후 목록 복귀와 초기화에서 동일하게 유지한다.
+- Excel 다운로드도 적용된 검색 상태를 재사용하므로 현재 화면과 같은 사업코드·추진현황 조건의 전체 결과를 내려받는다.
+- SN 운영 빌드와 별도 TypeScript 검사를 통과했다. 최초 빌드는 Windows 파일 핸들 한도 오류로 중단됐으나 단독 재시도에서 성공했다.
+
+## 2026-09-22 공단의견 목록·폼·Excel 제거
+
+- 사용자 유지보수 요청에 따라 목록의 공단의견 열과 등록·상세·수정 화면의 공단 의견 입력란을 제거했다.
+- FE 저장 요청 타입에서 `pbcpOpnnCn`을 제거해 등록·수정 API 요청에 공단의견을 포함하지 않는다.
+- 업로드 헤더 검증과 행 파싱에서 V열 공단의견을 제거하고 B~U의 20개 업무 열만 저장 요청으로 변환한다.
+- 기존 B~V 업로드 파일의 V열이 남아 있어도 이를 읽거나 저장하지 않으며, 나머지 B~U 열은 같은 위치로 처리한다.
+- FE Excel 다운로드의 V열 공단의견, 열 폭, 스타일 범위를 제거하고 B~U 양식으로 생성한다.
+- DB 컬럼과 기존 값은 유지하며 화면·API·Excel에서만 제외한다.
diff --git a/apps/sn/package.json b/apps/sn/package.json
index 136cca511..6b461acd4 100644
--- a/apps/sn/package.json
+++ b/apps/sn/package.json
@@ -8,9 +8,7 @@
"build": "next build",
"start:sn": "next start -p 3002",
"lint": "next lint",
- "test:refactor": "node scripts/run-refactor-tests.cjs",
"typecheck": "tsc --noEmit --incremental false --pretty false",
- "check:boundaries": "node scripts/check-module-boundaries.cjs",
"lint:refactor": "eslint src/shared/api/http-transport.ts src/app/providers/app-provider.tsx src/features/dredging src/features/mapLayers src/features/gisManagement --rule \"@typescript-eslint/no-explicit-any:warn\" --max-warnings 22"
},
"dependencies": {
diff --git a/apps/sn/src/app/providers/app-provider.tsx b/apps/sn/src/app/providers/app-provider.tsx
index f665ac235..1c00693db 100644
--- a/apps/sn/src/app/providers/app-provider.tsx
+++ b/apps/sn/src/app/providers/app-provider.tsx
@@ -13,6 +13,7 @@ import { AlertDialog } from "@/shared/ui/alert-dialog";
import { PromptDialog } from "@/shared/ui/prompt-dialog";
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
import { BusinessUserMessageBridge } from "@/shared/ui/business-user-message-bridge";
+import { GridCellTooltip } from "@/shared/ui/grid-cell-tooltip";
// 개발 도구는 실제 로컬 개발 환경에서만 별도 청크로 불러와 최초 페이지 컴파일 범위를 줄인다.
const LazyReactQueryDevtools = dynamic(
@@ -30,6 +31,7 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
+
diff --git a/apps/sn/src/app/styles/grid-ellipsis.css b/apps/sn/src/app/styles/grid-ellipsis.css
new file mode 100644
index 000000000..45f45f457
--- /dev/null
+++ b/apps/sn/src/app/styles/grid-ellipsis.css
@@ -0,0 +1,19 @@
+/* SN 목록 그리드의 데이터 텍스트만 한 줄로 제한한다. 폼·병합·액션 셀은 보존한다. */
+body[data-sn-grid-ellipsis="true"] table:has(> thead) > tbody > tr > td:not([colspan]):not([rowspan]):not([data-sn-cell-wrap]):not(:has(input, textarea, select, button, [role="checkbox"], [role="switch"], [contenteditable="true"], table, img, svg)) {
+ max-width: 0;
+ overflow: hidden;
+ white-space: nowrap !important;
+ text-overflow: ellipsis;
+}
+
+/* 셀 안의 block/whitespace-normal·pre-line 텍스트도 실제 열 너비 안에서 생략한다. */
+body[data-sn-grid-ellipsis="true"] table:has(> thead) > tbody > tr > td:not([colspan]):not([rowspan]):not([data-sn-cell-wrap]):not(:has(input, textarea, select, button, [role="checkbox"], [role="switch"], [contenteditable="true"], table, img, svg)) :is(div, span, a, p) {
+ display: block;
+ min-width: 0;
+ max-width: 100%;
+ overflow: hidden;
+ white-space: nowrap !important;
+ overflow-wrap: normal;
+ word-break: normal;
+ text-overflow: ellipsis;
+}
diff --git a/apps/sn/src/features/cfmtnBgt/ui/CfmtnBgtPopupContent.tsx b/apps/sn/src/features/cfmtnBgt/ui/CfmtnBgtPopupContent.tsx
index 4784372bb..8c3e7d03a 100644
--- a/apps/sn/src/features/cfmtnBgt/ui/CfmtnBgtPopupContent.tsx
+++ b/apps/sn/src/features/cfmtnBgt/ui/CfmtnBgtPopupContent.tsx
@@ -3,6 +3,7 @@
*/
"use client";
+import { getSnRowNumber } from "@/shared/lib/row-number";
import { useEffect, useMemo, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
@@ -10,7 +11,6 @@ import useOnsemiroAddrStore from "@/app/store/useOnsemiroAddrStore";
import {
StandardDataTable,
StandardPopupLayout,
- getStandardRowNumber,
} from "@common/business_components";
import {
Button,
@@ -273,7 +273,7 @@ export default function CfmtnBgtPopupContent({
() =>
(Array.isArray(data.content) ? data.content : []).map((row, index) => ({
...row,
- rowNumber: getStandardRowNumber({
+ rowNumber: getSnRowNumber({
total: data.totalElements,
rowIndex: index,
pageIndex: page,
diff --git a/apps/sn/src/features/ctyFldngPrvtnBiz/ui/CtyFldngPrvtnBizListPage.tsx b/apps/sn/src/features/ctyFldngPrvtnBiz/ui/CtyFldngPrvtnBizListPage.tsx
index d5d753ff8..9d4107ddd 100644
--- a/apps/sn/src/features/ctyFldngPrvtnBiz/ui/CtyFldngPrvtnBizListPage.tsx
+++ b/apps/sn/src/features/ctyFldngPrvtnBiz/ui/CtyFldngPrvtnBizListPage.tsx
@@ -1,12 +1,12 @@
"use client";
+import { getSnRowNumber } from "@/shared/lib/row-number";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import {
StandardDataTable,
StandardTableSortHeader,
- getStandardRowNumber,
type UiTableSort,
} from "@common/business_components";
import {
@@ -463,7 +463,7 @@ export function CtyFldngPrvtnBizListPage() {
result.content.map((row, i) =>
flattenRow(
row,
- getStandardRowNumber({
+ getSnRowNumber({
total: result.totalElements,
rowIndex: i,
pageIndex: target - 1,
diff --git a/apps/sn/src/features/exmn/ui/ExmnRegistrationPage.tsx b/apps/sn/src/features/exmn/ui/ExmnRegistrationPage.tsx
index 35eee27f8..7143ce61a 100644
--- a/apps/sn/src/features/exmn/ui/ExmnRegistrationPage.tsx
+++ b/apps/sn/src/features/exmn/ui/ExmnRegistrationPage.tsx
@@ -1,5 +1,6 @@
"use client";
+import { getSnRowNumber } from "@/shared/lib/row-number";
import LoadingOverlay from "@/components/ui/LoadingBar";
import { fileDownload } from "@/components/lib/utils";
import { getStartYearArray } from "@/components/ui/YearRangeSelectBox";
@@ -20,7 +21,6 @@ import {
STANDARD_ROW_NUMBER_HEADER,
StandardDataTable,
StandardTableSortHeader,
- getStandardRowNumber,
type UiTableSort,
} from "@common/business_components";
import {
@@ -146,7 +146,7 @@ export function ExmnRegistrationPage() {
() =>
businesses.map((business, index) => ({
...business,
- rowNumber: getStandardRowNumber({
+ rowNumber: getSnRowNumber({
total: totalElements,
rowIndex: index,
pageIndex: pagination.pageIndex,
diff --git a/apps/sn/src/features/exmn/ui/ExmnStandardDataTable.tsx b/apps/sn/src/features/exmn/ui/ExmnStandardDataTable.tsx
index 81d32e112..4d865d627 100644
--- a/apps/sn/src/features/exmn/ui/ExmnStandardDataTable.tsx
+++ b/apps/sn/src/features/exmn/ui/ExmnStandardDataTable.tsx
@@ -1,5 +1,6 @@
"use client";
+import { getSnRowNumber } from "@/shared/lib/row-number";
import { useMemo, type ReactNode } from "react";
import {
getCoreRowModel,
@@ -9,7 +10,6 @@ import {
} from "@tanstack/react-table";
import {
StandardDataTable,
- getStandardRowNumber,
type ScrollMode,
type UiHeadProps,
} from "@common/business_components";
@@ -43,7 +43,7 @@ const formatValue = (value: unknown) => {
: String(value);
};
-/** 조사관리 표를 CS 목록 규격과 역순 순번 계약으로 연결한다. */
+/** 조사관리 표를 CS 목록 규격과 페이지 연속 오름차순 순번으로 연결한다. */
export function ExmnStandardDataTable>({
rows,
headlist,
@@ -69,7 +69,7 @@ export function ExmnStandardDataTable>({
header: ROW_NUMBER_IDS.has(head.id) ? "순번" : head.title,
cell: ({ row, getValue }) => {
const value = ROW_NUMBER_IDS.has(head.id)
- ? getStandardRowNumber({
+ ? getSnRowNumber({
total,
rowIndex: row.index,
pageIndex: page - 1,
diff --git a/apps/sn/src/features/exmn/ui/ExmnStatusDetailPanel.tsx b/apps/sn/src/features/exmn/ui/ExmnStatusDetailPanel.tsx
index 2276ab421..e2e3d3d3c 100644
--- a/apps/sn/src/features/exmn/ui/ExmnStatusDetailPanel.tsx
+++ b/apps/sn/src/features/exmn/ui/ExmnStatusDetailPanel.tsx
@@ -189,7 +189,7 @@ export function ExmnStatusDetailPanel({
const counts = getCounts(row);
return {
trtdstId: row.trtdstId,
- rowNumber: summaryRows.length - index,
+ rowNumber: index + 1,
trtdstNm: row.trtdstNm,
trgtPrlgText: formatNumber(row.trgtPrlg),
drvngDstncText: formatNumber(row.drvngDstnc),
diff --git a/apps/sn/src/features/exmn/ui/TrtdstManagementPage.tsx b/apps/sn/src/features/exmn/ui/TrtdstManagementPage.tsx
index 3ade6952d..8ec83c2c5 100644
--- a/apps/sn/src/features/exmn/ui/TrtdstManagementPage.tsx
+++ b/apps/sn/src/features/exmn/ui/TrtdstManagementPage.tsx
@@ -1,12 +1,12 @@
"use client";
+import { getSnRowNumber } from "@/shared/lib/row-number";
import LoadingOverlay from "@/components/ui/LoadingBar";
import useStore from "@/store/useStore";
import {
STANDARD_ROW_NUMBER_HEADER,
StandardDataTable,
StandardTableSortHeader,
- getStandardRowNumber,
sortStandardTableRows,
type UiTableSort,
} from "@common/business_components";
@@ -73,16 +73,16 @@ const createLocalKey = () =>
? `client:${crypto.randomUUID()}`
: `client:${Date.now()}-${Math.random().toString(36).slice(2)}`;
-/** 화면의 현재 행 순서를 전체 건수에서 시작하는 공통 내림차순 순번으로 정규화한다. */
-const withDescendingRowNumbers = (rows: TrtdstTableRow[]): TrtdstTableRow[] =>
+/** 화면의 현재 행 순서에 1부터 오름차순 표시 순번을 붙인다. */
+const withAscendingRowNumbers = (rows: TrtdstTableRow[]): TrtdstTableRow[] =>
rows.map((row, index) => ({
...row,
- rowNumber: getStandardRowNumber({ total: rows.length, rowIndex: index }),
+ rowNumber: getSnRowNumber({ total: rows.length, rowIndex: index }),
}));
/** 서버 처리분구 목록을 공통 표와 변경 추적에 필요한 행 구조로 변환한다. */
const toTableRows = (items: TrtdstSettingDto[]): TrtdstTableRow[] =>
- withDescendingRowNumbers(
+ withAscendingRowNumbers(
items.map((item) => ({
...item,
trtdstId: String(item.trtdstId),
@@ -103,7 +103,7 @@ const toSavedTableRows = (
rows: TrtdstTableRow[],
savedItems: TrtdstSettingDto[] = [],
): TrtdstTableRow[] =>
- withDescendingRowNumbers(
+ withAscendingRowNumbers(
rows
.filter((row) => row.rowStatus !== "deletePending")
.map((row) => {
@@ -300,7 +300,7 @@ export function TrtdstManagementPage() {
setSort(undefined);
setPagination((current) => ({ ...current, pageIndex: 0 }));
setRows((current) =>
- withDescendingRowNumbers([
+ withAscendingRowNumbers([
...current,
{
trtdstId: "",
@@ -353,7 +353,7 @@ export function TrtdstManagementPage() {
if (selectedRow.rowStatus === "added") {
if (!(await confirm("선택한 처리분구를 삭제하시겠습니까?"))) return;
setRows((current) =>
- withDescendingRowNumbers(
+ withAscendingRowNumbers(
current.filter((row) => row.localKey !== selectedRow.localKey),
),
);
diff --git a/apps/sn/src/features/obsltPpl/lib/obslt-ppl-excel.ts b/apps/sn/src/features/obsltPpl/lib/obslt-ppl-excel.ts
index 6fef56b6d..8de99efef 100644
--- a/apps/sn/src/features/obsltPpl/lib/obslt-ppl-excel.ts
+++ b/apps/sn/src/features/obsltPpl/lib/obslt-ppl-excel.ts
@@ -33,7 +33,7 @@ const SHEET_CONTRACTS: SheetContract[] = [
const DATA_START_ROW_INDEX = 7;
const FIRST_DATA_COLUMN_INDEX = 1;
-const DATA_COLUMN_COUNT = 21;
+const DATA_COLUMN_COUNT = 20;
const DOWNLOAD_FILENAME = "노후하수관로 정밀조사 및 정비사업 현황.xlsx";
/** API 문자열에 남은 Excel 개행 이스케이프를 실제 줄바꿈으로 정리한다. */
@@ -63,7 +63,7 @@ const validateHeaders = (sheet: XLSX.WorkSheet, contract: SheetContract) => {
[6, 14, "증빙서류제출여부"], [5, 15, contract.progressHeader],
[6, 15, contract.startDateHeader], [6, 16, "준공(예정)일자(yyyy.mm.dd)"],
[5, 17, "사업추진부진사유"], [5, 18, "사업담당자(지자체)"],
- [6, 18, "이름"], [6, 19, "연락처"], [5, 20, "비고"], [6, 21, "공단의견"],
+ [6, 18, "이름"], [6, 19, "연락처"], [5, 20, "비고"],
];
for (const [rowIndex, columnIndex, expected] of expectedHeaders) {
@@ -116,7 +116,7 @@ const dateText = (value: string): string | null => {
return (digits || normalized).slice(0, 8);
};
-/** B~V가 모두 비어 있는 행은 데이터가 아닌 양식 여백으로 판단한다. */
+/** B~U가 모두 비어 있는 행은 데이터가 아닌 양식 여백으로 판단한다. */
const isEmptyDataRow = (sheet: XLSX.WorkSheet, rowIndex: number) =>
Array.from({ length: DATA_COLUMN_COUNT }, (_, offset) =>
cellText(sheet, rowIndex, FIRST_DATA_COLUMN_INDEX + offset),
@@ -159,7 +159,6 @@ const parseSheet = (sheet: XLSX.WorkSheet, contract: SheetContract): ObsltPplBus
bizPicNm: optionalText(valueAt(17)),
bizPicCnpl: optionalText(valueAt(18)),
rmrkCn: optionalText(valueAt(19)),
- pbcpOpnnCn: optionalText(valueAt(20)),
});
}
return result;
@@ -183,27 +182,27 @@ export const parseObsltPplExcel = async (file: File): Promise {
- const rows = Array.from({ length: DATA_START_ROW_INDEX }, () => Array(22).fill(""));
+ const rows = Array.from({ length: DATA_START_ROW_INDEX }, () => Array(21).fill(""));
const topHeaders = [
"순번", "정밀\n조사\n차수", contract.businessCodeHeader.replace("사업코드", "\n사업코드"),
"시도", "시군", "환경청", "사업명", "추진현황", "착수\n년도", "준공(예정)\n년도",
contract.extensionHeader, "", "", "", contract.progressHeader.replace("추진현황", " 추진현황"),
- "", "사업추진 부진 사유", "사업 담당자(지자체)", "", "비고", "",
+ "", "사업추진 부진 사유", "사업 담당자(지자체)", "", "비고",
];
const bottomHeaders = [
"", "", "", "", "", "", "", "", "", "", "계획연장(km)", "상반기(예상)연장(km)",
"잔여연장(km)", "증빙서류\n제출여부", contract.startDateHeader.replace("(yyyy.mm.dd)", "\n(yyyy.mm.dd)"),
"준공(예정)일자\n(yyyy.mm.dd)", "", "이름", "연락처",
- "(필요시 사업 담당자 추가 작성)", "공단의견",
+ "(필요시 사업 담당자 추가 작성)",
];
topHeaders.forEach((value, index) => { rows[5]![index + FIRST_DATA_COLUMN_INDEX] = value; });
bottomHeaders.forEach((value, index) => { rows[6]![index + FIRST_DATA_COLUMN_INDEX] = value; });
return rows;
};
-/** DB에 저장하지 않는 B열 순번을 시트 출력 순서로 만들고 C~V에 업무값을 배치한다. */
+/** DB에 저장하지 않는 B열 순번을 시트 출력 순서로 만들고 C~U에 업무값을 배치한다. */
const downloadDataRow = (row: ObsltPplBusiness, index: number): Array => [
"",
index + 1,
@@ -226,7 +225,6 @@ const downloadDataRow = (row: ObsltPplBusiness, index: number): Array ({ wch }));
XLSX.utils.book_append_sheet(workbook, worksheet, contract.sheetName);
@@ -304,7 +302,7 @@ export const downloadObsltPplExcel = async (
fill: "FFF2CC",
border: true,
});
- styledSheet.range("B6:V7").style({
+ styledSheet.range("B6:U7").style({
bold: true,
fontFamily: "맑은 고딕",
fontSize: 10,
@@ -315,7 +313,7 @@ export const downloadObsltPplExcel = async (
});
styledSheet.range("B6:H7").style("fill", "B4C6E7");
styledSheet.range("I6:Q7").style("fill", "FFF2CC");
- styledSheet.range("R6:V7").style("fill", "FCE4D6");
+ styledSheet.range("R6:U7").style("fill", "FCE4D6");
styledSheet.row(2).height(26);
styledSheet.row(3).height(26);
styledSheet.row(5).height(22);
@@ -325,7 +323,7 @@ export const downloadObsltPplExcel = async (
if (rowCount > 0) {
const dataStartRow = DATA_START_ROW_INDEX + 1;
const dataEndRow = DATA_START_ROW_INDEX + rowCount;
- styledSheet.range(`B${dataStartRow}:V${dataEndRow}`).style({
+ styledSheet.range(`B${dataStartRow}:U${dataEndRow}`).style({
fontFamily: "맑은 고딕",
fontSize: 10,
horizontalAlignment: "center",
@@ -336,7 +334,7 @@ export const downloadObsltPplExcel = async (
styledSheet
.range(`L${dataStartRow}:N${dataEndRow}`)
.style("numberFormat", "#,##0.###");
- ["H", "R", "U", "V"].forEach((column) =>
+ ["H", "R", "U"].forEach((column) =>
styledSheet
.range(`${column}${dataStartRow}:${column}${dataEndRow}`)
.style({ horizontalAlignment: "left", wrapText: true }),
diff --git a/apps/sn/src/features/obsltPpl/model/obslt-ppl-types.ts b/apps/sn/src/features/obsltPpl/model/obslt-ppl-types.ts
index dd991d1bf..dfd7adf90 100644
--- a/apps/sn/src/features/obsltPpl/model/obslt-ppl-types.ts
+++ b/apps/sn/src/features/obsltPpl/model/obslt-ppl-types.ts
@@ -22,7 +22,6 @@ export interface ObsltPplBusiness {
bizPicNm?: string | null;
bizPicCnpl?: string | null;
rmrkCn?: string | null;
- pbcpOpnnCn?: string | null;
regDt?: string | null;
mdfcnDt?: string | null;
}
diff --git a/apps/sn/src/features/obsltPpl/ui/ObsltPplFormPage.tsx b/apps/sn/src/features/obsltPpl/ui/ObsltPplFormPage.tsx
index e69c24d5d..5ba43063e 100644
--- a/apps/sn/src/features/obsltPpl/ui/ObsltPplFormPage.tsx
+++ b/apps/sn/src/features/obsltPpl/ui/ObsltPplFormPage.tsx
@@ -79,7 +79,6 @@ const FIELDS: Array<{
{ key: "bizPicNm", label: "담당자 이름" },
{ key: "bizPicCnpl", label: "담당자 연락처" },
{ key: "rmrkCn", label: "비고", type: "textarea" },
- { key: "pbcpOpnnCn", label: "공단 의견", type: "textarea" },
];
const NUMBER_KEYS = new Set([
@@ -530,7 +529,6 @@ export function ObsltPplFormPage({ id }: { id?: string }) {
{renderField("bizPicCnpl", 2)}
{renderField("bizPrplDlyRsnCn", 3)}
{renderField("rmrkCn", 3)}
- {renderField("pbcpOpnnCn", 3)}