` ~ ``
+
+| 영역 | JSP 식별 | 개요 |
+| ---- | ---------------------------------- | --------------------------------------------------------------- |
+| A | `gTitle1` | 페이지 타이틀·브레드크럼 |
+| B | `slide_10` | 기본계획변경 신청(마스터·연도·개요) |
+| C | `slide_15` | 신청자·설계자 정보 |
+| D | `slide_19` | 개인정보 동의 |
+| E | `slide_11` | 사업개요(단계별 인구·보급률) |
+| F | `slide_12` | 총사업비 — `BizCstMatrixFormSection` 전용 |
+| G | `slide_13` | 재원분담 |
+| H | `slide_14` | 처리시설별 하수도정비기본계획 (Checkbox + Tabs) |
+| I | `slide_16` | 체크리스트 + 필수첨부 + 기타첨부 |
+| J | JSP 상단 `saveBtn` / `presentnBtn` | React: 폼 하단 액션바 (`fnrs-form-page` 279~323과 동일 구성, API만 sewer) |
+
+### 1-2. 제외
+
+- `<%@ include %>`: top / sideMenu / footer / `bscPlnPopup.jsp` / `sndrdBzAmnt.jsp`
+- 레이아웃·메뉴·권한 셸 → 기존 `BaseLayout` / `PageLayout` / AUTH 체계
+- `mStep1` 진행 단계 표시 (미구현 · 마이그레이션 범위 밖)
+
+팝업(`이전기본계획조회`)은 **별도 Dialog 컴포넌트**로 재구현하며, include JSP 자체는 포팅하지 않는다.
+
+---
+
+## 2. 목표 아키텍처
+
+```
+app/(with-base-layout)/sewer-bsc-plan/regist/page.tsx
+ └─ pages/sewer-bsc-plan/ui/sewer-bsc-plan-regist-page.tsx
+ └─ sewer-bsc-plan-form-page.tsx # FormProvider + 섹션 조립
+ ├─ sections/*-form-section.tsx # 도메인 전용
+ └─ widgets/pbadms-sprt/* # 공통 재사용
+
+entities/sewer-bsc-plan/ # 이미 구현됨
+ saveApply / findDetails / submitSupplement …
+```
+
+**폼 스택 (fnrs-cnsltn과 동일)**
+
+- `react-hook-form` + `zod` (`zodResolver`, `mode: "onChange"`)
+- 섹션 래퍼: `SectionCollapsible` (Accordion 아님)
+- UI: `@common/components` (Input, Select, Textarea, Button, Radio, Tabs, Table 등)
+- 그리드가 필요한 곳만 `@tanstack/react-table` + `DataTable`
+- 데이터: React Query (`useSaveApplySewerBscPlan` 등) — 목데이터는 API 미연동 구간만 `@faker-js/faker`
+
+**Vercel React Best Practices (적용 요약)**
+
+- `client-swr-dedup`: React Query로 체크리스트/필수첨부/코드 조회 중복 제거
+- `bundle-dynamic-imports`: 처리시설 Tabs 패널·이전계획 Dialog는 `next/dynamic` 후보
+- `rerender-derived-state`: 단계 연도·합계·진행률은 render 중 파생, 불필요 effect 금지
+- `async-parallel`: 등록 진입 시 코드/체크리스트트리/필수첨부트리를 병렬 조회
+
+---
+
+## 3. 전체 레이아웃
+
+`fnrs-form-page.tsx`와 동일한 **FormProvider + 세로 섹션 스택** 구조를 따르고,
+하단 액션바는 **[`fnrs-form-page.tsx` 279~323](../../src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx)** 과 동일 구성(목록·저장·신청완료·삭제·보완완료)이며 **API/라우트만 sewer** 로 바꾼다.
+타이틀·브레드크럼은 폼 밖(`PageLayout` / regist·edit·view 셸)에서 처리한다.
+(상세 View의 sticky FAB·검토/결재 버튼은 본 등록 문서 범위 밖 — `fnrs-view-page` 대응 후속.)
+
+### 3-1. 컴포넌트 트리 (fnrs 대응)
+
+```
+PageLayout (title / breadcrumb)
+└─ SewerBscPlanRegistPage | SewerBscPlanEditPage | SewerBscPlanViewPage
+ └─ SewerBscPlanFormPage | SewerBscPlanViewPage 본문
+ FormProvider (등록/수정) / 읽기 섹션 (상세)
+ └─
│
+└─────────────────────────────────────────────────────────────────┘
+```
+
+### 3-3. 액션바 동작 (`fnrs-form-page` 279~323 동일, API만 sewer)
+
+구현 위치: `sewer-bsc-plan-form-page.tsx` 하단
+(`fnrs-form-page.tsx`의 `mx-4 flex flex-wrap justify-center gap-3` 블록을 그대로 옮기고 훅·path만 교체)
+
+| 버튼 | 조건 | 대표 동작 |
+| -------- | ---------------------------- | -------------------------------------------------------------------------- |
+| 목록 | 항상 | `router.push("/sewer-bsc-plan")` |
+| 저장 | `!isSplmntPrgrsStts` | `type="submit"` → `saveApply` + 임시저장(`SV`) |
+| 신청완료 | edit && `!isSplmntPrgrsStts` | 체크리스트 완료 + **필수 첨부 완료** 검증 → `saveApply` + `LE` |
+| 삭제 | edit && `!isSplmntPrgrsStts` | confirm → `delete` → 목록 |
+| 보완완료 | `isSplmntPrgrsStts` (SL) | `form.handleSubmit` → `useSubmitSupplementSewerBscPlan` |
+
+**신청완료 사전 검증** (fnrs `handleApplyClick`과 동일 정책)
+
+1. **체크리스트**: `isFormChecklistComplete(cklstResultList)` — 미완료 시 `"모든 체크리스트 항목을 체크해야 신청이 가능합니다."`
+2. **필수 첨부**: `esntlAtchYn === "Y"`인 필수첨부 슬롯마다 `esntlAtchFileList`에 `atchFileGroupId`가 있어야 함
+ - Form: `AttachmentsFormSection`의 트리 + 폼 `esntlAtchFileList`
+ - 미충족 시 예: `"필수 첨부파일을 모두 등록해야 신청이 가능합니다."`
+ - 헬퍼 후보: `isFormEsntlAtchComplete(requiredTree, esntlAtchFileList)` (`pbadms-sprt`에 추가)
+
+저장에는 위 검증을 적용하지 않는다 (임시저장 허용).
+
+저장/제출 버튼을 섹션 헤더(`gTitle2 gRt`)에 두지 않는다 — **폼 하단 액션바 단일 진입점** (§4-J).
+
+### 3-4. 라우트
+
+| 화면 | path | 셸 → FormPage |
+| ---- | --------------------------- | ------------------------------------------ |
+| 등록 | `/sewer-bsc-plan/regist` | `mode="create"` |
+| 수정 | `/sewer-bsc-plan/[id]/edit` | `mode="edit"` + `useSewerBscPlanDetails` |
+| 상세 | `/sewer-bsc-plan/[id]` | ViewPage (본 문서 범위 밖, fnrs-view 대응) |
+
+`docSeComCd` 후보: 백엔드 체크리스트·필수첨부 등록값 확인 후 **`SEWER`** (또는 레거시 `DMBP`) — **TBD, CMS 코드와 맞출 것**.
+
+---
+
+## 4. 섹션별 상세 구현
+
+### 4-0. Hidden / 세션 컨텍스트 (폼 루트)
+
+JSP hidden → React 폼 기본값·세션 스토어로 흡수.
+
+| JSP | 의미 | React |
+| ------------------------------- | --------------- | ------------------------------------------------------- |
+| `chgSe` / `chgSeSp` / `chgSeCd` | 변경구분 | `sewerBscPlan.sewerBscPlanSeComCd` + 표시용 코드명 |
+| `progrsSttus` | SAVE / PRESENTN | 저장·제출 시 `prgrsSttsComCd`로 매핑 (임시저장 vs 제출) |
+| `planSeCd` | 고정 `A` | 필요 시 상수 또는 API 필드 확인 |
+| `adstDstrCd` / `rgnCd` | 행정구역 | `sewerBscPlan.admdstCd` |
+| `sidoNm` / `gugunNm` | 표시용 | 읽기 전용 표시 (`detDto.sido`/`gugun`) |
+
+구현: `getCreateDefaultValues(userInfo)`에서 로그인 사용자·소속 지자체 주입.
+
+---
+
+### 4-A. 타이틀·브레드크럼
+
+| JSP | React |
+| ------------------------ | ------------------------------------------------------ |
+| `h3` 기본계획검토 | PageLayout title: **하수도기본계획 신청** (IIPBS 명명) |
+| 설치인가 및 제원협의 > … | 메뉴 메타 기반 breadcrumb |
+
+별도 섹션 컴포넌트 불필요. 라우트 `page.tsx` / layout metadata로 처리.
+
+---
+
+### 4-B. 기본계획변경 신청 (`slide_10`)
+
+**파일**: `ui/sections/basic-info-form-section.tsx`
+**래퍼**: `SectionCollapsible` title=`기본계획 신청`
+**필드 접두사**: `sewerBscPlan.*` + `sewerBscPlanDtl.*`
+
+| 표시명 | JSP | API 매핑 | UI | 필수 | 비고 |
+| ---------------- | ----------------- | ------------------------------- | ------------------ | ---- | ---------------------------------- |
+| 기본계획명 | `drngMtncPlanNm` | `sewerBscPlan.sewerBscPlanNm` | Input | Y | |
+| 계획년도 | `planYr` | `sewerBscPlanDtl.planYr` | Select (현재~2005) | Y | |
+| 기준년도 | `crtrYr` | `sewerBscPlanDtl.crtrYr` | Select | Y | |
+| 1~4단계 목표연도 | `goalYrStep1~4` | `frlvlGoalYr`~`folvlGoalYr` | Select (현재~+30) | Y | 1단계 변경 시 +5/+10/+15 자동 연동 |
+| 계획개요 | `bzCn` | `sewerBscPlanDtl.bizOtlnCn` | Textarea | Y | |
+| 구분 | `#chgSeNm` | `sewerBscPlanSeComCd` (+코드명) | 읽기 또는 Select | | 신규/변경 등 |
+| 신청 지자체 | `${lclgvrnmntNm}` | `admdstCd` | 읽기 전용 | | |
+
+**동작**
+
+- `goalYrStep1` onChange → 2·3·4단계 기본값 세팅 + E/F/G 헤더 연도 span 갱신 (`watch` 파생)
+- JSP 상단 `gRt` 저장/제출은 사용하지 않음 → FormPage 하단 액션바 (§3-3, §4-J)
+
+---
+
+### 4-C. 신청자 정보 (`slide_15`)
+
+**재사용**: `@/widgets/pbadms-sprt`의 `ContactInfoFormSection`
+**props**: `fieldPrefix="sewerBscPlanDtl"` (또는 dtl 필드 구조에 맞게)
+
+| 표시 | JSP | API |
+| ---------------------------- | --------------------------- | --------------------------------------------------------------------------------------- |
+| 신청자 이름/전화/부서/이메일 | `${UsrNm}` 등 서버렌더 | 로그인 사용자 읽기 전용 → `aplcntNm`, `aplcntMblTelno`, `aplcntOgdpNm`, `aplcntEmlAddr` |
+| 설계자 | `dsgnrNm/Telno/Psitn/Email` | `dsgnrNm`, `dsgnrTelno`, `dsgnrOgdpNm`, `dsgnrEmlAddr` |
+
+신청자 4필드는 세션에서 defaultValues에 채우고 Input `disabled`.
+
+---
+
+### 4-D. 개인정보 동의 (`slide_19`)
+
+**재사용**: `PrivacyConsentSection`
+
+| JSP | React |
+| ----------------------------- | ------------------------------------------------------------------ |
+| 고지 textarea + 보유기간 문구 | 동일 문구 상수/마크다운 |
+| `privacyAgree` Y/N | 폼 필드 `privacyAgree: "Y" \| "N"` (API 미전송, 클라이언트 검증만) |
+
+**검증**: 제출 시 `Y` 필수 (JSP `presentnBtn`과 동일). 저장(임시)은 동의 없이 허용할지 정책 TBD — 기본은 JSP와 같이 **제출만 강제**.
+
+---
+
+### 4-E. 사업개요 (`slide_11`)
+
+**파일**: `ui/sections/outline-form-section.tsx` (신규, 도메인 전용)
+**API**: `sewerBscPlanOtlnList: SewerBscPlanOtlnItem[]` (planStpNo 1~4)
+
+| 행(구분) | JSP name | Item 필드 |
+| ------------ | --------------- | ------------------- |
+| 계획인구 | `arrPlanPpltn` | `sewerPlanPpltnCnt` |
+| 처리인구 | `arrPrcsPpltn` | `sewerPrcsPpltnCnt` |
+| 하수도보급율 | `arrDrngSplyRt` | `sewerPsplyRt` |
+
+열: 1~4단계 (헤더에 `frlvlGoalYr` 등 표시).
+
+**버튼**: `이전기본계획조회` → Dialog (`PreviousPlanLookupDialog`)
+
+- 레거시: `fnbscPlnPopupToggle` + `bscPlnPopup.jsp`
+- React: Modal + 조회 API (**TBD**: 이전 계획 조회 엔드포인트; 없으면 1차는 Dialog UI만 + mock)
+
+숫자 입력: 콤마 제거 후 number 전송 (제출 시 JSP `replaceComma` 대체).
+
+---
+
+### 4-F. 총사업비 (`slide_12`)
+
+**파일**: `ui/sections/biz-cst-matrix-form-section.tsx` (**전용 컴포넌트** — fnrs `BusinessCostFormSection` 재사용 금지)
+**래퍼**: `SectionCollapsible` title=`총사업비` (단위 `백만원`은 헤더 우측)
+
+**JSP 현실**: 단계(1~4) × 원가 계층(총사업비, 공사비, 처리시설, 하수관로 신설/보수, 펌프장, 배수설비, 기타, 부대경비, 설계/감리/부대비). 일부 readonly·자동합산 JS.
+
+**신규 API**: `bizCstList: SewerBscPlanBizCstItem[]` = `{ planStpNo, acntgSeCd, cstrnAmt }`
+
+**구현 방향 (`BizCstMatrixFormSection` 전용)**
+
+- 행 = `useAcntgSeMngTree` → `buildAcntgCostItems` (fnrs `BusinessCostFormSection`과 동일 출처)
+- `costItems` 로드 시 `useEffect`로 `bizCstList`를 `mergeBizCstList(existing, costItems)`로 동기화
+- 열 = `planStpNo` 1~4 (헤더에 목표연도 표시)
+- readonly 행(`CostItemDef.readonly=true`)은 children 합산으로 자동계산; Input에 `readOnly` + `bg-muted`
+- 변경 시 `setAmt` → `recalculateBizCstMatrix(list, costItems)` → `setValue("bizCstList")`
+- 저장 시 섹션 onChange 합산을 신뢰하여 `bizCstList` 그대로 전송 (`toBizCstSaveList` identity)
+
+**표준사업비 조회** 버튼은 JSP에서 주석 처리됨 → **미구현**.
+
+---
+
+### 4-G. 재원분담 (`slide_13`)
+
+**주의**: fnrs의 `FnrsSharFormSection`(재원협의 공유구분 트리)과 **다름**.
+
+| 행 | JSP | `SewerBscPlanFnrsCstItem` |
+| ------------ | -------------- | ------------------------- |
+| 총사업비 | `arrTotAmnt` | `sumAmt` (또는 파생) |
+| 국고 | `arrNlepAmt` | `gvexpAmt` |
+| 지방비 | `arrLcepAmt` | `lcexpAmt` |
+| 원인자부담금 | `arrCusrAlotm` | `orgprBdchgAmt` |
+| 기타 | `arrEtcAmt` | `etcAmt` |
+
+**파일**: `ui/sections/fnrs-cst-form-section.tsx` (신규)
+데이터: `fnrsCstList` 길이 4 (`planStpNo` 1~4).
+
+검증(선택): 단계별 `gvexp+lcexp+orgpr+etc === sumAmt` (JSP `calculateTotalFunding` 참고).
+
+---
+
+### 4-H. 처리시설별 하수도정비기본계획 (`slide_14`) — 핵심 복잡도
+
+**JSP 동작**
+
+1. `BscFctList` 체크박스로 처리시설 다중 선택 (`arrBscFctCd`)
+2. 선택 시 `createFormSection(code, name)`로 **당초 4단계 | 현재 | 변경 4단계** 매트릭스 동적 삽입
+ → **React**: 세로 삽입 대신 `@common/components` `Tabs`로 시설별 패널 관리 (§아래 Tabs 규칙)
+3. 활성 `formFields`(축소판): 처리구역면적, 하수처리인구, 계획하수량, 시설용량, 분류식 오수/우수, 합류식, 차집, 배수설비, 오수중계/일반/우수 펌프
+4. 시설별 필수첨부 `createFileAttachmentSection`
+5. `fetchBasicPlanData`로 당초(이전) 데이터 바인딩
+
+**API 매핑**
+
+| UI | DTO |
+| -------------------- | ------------------------------------------------------------- |
+| 선택 시설 목록 | `sewerBscPlanTmplntList[].swtrmtFcltCd` (+ `atchFileGroupId`) |
+| 단계별 수치 (변경측) | `sewerBscPlanTmplntDtlList[]` → `SewerBscPlanTmplntDtlItem` |
+| 임시저장 | `tmplntTmprStrgList` (편집 중 임시, 필요 시) |
+
+필드 대응 (변경 단계 기준):
+
+| UI 라벨 | `SewerBscPlanTmplntDtlItem` |
+| -------------- | --------------------------- |
+| 처리구역면적 | `prcsZoneArea` |
+| 하수처리인구 | `swtrmtPpltnCnt` |
+| 계획하수량 | `planSwqty` |
+| 시설용량 | `fcltCpct` |
+| 분류식 오수 | `sprsysDrtwPplLen` |
+| 분류식 우수 | `sprsysExlnPplLen` |
+| 합류식 | `cbsysPplLen` |
+| 차집관로 | `icptgPplLen` |
+| 배수설비 | `drgCnt` |
+| 오수 중계 펌프 | `drtwRlyPumpCnt` |
+| 오수 일반 펌프 | `drtwPumpCnt` |
+| 우수 펌프 | `exlnPumpCnt` |
+
+**구현 구성**
+
+```
+ui/sections/tmplnt-form-section.tsx
+ ├─ TmplntFacilityPicker # BscFctList Checkbox 목록 (시설 마스터 API TBD)
+ └─ Tabs (@common/components Tabs) # 선택 시설별 동적 패널
+ ├─ TabsList / TabsTrigger # value=swtrmtFcltCd, 라벨=시설명
+ └─ TabsContent (시설 1개당)
+ ├─ TmplntMatrixTable # 당초|현재|변경 매트릭스 (useFieldArray)
+ └─ TmplntFacilityAttachments # 시설별 필수첨부 (선택)
+```
+
+**Tabs 관리 규칙** (`packages/common/components/src/ui/tabs`)
+
+- `BscFctList` 체크 **선택** → `useFieldArray` append + 해당 시설 `TabsTrigger`/`TabsContent` 추가 → `Tabs` `value`를 신규 시설로 전환
+- 체크 **해제** → confirm 후 remove + 해당 탭 제거 → 남은 탭 중 하나로 `value` 이동 (없으면 Tabs 영역 비표시)
+- 선택 시설이 0개면 Tabs UI 숨김; 1개 이상이면 Tabs로 시설별 패널 전환
+- 매트릭스·첨부는 각 `TabsContent` 안에만 렌더 (JSP의 세로 동적 삽입을 탭 패널로 대체)
+
+**매트릭스 열**
+
+- **당초 열**: 이전계획 API 조회 결과 읽기 전용 (없으면 빈 칸)
+- **현재 열**: 기준년도 시점 값 (API 제공 시)
+- **변경 열**: 편집 가능 → 저장 payload의 dtl만 전송
+
+시설 마스터 목록 API: 레거시 `BscFctList` 대체 — **TBD** (`swtrmtFclt` 계열).
+
+`bundle-dynamic-imports`: `TmplntFormSection`(또는 TabsContent 내부 매트릭스)을 `next/dynamic` 후보.
+
+---
+
+### 4-I. 체크리스트 + 첨부 (`slide_16`)
+
+#### I-1. 체크리스트
+
+**재사용**: `ChecklistFormSection`
+`docSeComCd="
"`
+`fieldPrefix`: 마스터에서 `taskSeComCd`를 읽는 경로 (예: `sewerBscPlan`)
+
+| JSP | React |
+| ----------------------- | ------------------------------------------- |
+| 업무탭 `ceckEssntlArr` | 코드 `taskSeComCd` 다중 → Tabs |
+| 진행률 / 전체 예·아니오 | 위젯 내장 로직 |
+| `exmntResult` Y/N | `cklstResultList[].selected` (+ `picRvwCn`) |
+
+저장: `cklstResultList: CklstResultItem[]`
+
+#### I-2. 필수 첨부 (탭별)
+
+**재사용**: `AttachmentsFormSection`
+`docSeComCd` 동일, `esntlAtchFileList`
+
+레거시: 체크리스트 탭마다 `essntlList` + `uploadFile` multipart.
+신규: TUS/`attach-service` + `esntlAtchFileId` / `atchFileGroupId` (fnrs와 동일).
+
+#### I-3. 기타 첨부
+
+`AttachmentsFormSection`의 etc 영역 또는 `atchFileGroupId` on `sewerBscPlanDtl`.
+JSP `uploadFileETC` multiple → Zip 안내 문구 유지.
+
+---
+
+### 4-J. 저장 / 제출
+
+**UI 구성**: [`fnrs-form-page.tsx` 279~323](../../src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx) 과 **동일**.
+마크업·분기(`isSplmntPrgrsStts` / `isEditMode`)·버튼 색상·`disabled={isSubmitting}` 패턴을 그대로 두고, **호출 API·목록 path만 sewer** 로 교체한다.
+
+```tsx
+
+
+ {!isSplmntPrgrsStts ? (
+ <>
+
+ {isEditMode && (
+ <>
+
+
+ >
+ )}
+ >
+ ) : (
+
+ )}
+
+```
+
+| 버튼 | fnrs 대응 | sewer API / 동작 |
+| -------- | ----------------------------- | ------------------------------------------------------------ |
+| 목록 | `router.push("/fnrs-cnsltn")` | `router.push("/sewer-bsc-plan")` |
+| 저장 | `saveApply` + 임시저장 | `useSaveApplySewerBscPlan` + `prgrsSttsComCd`=임시저장(`SV`) |
+| 신청완료 | `handleApplyClick` → 제출 | 체크리스트+필수첨부 검증 → `saveApply` + `LE` (상태코드 TBD) |
+| 삭제 | `handleDelClick` | `useDeleteSewerBscPlan`(또는 동등) → 목록 |
+| 보완완료 | `onSplmntCmptn` | `useSubmitSupplementSewerBscPlan` |
+
+| JSP | 진행값 | URL | React |
+| ---- | ---------- | ------------------------- | ---------------------------------------------------------- |
+| 저장 | `SAVE` | `insertFnrscDmbp.do` | `saveApply` + 임시저장 코드 |
+| 제출 | `PRESENTN` | `insertFnrscDscss.do` (!) | `saveApply` + 제출 상태코드 — **백엔드 상태코드 확인 TBD** |
+
+페이로드 조립 (`buildApplyPayload`):
+
+```ts
+SewerBscPlanApplySaveRequestDto {
+ sewerBscPlan, sewerBscPlanDtl,
+ sewerBscPlanOtlnList, bizCstList, fnrsCstList,
+ sewerBscPlanTmplntList, tmplntTmprStrgList?,
+ cklstResultList, esntlAtchFileList,
+ splmntDmndList?, rvwOpnnList? // 등록 화면에서는 보통 빈 배열
+}
+```
+
+성공 후: `invalidateQueries(sewerBscPlanBaseKey)` → `router.push('/sewer-bsc-plan')` (신청완료는 상세로 보낼 수 있음 — fnrs와 동일 정책).
+
+권한: JSP `AUTH3` → 기존 AS 권한 훅/역할로 버튼 `disabled`/`hidden`.
+
+---
+
+## 5. API / DTO 연결
+
+Base: `/as-service/api/v1/sewer-bsc-plans`
+엔티티: `@/entities/sewer-bsc-plan`
+
+| 화면 동작 | Hook / Service | 비고 |
+| -------------- | ---------------------------------------- | ---------------- |
+| 등록 저장·제출 | `useSaveApplySewerBscPlan` → `saveApply` | 본 문서 핵심 |
+| 수정 로드 | `useSewerBscPlanDetails(docNo, chgCycl)` | 편집 시 |
+| 마스터만 | `useSewerBscPlan` | 목록 행 클릭 등 |
+| 보완 제출 | `useSubmitSupplementSewerBscPlan` | edit + `prgrsSttsComCd=SL` 시 폼 하단 [보완완료] |
+| 기술검토 | `useTechReviewSewerBscPlan` | 등록 JSP 범위 밖 |
+
+등록 JSP에 없는 섹션(보완요구·검토의견)은 **상세/검토 화면**에서 `SplmntDmndFormSection` / 검토의견 섹션으로 후속 구현.
+
+---
+
+## 6. 권장 파일 배치
+
+```
+apps/as/
+├── app/(with-base-layout)/sewer-bsc-plan/
+│ └── regist/page.tsx
+├── src/pages/sewer-bsc-plan/
+│ ├── lib/
+│ │ ├── sewer-bsc-plan-regist-schema.ts # zod
+│ │ └── form-data-mapper.ts # Det ↔ Form, Apply payload
+│ ├── model/
+│ │ └── (필요 시 table columns)
+│ └── ui/
+│ ├── sewer-bsc-plan-regist-page.tsx
+│ ├── sewer-bsc-plan-form-page.tsx
+│ ├── components/
+│ │ └── previous-plan-lookup-dialog.tsx
+│ └── sections/
+│ ├── basic-info-form-section.tsx
+│ ├── outline-form-section.tsx # 사업개요
+│ ├── biz-cst-matrix-form-section.tsx # 총사업비 (전용)
+│ ├── fnrs-cst-form-section.tsx # 재원분담
+│ └── tmplnt-form-section.tsx # 처리시설 (Checkbox + Tabs)
+└── docs/sewer-bsc-plan/
+ └── dmbp-regist-migration.md # 본 문서
+```
+
+공통 재사용 (신규 작성 최소화):
+
+- `ContactInfoFormSection`, `PrivacyConsentSection`
+- `ChecklistFormSection`, `AttachmentsFormSection`
+- `SectionCollapsible`
+- `Tabs` / `TabsList` / `TabsTrigger` / `TabsContent` (`@common/components` — 처리시설 패널)
+
+도메인 전용 (재사용 금지):
+
+- `BizCstMatrixFormSection` — 총사업비 (fnrs `BusinessCostFormSection` 미사용)
+- 폼 하단 액션바 — `fnrs-form-page` 279~323 복제, API만 sewer
+
+---
+
+## 7. 화면 데이터 흐름
+
+```
+BEGIN
+
+ // 최초 진입 (등록)
+ CALL 로그인사용자조회
+ CALL 코드조회(SEWERBSCPLANSE, SEWER, …) // 병렬
+ CALL 체크리스트트리(docSeComCd) // 병렬
+ CALL 필수첨부트리(docSeComCd) // 병렬
+ CALL 처리시설마스터목록 // TBD (BscFctList)
+ DISPLAY 폼 defaultValues
+
+ ON 1단계목표연도 CHANGE DO
+ SET 2·3·4단계 연도 (+5/+10/+15)
+ DISPLAY E/F/G/H 헤더 연도
+ END ON
+
+ ON 처리시설 체크 DO
+ ADD tmplntList 항목
+ ADD TabsTrigger / TabsContent (시설 패널)
+ SET Tabs value = 신규 시설
+ CALL 이전기본계획수치(시설코드) // 당초열, TBD
+ END ON
+
+ ON 처리시설 체크해제 DO
+ CONFIRM 제거
+ REMOVE tmplntList + 해당 Tabs 패널
+ SET Tabs value = 남은 시설 (없으면 Tabs 숨김)
+ END ON
+
+ ON [이전기본계획조회] CLICK DO
+ OPEN PreviousPlanLookupDialog
+ END ON
+
+ ON [저장] CLICK DO
+ VALIDATE zod (동의 제외 가능)
+ CALL saveApply(payload, 임시저장 상태)
+ NAVIGATE 목록
+ END ON
+
+ ON [신청완료] CLICK DO
+ VALIDATE zod + privacyAgree=Y + 체크리스트 완료 + 필수첨부(esntlAtchYn=Y) 완료
+ CONFIRM 신청 확인
+ CALL saveApply(payload, 제출 상태 LE)
+ NAVIGATE 상세
+ END ON
+
+END
+```
+
+---
+
+## 8. JSP ↔ 신규 갭·리스크
+
+| # | 이슈 | 대응 |
+| --- | ---------------------------------------------------- | ------------------------------------------------- |
+| 1 | JSP 제출 URL이 `dscss`로 분기 (오타/구프로세스 가능) | 신규는 `saveApply` 단일 진입으로 통일 |
+| 2 | 총사업비 UI 계층 ≠ `BizCstItem` | `BizCstMatrixFormSection` 전용 + `acntgSeCd` 매핑 테이블 |
+| 3 | 재원분담 ≠ fnrs shar | 전용 `fnrs-cst-form-section` |
+| 4 | 처리시설 매트릭스(당초/현재/변경) | Checkbox 선택 → Tabs 패널; 저장은 변경 dtl 중심; 당초는 조회 전용 |
+| 5 | 시설·이전계획 API | TBD — 미정 시 체크박스 목록 mock(`faker`)만 |
+| 6 | `docSeComCd` 값 | CMS/체크리스트 마스터와 합의 |
+| 7 | 레거시 multipart vs TUS | fnrs 첨부 패턴 준수 |
+| 8 | 주석 처리된 상세 `formFields` (자연발생인구 등) | **축소된 formFields만** 이관; 주석 블록 복원 금지 |
+
+---
+
+## 9. 구현 순서 (권장)
+
+1. 라우트 + `FormPage` 골격 + zod 스키마(마스터·dtl·동의) + 폼 하단 액션바(§4-J)
+2. B·C·D 섹션 (공통 위젯 최대 활용)
+3. E 사업개요 + G 재원분담 (4열 매트릭스 패턴 공유)
+4. F 총사업비 (`BizCstMatrixFormSection` 전용)
+5. I 체크리스트·첨부
+6. H 처리시설 (`BscFctList` Checkbox + `@common/components` Tabs — 가장 복잡)
+7. 저장/신청완료/삭제/보완완료 API 연동 (§4-J)
+8. 이전계획 Dialog (API 확정 후)
+
+---
+
+## 10. 체크리스트 (jsp-migration-as)
+
+- [ ] contents만 이관 (include 제외)
+- [ ] vercel-react-best-practices 적용 (Query 병렬·파생상태·dynamic)
+- [ ] `@common/components` + 필요 시 DataTable
+- [ ] API·모델은 `@/entities/sewer-bsc-plan` 우선
+- [ ] 목데이터는 `@faker-js/faker`만 (시설 마스터 등 TBD 구간)
+- [ ] 로딩·빈·에러 상태
+- [ ] `router.push`로 목록 복귀
+
+---
+
+## 11. 후속 문서
+
+- 목록(`dmbpList.jsp`) / 상세(`dmbpView.jsp`) / 수정(`dmbpModify.jsp`)은 본 문서와 동일 섹션 체계로 별도 확장
+- 화면설계서 SPEC 형식이 필요하면 `docs/sewer-bsc-plan/SPEC.md`로 분리 (영역 A~J 표 + API 표)
diff --git a/apps/as/docs/spec/previous-plan-lookup/SPEC.md b/apps/as/docs/spec/previous-plan-lookup/SPEC.md
new file mode 100644
index 000000000..927121116
--- /dev/null
+++ b/apps/as/docs/spec/previous-plan-lookup/SPEC.md
@@ -0,0 +1,441 @@
+# 화면설계서: 이전기본계획조회 (기본 계획 찾기)
+
+## 1. 기본 정보
+
+| 항목 | 내용 |
+| ---------- | --------------------------------------------------------------- |
+| 버전 | 0.2 |
+| 프로세스명 | UI설계 |
+| 화면명 | 이전기본계획조회 (기본 계획 찾기) |
+| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
+| 메뉴 경로 | 하수도기본계획 등록·수정 > 사업개요 > [이전기본계획조회] Dialog |
+
+---
+
+## 2. 화면 목적
+
+하수도기본계획 등록·수정 화면에서 **이전(기수립) 기본계획** 수치와 관련 보고서를 **조회·다운로드**한다. 시도·구군·연도·처리시설을 선택한 뒤 총설·처리구역별 현황을 표시하고, 분류 트리에서 기본계획 파일을 내려받는다. **부모 사업개요 폼에는 수치를 반영하지 않는다**(레거시 `bscPlnPopup.jsp`와 동일).
+
+---
+
+## 3. 화면 구성
+
+### 3-1. 전체 레이아웃
+
+부모: 하수도기본계획 등록/수정 폼 → 사업개요 섹션의 `[이전기본계획조회]` 버튼으로 Dialog 오픈.
+
+```
+┌─────────────────────────────────────────────────────────────────────────────────┐
+│ Dialog 타이틀: 기본 계획 찾기 [닫기] │
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ [영역 A] 검색 필터 │
+│ 시도 ▼ 구군 ▼ 연도 ▼ 처리시설 ▼ [조회] │
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ [영역 B] 기본계획파일 │
+│ ┌──────────────────────┬────────────────────────────────────────────────────┐ │
+│ │ [영역 B-1] 분류 트리 │ [영역 B-2] 파일 목록 │ │
+│ │ (약 40%) │ (약 60%) │ │
+│ │ └ 노드… │ 파일명 / 다운로드 │ │
+│ └──────────────────────┴────────────────────────────────────────────────────┘ │
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ [영역 C] 총설 │
+│ ┌────────┬──────────────당초──────────────┬──기준──┬──────────────변경──────────────┐ │
+│ │ 구분 │ 1단계 │ 2단계 │ 3단계 │ 4단계 │ 기준 │ 1단계 │ 2단계 │ 3단계 │ 4단계 │ │
+│ ├────────┼───────┼───────┼───────┼───────┼────────┼───────┼───────┼───────┼───────┤ │
+│ │계획목표│ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ │
+│ │년도 │ │ │ │ │ │ │ │ │ │ │
+│ └────────┴───────┴───────┴───────┴───────┴────────┴───────┴───────┴───────┴───────┘ │
+├─────────────────────────────────────────────────────────────────────────────────┤
+│ [영역 D] 처리구역별 현황 │
+│ ┌────────┬──────────────당초──────────────┬──기준──┬──────────────변경──────────────┐ │
+│ │ 구분 │ 1~4단계(+연도헤더) │ 기준 │ 1~4단계(+연도헤더) │ │
+│ ├────────┼────────────────────────────────┼────────┼────────────────────────────────┤ │
+│ │ 하수처리인구 … 배제방식 (읽기 전용 행) │ │
+│ └────────┴────────────────────────────────┴────────┴────────────────────────────────┘ │
+└─────────────────────────────────────────────────────────────────────────────────┘
+```
+
+### 3-2. 화면 데이터 흐름
+
+```
+BEGIN
+
+ // 부모 폼 — 사업개요
+ ON [이전기본계획조회] 버튼 CLICK DO
+ OPEN PreviousPlanLookupDialog
+ // 프리필: 부모 폼 sewerBscPlan.sido / gugun / admdstCd
+ CALL findSidoCombo()
+ DISPLAY 시도 옵션 (영역 A)
+ IF 시도 프리필 THEN
+ CALL findGugunCombo(sidoCd)
+ DISPLAY 구군 옵션 (영역 A)
+ CALL getLvNowDmgMtncGnsvy({ level: "1", ctpvNm })
+ // TBD: useLvNowDmgMtncGnsvy enabled — level별 필수 파라미터만 검사하도록 조정
+ IF 구군 프리필 THEN
+ CALL getLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })
+ DISPLAY 연도 옵션 (영역 A) // children 중 level=3 노드의 gnsvyYr
+ CALL getClsfSystm({ clsfSystmNm: gugunNm })
+ DISPLAY 분류 트리 (영역 B-1)
+ END IF
+ END IF
+ END ON
+
+ // 영역 A: 시도 변경
+ ON 영역 A 시도 CHANGE DO
+ RESET 구군 / 연도 / 처리시설
+ CALL findGugunCombo(sidoCd)
+ CALL getLvNowDmgMtncGnsvy({ level: "1", ctpvNm })
+ // 구군 미선택 → 연도·처리시설 옵션 비움, 트리 빈 안내
+ END ON
+
+ // 영역 A: 구군 변경
+ ON 영역 A 구군 CHANGE DO
+ RESET 연도 / 처리시설
+ CALL getLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })
+ DISPLAY 연도 옵션 (영역 A) // level=3 노드 → gnsvyYr
+ CALL getClsfSystm({ clsfSystmNm: sggNm })
+ DISPLAY 분류 트리 (영역 B-1)
+ END ON
+
+ // 영역 A: 연도 변경
+ ON 영역 A 연도 CHANGE DO
+ RESET 처리시설
+ CALL getLvNowDmgMtncGnsvy({ level: "3", ctpvNm, sggNm, gnsvyYr })
+ DISPLAY 처리시설 옵션 (영역 A) // level=3 노드 children → swtrmtFcltCd / swtrmtFcltNm
+ END ON
+
+ // 영역 A: 조회
+ ON [조회] 버튼 CLICK DO
+ VALIDATE 시도, 구군, 연도(targetYear), 처리시설(swtrmtFcltCd)
+ CALL getNowDmgMtncGnsvy({ swtrmtFcltCd, ctpvNm, sggNm, targetYear })
+ DISPLAY 총설 (영역 C) // 응답[0]의 planYr / planYrDt* / planYrDc*
+ DISPLAY 처리구역별 현황 (영역 D) // 응답[0]의 지표 Dt1~4 / base / Dc1~4
+ END ON
+
+ // 영역 B: 트리 노드 선택
+ ON 영역 B-1 노드 CLICK DO
+ CALL getFile({ clsfSystmSn, sort, vetPrcsYn }) // Response DTO TBD
+ DISPLAY 파일 목록 (영역 B-2)
+ END ON
+
+ // 영역 B: 파일 다운로드
+ ON 영역 B-2 파일 CLICK DO
+ CALL getFilePrcs(fileSn) // 파일 메타(경로·제목) — Response TBD
+ CALL fileDownload({ filePath, fileName })
+ // 브라우저 다운로드 트리거
+ END ON
+
+ ON [닫기] DO
+ CLOSE PreviousPlanLookupDialog
+ // 부모 폼 값 변경 없음
+ END ON
+
+END
+```
+
+---
+
+## 4. 영역 A — 검색 필터
+
+구성: **시도 · 구군 · 연도 · 처리시설** (+ 조회).
+
+- 시도·구군: `adstdstr` 콤보 (기존 스펙 유지)
+- 연도·처리시설: 각각 `useLvNowDmgMtncGnsvy`를 **별도 호출**. `level`에 따라 반환 계층이 달라진다.
+
+### 4-1. 필드 정의
+
+| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
+| -------- | --------- | ---- | ------------------------- | ------------------------------------------------ | -------------------------------------------------------------- |
+| 시도 | Select | Y | 부모 `sewerBscPlan.sido` | `ctpvNm`(표시명), 코드는 `adstDstrCd` | `useFindSidoCombo` |
+| 구군 | Select | Y | 부모 `sewerBscPlan.gugun` | `sggNm`(표시명), 코드는 `adstDstrCd` | `useFindGugunCombo(sidoCd)` |
+| 연도 | Select | Y | 없음 | `gnsvyYr` / 조회 시 `targetYear` | `useLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })` |
+| 처리시설 | Select | Y | 없음 | `swtrmtFcltCd` ← 옵션 value, 라벨 `swtrmtFcltNm` | `useLvNowDmgMtncGnsvy({ level: "3", ctpvNm, sggNm, gnsvyYr })` |
+
+> 처리시설은 `bns-if` `useFacilityList`를 **사용하지 않는다**.
+
+### 4-2. 버튼
+
+| 버튼명 | 동작 |
+| ------ | -------------------------------------------------------- |
+| 조회 | 필수값 검증 후 `getNowDmgMtncGnsvy` 호출 → 영역 C·D 갱신 |
+| 닫기 | Dialog 종료 (부모 미반영) |
+
+### 4-3. `lvNowDmgMtncGnsvy` level 캐스케이드
+
+| 트리거 | Request `level` | Request 파라미터 | UI에 쓰는 응답 | 옵션 추출 |
+| --------- | --------------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------ |
+| 시도 선택 | `"1"` | `ctpvNm` | (하위 초기화용) | 연도·처리시설 Select 리셋. 구군은 `findGugunCombo` |
+| 구군 선택 | `"2"` | `ctpvNm`, `sggNm` | 연도 Select | 트리에서 `level === 3` 노드의 `gnsvyYr` |
+| 연도 선택 | `"3"` | `ctpvNm`, `sggNm`, `gnsvyYr` | 처리시설 Select | 선택 연도(`level === 3`) 노드의 `children[]` → `swtrmtFcltCd` / `swtrmtFcltNm` |
+
+상위 변경 시 하위 선택값·옵션을 RESET한다 (시도→구군·연도·시설 / 구군→연도·시설 / 연도→시설).
+
+---
+
+## 5. 영역 B — 기본계획파일
+
+### 5-1. 트리 (B-1)
+
+| 표시명 | Field | 비고 |
+| ------ | --------------- | ------------------------ |
+| 분류명 | `clsfSystmNm` | `ClsfSystmResponseDto` |
+| 노드키 | `clsfSystmSn` | 선택 시 파일 목록 조회 |
+| 상위키 | `upClsfSystmSn` | 클라이언트에서 트리 조립 |
+| 정렬 | `sortSeq` | |
+| 경로 | `path` | |
+
+- 조회 파라미터: `clsfSystmNm` = 선택 구군명(레거시와 동일). `upClsfSystmSn`은 하위 전개 시 TBD.
+- 시도·구군 미선택 시: 「시도와 구군을 선택하세요.」 안내.
+
+### 5-2. 파일 목록 (B-2)
+
+| 표시명 | Field | 비고 |
+| ------ | ----- | ------------------------------------------------------------------- |
+| 파일명 | TBD | `getFile` Response DTO가 `unknown` — 백엔드 스키마 확정 후 매핑 |
+| (기타) | TBD | 레거시: 트리 선택 후 목록 표시, 클릭 시 `fileprcs` → `fileDownload` |
+
+### 5-3. 버튼·상호작용
+
+| 동작 | 설명 |
+| -------------- | -------------------------------------------------------------- |
+| 트리 노드 클릭 | `getFile({ clsfSystmSn, … })` → B-2 목록 |
+| 파일 클릭 | `getFilePrcs(fileSn)` → `fileDownload({ filePath, fileName })` |
+| 빈 목록 | 「트리에서 항목을 선택하세요.」 / 「파일이 없습니다.」 |
+
+---
+
+## 6. 영역 C — 총설
+
+### 6-1. 테이블 구조
+
+| 행(구분) | 열 매핑 (당초1~~4 / 기준 / 변경1~~4) |
+| ------------ | ------------------------------------------------------------------------------ |
+| 계획목표년도 | `planYrDt1planYrDt4` ~~/~~ `planYr` ~~/~~ `planYrDc1planYrDc4` (+ `년` 접미사) |
+
+- 헤더: 당초(1~~4단계) · 기준 · 변경(1~~4단계)
+- 데이터 소스: `getNowDmgMtncGnsvy` 응답 배열의 **첫 번째** 요소
+- 읽기 전용
+
+---
+
+## 7. 영역 D — 처리구역별 현황
+
+### 7-1. 행 정의 (표시명 → Field 접두사)
+
+각 행은 `Dt1Dt4` ~~/ (접미사 없음=기준) /~~ `Dc1Dc4` 셀로 표시한다. 숫자는 천단위 콤마, 없으면 `-`.
+
+| 표시명 | Field 접두사 | 비고 |
+| ------------------------ | -------------------- | ---------------------------------------------- |
+| 하수처리인구(인) | `swtrmtPpltnCnt` | |
+| 자연적하수처리인구수(인) | `natrSwtrmtPpltnCnt` | |
+| 사회적하수처리인구수(인) | `scilSwtrmtPpltnCnt` | |
+| 하수처리구역면적 | `swtrmtZoneArea` | |
+| 물사용량원단위 | `wtrUsqtyOrgnUnit` | |
+| 오수전환율(%) | `drtwSwtcRt` | |
+| 변동부하율(%) | `flctnLodRt` | |
+| 오수원단위 | `drtwOrgnUnit` | |
+| 계획하수량합계(㎥/일) | `planSwageQntSum` | |
+| 생활오수(㎥/일) | `lvngDrtwSum` | |
+| 지하수(㎥/일) | `ugwtSum` | |
+| 공장폐수(㎥/일) | `fctryWwtSum` | |
+| 관광오수(㎥/일) | `toursmDrtwSum` | |
+| 군부대(㎥/일) | `mtbsSum` | |
+| 연계처리수(㎥/일) | `linkPrcsCntSum` | |
+| 기타오수(㎥/일) | `etcDrtwSum` | |
+| 지하수사용량(㎥/일) | `ugwtUseSum` | |
+| 시설용량(㎥/일) | `fcltCpctSum` | |
+| 배제방식 | `exclsMethExpln` | 문자열; 단계별 `*Dt*`/`*Dc*` 존재 시 동일 규칙 |
+
+헤더 3행째 연도: 영역 C와 동일하게 `planYrDt*` / `planYr` / `planYrDc*` 표시.
+
+---
+
+## 8. API/DTO 명세
+
+### 8-1. 공통
+
+| 항목 | 값 |
+| --------------- | ---------------------------------------------------- |
+| DC Base | `/dc-service/api/v1` (`ENDPOINT.DC_SERVICE`) |
+| AS adstdstr | `/as-service/api/v1/adstdstr` |
+| 클라이언트 모듈 | `@/entities/dc` (`dcService`), `@/entities/adstdstr` |
+
+> 영역 A 처리시설은 `bns-if`를 사용하지 않음.
+
+### 8-2. 시도·구군 콤보 (non-DC)
+
+| 기능 | Method | URL | Request DTO | Response DTO | API |
+| -------- | ------ | ----------------------------------- | ------------------------ | -------------------------------- | ---------------------------------------------------------- |
+| 시도목록 | GET | `/as-service/api/v1/adstdstr/sido` | — | `CmCdAdstDstrComboResponseDto[]` | `cmCdAdstDstrService.findSidoCombo` / `useFindSidoCombo` |
+| 구군목록 | GET | `/as-service/api/v1/adstdstr/gugun` | Query: `sido` (시도코드) | `CmCdAdstDstrComboResponseDto[]` | `cmCdAdstDstrService.findGugunCombo` / `useFindGugunCombo` |
+
+### 8-3. 연도·처리시설 계층 조회 — DC
+
+| 기능 | Method | URL | Request DTO | Response DTO | API |
+| --------------- | ------ | -------------------------------------- | ---------------------------- | -------------------------------- | --------------------------------------------------------- |
+| 계층형 계획목록 | GET | `/dc-service/api/v1/lvNowDmgMtncGnsvy` | `LvNowDmgMtncGnsvySearchDto` | `LvNowDmgMtncGnsvyResponseDto[]` | `dcService.getLvNowDmgMtncGnsvy` / `useLvNowDmgMtncGnsvy` |
+
+연도 Select·처리시설 Select는 **각각** `useLvNowDmgMtncGnsvy`를 호출한다 (`level`만 다르게).
+
+**Request (**`LvNowDmgMtncGnsvySearchDto`**)**
+
+| 필드 | 시도(`level=1`) | 구군→연도(`level=2`) | 연도→시설(`level=3`) |
+| --------- | --------------- | -------------------- | -------------------- |
+| `level` | `"1"` | `"2"` | `"3"` |
+| `ctpvNm` | 필수 | 필수 | 필수 |
+| `sggNm` | — | 필수 | 필수 |
+| `gnsvyYr` | — | — | 필수 |
+
+**Response 노드 필드 (**`LvNowDmgMtncGnsvyResponseDto`**, 부분 optional)**
+
+| 필드 | 등장 level | 설명 |
+| -------------- | ---------- | ------------------------ |
+| `level` | 1~3 | 계층 깊이 |
+| `ctpvNm` | 1 | 시도명 |
+| `sggNm` | 2 | 구군명 |
+| `gnsvyYr` | 3 | 연도 → 연도 Select value |
+| `swtrmtFcltCd` | 시설(리프) | 처리시설 코드 |
+| `swtrmtFcltNm` | 시설(리프) | 처리시설명 |
+| `children` | 1~3 | 하위 노드 배열 |
+
+> **TBD**: 현재 `useLvNowDmgMtncGnsvy` `enabled`가 `level && ctpvNm && sggNm && gnsvyYr`로 고정되어 `level=1`/`2` 호출이 막힘. level별 필수 파라미터만 검사하도록 수정 필요.
+>
+> 예: `level=1` → `ctpvNm` / `level=2` → `ctpvNm && sggNm` / `level=3` → `ctpvNm && sggNm && gnsvyYr`
+
+### 8-4. 기본계획 수치 조회 — DC
+
+| 기능 | Method | URL | Request DTO | Response DTO | API |
+| ---------------- | ------ | ------------------------------------ | -------------------------- | ------------------------------ | ----------------------------------------------------- |
+| 현재·단계별 수치 | GET | `/dc-service/api/v1/nowDmgMtncGnsvy` | `NowDmgMtncGnsvySearchDto` | `NowDmgMtncGnsvyResponseDto[]` | `dcService.getNowDmgMtncGnsvy` / `useNowDmgMtncGnsvy` |
+
+**Request (**`NowDmgMtncGnsvySearchDto`**)**
+
+| 필드 | 매핑 |
+| -------------- | ----------------------- |
+| `swtrmtFcltCd` | 영역 A 처리시설 |
+| `ctpvNm` | 영역 A 시도 표시명 |
+| `sggNm` | 영역 A 구군 표시명 |
+| `targetYear` | 영역 A 연도 (`gnsvyYr`) |
+
+**Response**: 지표 필드 + `Dt1Dt4` ~~/~~ `Dc1Dc4` 접미사 스테이지 필드 (`NowDmgMtncGnsvyResponseDto`). UI는 `data[0]` 사용.
+
+### 8-5. 분류체계 — DC
+
+| 기능 | Method | URL | Request DTO | Response DTO | API |
+| ------------ | ------ | ------------------------------ | -------------------- | ------------------------ | ----------------------------------------- |
+| 분류트리조회 | GET | `/dc-service/api/v1/clsfSystm` | `ClsfSystmParamsDto` | `ClsfSystmResponseDto[]` | `dcService.getClsfSystm` / `useClsfSystm` |
+
+**Request**: `clsfSystmNm?`, `upClsfSystmSn?`
+**Response**: `clsfSystmSn`, `upClsfSystmSn`, `clsfSystmNm`, `sortSeq`, `droppAble`, `path`
+
+> 레거시 경로 `/clsf` → 현재 클라이언트는 `/clsfSystm`.
+
+### 8-6. 파일 목록·처리·다운로드 — DC
+
+| 기능 | Method | URL | Request DTO / Params | Response DTO | API |
+| ------------ | ------ | --------------------------------- | ------------------------------------------------------- | ----------------- | -------------------------------------------- |
+| 파일목록 | GET | `/dc-service/api/v1/file` | `FileParamsDto` (`sort?`, `clsfSystmSn?`, `vetPrcsYn?`) | `unknown` **TBD** | `dcService.getFile` / `useFile` |
+| 파일처리정보 | GET | `/dc-service/api/v1/fileprcs` | Query: `fileSn` | `unknown` **TBD** | `dcService.getFilePrcs` / `useFilePrcs` |
+| 파일다운로드 | POST | `/dc-service/api/v1/fileDownload` | `FileDownloadParamsDto` (`filePath?`, `fileName?`) | `unknown` **TBD** | `dcService.fileDownload` / `useFileDownload` |
+
+> **TBD**: 파일 관련 Response 스키마·다운로드 blob/헤더 처리·`fileDownload` POST body vs query 전달 방식은 백엔드/게이트웨이 확인 후 확정.
+
+---
+
+## 9. UI 구현 가이드
+
+### 9-1. 기술 스택·원칙
+
+- UI: `@common/components` — `Dialog`, `Select`, `Button`, `Table`, `Skeleton`
+- 상태: Dialog 로컬 state + React Query (`entities/dc`, `adstdstr`)
+- 영역 A 연도·처리시설: `useLvNowDmgMtncGnsvy`를 level별 **두 번**(또는 시도 포함 시 세 번) 호출
+- 테이블: 읽기 전용 고정 행 → `@tanstack/react-table` / `DataTable` **불필요**
+- 트리: jsTree 미사용 — 재귀 들여쓰기 트리
+- 번들: `PreviousPlanLookupDialog`는 outline에서 `next/dynamic` 로드 권장
+- 부모 폼: **수치 미반영** (조회·다운로드 only)
+- 1차 구현 시 DC 미연동이면 `@faker-js/faker` mock + TODO → 이후 `dcService` 훅으로 교체
+
+### 9-2. 앱·파일 배치
+
+| 구분 | 경로 |
+| ------------ | ------------------------------------------------------------------------ |
+| 진입 버튼 | `pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx` |
+| Dialog | `pages/sewer-bsc-plan/ui/previous-plan-lookup-dialog.tsx` |
+| 필터 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-search-filters.tsx` |
+| 파일 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-file-section.tsx` |
+| 총설 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-summary-table.tsx` |
+| 처리구역 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-area-table.tsx` |
+| 셀 매핑 유틸 | `pages/sewer-bsc-plan/lib/previous-plan-table-utils.ts` |
+| mock(선택) | `pages/sewer-bsc-plan/lib/previous-plan-mock.ts` |
+| DC entity | `entities/dc/api/dc-service.ts`, `queries.ts`, `model/dc-interface.ts` |
+| 레거시 참조 | `adssNewOld/.../cmmn/bscPlnPopup.jsp` |
+
+### 9-3. 영역별 UI 매핑
+
+| 영역 | 컴포넌트 | 데이터 |
+| ---- | ------------------------------ | -------------------------------------------------------------- |
+| A | `previous-plan-search-filters` | adstdstr + `useLvNowDmgMtncGnsvy`(level 1·2·3) |
+| B | `previous-plan-file-section` | `useClsfSystm` + `useFile` + `useFilePrcs` + `useFileDownload` |
+| C | `previous-plan-summary-table` | `useNowDmgMtncGnsvy` / 조회 결과 state |
+| D | `previous-plan-area-table` | 동일 |
+
+### 9-4. 다이얼로그·라우팅
+
+| 항목 | 내용 |
+| ------------ | ------------------------------------------------------------------------ |
+| 다이얼로그명 | `PreviousPlanLookupDialog` / 타이틀 `기본 계획 찾기` |
+| 오픈 | 사업개요 `[이전기본계획조회]` |
+| 라우트 | 없음 (모달). 부모: `/sewer-bsc-plan/regist`, `/sewer-bsc-plan/[id]/edit` |
+| 선택 반영 | 없음 |
+
+### 9-5. 데이터 흐름 (구현 요약)
+
+```
+BEGIN
+
+ ON [이전기본계획조회] CLICK DO
+ OPEN PreviousPlanLookupDialog (sido/gugun 프리필)
+ END ON
+
+ ON 시도 CHANGE DO
+ CALL findGugunCombo
+ CALL getLvNowDmgMtncGnsvy(level=1)
+ RESET 연도·처리시설
+ END ON
+
+ ON 구군 CHANGE DO
+ CALL getLvNowDmgMtncGnsvy(level=2) // → 연도 옵션
+ CALL getClsfSystm
+ RESET 처리시설
+ END ON
+
+ ON 연도 CHANGE DO
+ CALL getLvNowDmgMtncGnsvy(level=3) // → 처리시설 옵션
+ END ON
+
+ ON [조회] CLICK DO
+ VALIDATE
+ CALL getNowDmgMtncGnsvy
+ DISPLAY 영역 C · 영역 D
+ END ON
+
+ ON 트리 노드 / 파일 CLICK DO
+ CALL getFile → getFilePrcs → fileDownload // Response TBD
+ END ON
+
+END
+```
+
+---
+
+## 10. 미확정 (TBD) 추적
+
+| ID | 항목 | 비고 |
+| --- | -------------------------------------------------------- | -------------------------------------------------------------------- |
+| T1 | `useLvNowDmgMtncGnsvy` enabled을 level별 필수값으로 분기 | `1`: ctpvNm / `2`: +sggNm / `3`: +gnsvyYr |
+| T2 | `getFile` / `getFilePrcs` / `fileDownload` Response DTO | 현재 `unknown` |
+| T3 | 파일 다운로드 UX (blob, Content-Disposition, 게이트웨이) | 레거시 apigateway 직접 호출과 차이 |
+| T4 | `clsfSystm`의 `upClsfSystmSn` 점진 로드 여부 | flat 목록 vs 계층 API |
+| T5 | 1차 mock vs 즉시 `dcService` 실연동 | 마이그레이션 플랜은 mock 우선, entity는 준비됨 |
+| T6 | 부모 폼 수치 반영 | 요구 없음(레거시 동일). 요청 시 별도 스펙 |
+| T7 | `level=1` 응답 UI 활용 여부 | 시도 선택 시 호출하나 구군은 adstdstr — 응답은 캐시·검증용일 수 있음 |
diff --git a/apps/as/middleware.ts b/apps/as/middleware.ts
index 94a1d0381..f5bd866f3 100644
--- a/apps/as/middleware.ts
+++ b/apps/as/middleware.ts
@@ -1,8 +1,16 @@
import { NextRequest, NextResponse } from "next/server";
-import { AUTH_USER_ID, CLAIM_NAME, PORT, PROXY_HOST, SERVER_API_URL, SITE_ID } from "@/shared/config";
+import {
+ AUTH_USER_ID,
+ CLAIM_NAME,
+ PORT,
+ PROXY_HOST,
+ SERVER_API_URL,
+ SITE_ID,
+} from "@/shared/config";
const serverApiUrl = process.env.SERVER_API_URL;
+const dcServiceApiUrl = process.env.DC_SERVICE_API_URL;
// CSP 헤더 설정 함수 (img-src만)
function addSecurityHeaders(response: NextResponse): NextResponse {
@@ -22,7 +30,15 @@ export function middleware(req: NextRequest) {
if (pathname.startsWith("/server")) {
const queryParams = searchParams.toString();
- const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
+
+ let serverUrl: string | undefined;
+ if (pathname.includes("/dc-service")) {
+ serverUrl = dcServiceApiUrl;
+ } else {
+ serverUrl = serverApiUrl;
+ }
+
+ const newUrl = `${serverUrl}${pathname.replace("/server", "")}${
queryParams ? `?${queryParams}` : ""
}`;
@@ -34,7 +50,6 @@ export function middleware(req: NextRequest) {
return addSecurityHeaders(response);
}
-
// tus-js-client PATCH 요청: Location 헤더로 /server 없이 반환된 경로 직접 처리
// (예: /attach-service/api/v1/tus/upload/{id})
if (pathname.startsWith("/attach-service")) {
diff --git a/apps/as/package.json b/apps/as/package.json
index cb3887d08..075300898 100644
--- a/apps/as/package.json
+++ b/apps/as/package.json
@@ -39,7 +39,6 @@
"next": "15.5.18",
"react-hook-form": "^7.54.2",
"tus-js-client": "^4.3.1",
- "uuid": "^14.0.1",
"zod": "^3.24.2"
},
"devDependencies": {
diff --git a/apps/as/src/app/styles/globals.css b/apps/as/src/app/styles/globals.css
index e557f947b..bf1ecad16 100644
--- a/apps/as/src/app/styles/globals.css
+++ b/apps/as/src/app/styles/globals.css
@@ -6,10 +6,19 @@
@tailwind components;
@tailwind utilities;
-html,
-body {
- color: var(--foreground);
- background: var(--background);
- font-family: "Noto Sans KR", sans-serif;
- font-size: 10px;
+@layer base {
+ :root {
+ --secondary-background: rgba(0, 0, 0, 0.04);
+ }
+ [data-theme="black"] {
+ --secondary-background: rgba(255, 255, 255, 0.09);
+ }
+
+ html,
+ body {
+ color: var(--foreground);
+ background: var(--background);
+ font-family: "Noto Sans KR", sans-serif;
+ font-size: 10px;
+ }
}
diff --git a/apps/as/src/entities/adstdstr/api/adstdstr-service.ts b/apps/as/src/entities/adstdstr/api/adstdstr-service.ts
index b4bb11fae..f282ea67b 100644
--- a/apps/as/src/entities/adstdstr/api/adstdstr-service.ts
+++ b/apps/as/src/entities/adstdstr/api/adstdstr-service.ts
@@ -13,6 +13,11 @@ async function findSidoCombo(): Promise {
return response.data;
}
+async function findUserSidoCombo(): Promise {
+ const response = await client.get(`${BASE}/user/sido`);
+ return response.data;
+}
+
async function findGugunCombo(sido: string): Promise {
const response = await client.get(`${BASE}/gugun`, {
params: { sido },
@@ -20,7 +25,16 @@ async function findGugunCombo(sido: string): Promise {
+ const response = await client.get(`${BASE}/user/gugun`, {
+ params: { sido },
+ });
+ return response.data;
+}
+
export const cmCdAdstDstrService = {
findSidoCombo,
+ findUserSidoCombo,
findGugunCombo,
+ findUserGugunCombo,
} satisfies CmCdAdstDstrService;
diff --git a/apps/as/src/entities/adstdstr/api/queries.ts b/apps/as/src/entities/adstdstr/api/queries.ts
index 353e25cfe..2556ec6db 100644
--- a/apps/as/src/entities/adstdstr/api/queries.ts
+++ b/apps/as/src/entities/adstdstr/api/queries.ts
@@ -1,6 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
+import { IUserInfo } from "@/shared/store";
import { cmCdAdstDstrService } from "./adstdstr-service";
@@ -13,6 +14,16 @@ export function useFindSidoCombo() {
});
}
+export function useFindUserSidoCombo(userInfo?: IUserInfo | null) {
+ const adstDstrCdByUser = userInfo?.laeSeCd?.substring(2, 4);
+ return useQuery({
+ queryKey: [...cmCdAdstDstrBaseKey, "findUserSidoCombo", adstDstrCdByUser ?? "all"],
+ queryFn: () => cmCdAdstDstrService.findUserSidoCombo(),
+ select: (data) =>
+ adstDstrCdByUser ? data.filter((item) => item.adstDstrCd === adstDstrCdByUser) : data,
+ });
+}
+
export function useFindGugunCombo(sido: string) {
return useQuery({
queryKey: [...cmCdAdstDstrBaseKey, "findGugunCombo", sido],
@@ -20,3 +31,11 @@ export function useFindGugunCombo(sido: string) {
enabled: Boolean(sido),
});
}
+
+export function useFindUserGugunCombo(sido: string) {
+ return useQuery({
+ queryKey: [...cmCdAdstDstrBaseKey, "findUserGugunCombo", sido],
+ queryFn: () => cmCdAdstDstrService.findUserGugunCombo(sido),
+ enabled: Boolean(sido),
+ });
+}
diff --git a/apps/as/src/entities/adstdstr/index.ts b/apps/as/src/entities/adstdstr/index.ts
index da1dbd132..2fffe5a79 100644
--- a/apps/as/src/entities/adstdstr/index.ts
+++ b/apps/as/src/entities/adstdstr/index.ts
@@ -1,5 +1,10 @@
export { cmCdAdstDstrService } from "./api/adstdstr-service";
-export { useFindGugunCombo, useFindSidoCombo } from "./api/queries";
+export {
+ useFindGugunCombo,
+ useFindSidoCombo,
+ useFindUserGugunCombo,
+ useFindUserSidoCombo,
+} from "./api/queries";
export type {
CmCdAdstDstrComboResponseDto,
CmCdAdstDstrService,
diff --git a/apps/as/src/entities/adstdstr/model/adstdstr-interface.ts b/apps/as/src/entities/adstdstr/model/adstdstr-interface.ts
index 72cb673dc..1eaa14bff 100644
--- a/apps/as/src/entities/adstdstr/model/adstdstr-interface.ts
+++ b/apps/as/src/entities/adstdstr/model/adstdstr-interface.ts
@@ -7,5 +7,7 @@ export interface CmCdAdstDstrComboResponseDto {
/** Controller public 메서드명·시그니처와 동일 */
export interface CmCdAdstDstrService {
findSidoCombo(): Promise;
+ findUserSidoCombo(): Promise;
findGugunCombo(sido: string): Promise;
+ findUserGugunCombo(sido: string): Promise;
}
diff --git a/apps/as/src/entities/auth/api/auth-service.ts b/apps/as/src/entities/auth/api/auth-service.ts
index b7158bf80..d84f84776 100644
--- a/apps/as/src/entities/auth/api/auth-service.ts
+++ b/apps/as/src/entities/auth/api/auth-service.ts
@@ -1,7 +1,11 @@
import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
-import { type AuthPayload, type AuthService } from "@common/auth";
+import {
+ type AuthPayload,
+ type AuthService,
+ clearDeviceId,
+ getOrCreateDeviceId,
+} from "@common/auth";
-import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id";
import { clearRefreshTimer, setToken, tokenManager } from "../lib/token-manager";
async function login(payload: AuthPayload): Promise {
@@ -72,6 +76,7 @@ async function silentRefresh(): Promise {
}
async function logout(): Promise {
+ await fetch("/api/auth/logout", { method: "POST" });
tokenManager.clearAuthToken();
clearDeviceId();
diff --git a/apps/as/src/entities/auth/lib/device-id.ts b/apps/as/src/entities/auth/lib/device-id.ts
deleted file mode 100644
index ce173b141..000000000
--- a/apps/as/src/entities/auth/lib/device-id.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { v4 as uuidv4 } from "uuid";
-
-const DEVICE_ID_KEY = "x-device-id";
-
-export function getOrCreateDeviceId(): string {
- if (typeof window === "undefined") return "";
- const existing = localStorage.getItem(DEVICE_ID_KEY);
- if (existing) return existing;
- const id = uuidv4();
- localStorage.setItem(DEVICE_ID_KEY, id);
- return id;
-}
-
-export function clearDeviceId(): void {
- if (typeof window === "undefined") return;
- localStorage.removeItem(DEVICE_ID_KEY);
-}
diff --git a/apps/as/src/entities/bns-if/api/bns-if-service.ts b/apps/as/src/entities/bns-if/api/bns-if-service.ts
index 320a01efc..316ea041c 100644
--- a/apps/as/src/entities/bns-if/api/bns-if-service.ts
+++ b/apps/as/src/entities/bns-if/api/bns-if-service.ts
@@ -8,6 +8,8 @@ import type {
BnsListResponseWrapperDto,
BnsSearchDto,
EmCodeListResponseDto,
+ FacilityListResponseWrapperDto,
+ FacilitySearchDto,
} from "../model/bns-if-interface";
const BASE = ENDPOINT.AS_SERVICE.BNS_IF;
@@ -40,8 +42,18 @@ async function getEmCodeList(
return response.data;
}
+async function getFacilityList(
+ params: FacilitySearchDto,
+): Promise {
+ const response = await client.get(`${BASE}/facilityList`, {
+ params,
+ });
+ return response.data;
+}
+
export const bnsIfService = {
getBnsList,
getBnsDetail,
getEmCodeList,
+ getFacilityList,
} satisfies BnsIfService;
diff --git a/apps/as/src/entities/bns-if/api/queries.ts b/apps/as/src/entities/bns-if/api/queries.ts
index 7ab0b46a3..8edf93b0c 100644
--- a/apps/as/src/entities/bns-if/api/queries.ts
+++ b/apps/as/src/entities/bns-if/api/queries.ts
@@ -2,7 +2,7 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
-import type { BnsSearchDto } from "../model/bns-if-interface";
+import type { BnsSearchDto, FacilitySearchDto } from "../model/bns-if-interface";
import { bnsIfService } from "./bns-if-service";
@@ -31,3 +31,12 @@ export function useEmCodeList(dpmCd?: string, lclgvrnmntCd?: string) {
queryFn: () => bnsIfService.getEmCodeList(dpmCd, lclgvrnmntCd),
});
}
+
+export function useFacilityList(params: FacilitySearchDto, options?: { enabled?: boolean }) {
+ return useQuery({
+ queryKey: [...bnsIfBaseKey, "getFacilityList", params],
+ queryFn: () => bnsIfService.getFacilityList(params),
+ enabled: options?.enabled,
+ placeholderData: keepPreviousData,
+ });
+}
diff --git a/apps/as/src/entities/bns-if/index.ts b/apps/as/src/entities/bns-if/index.ts
index 430f4a611..d01b27189 100644
--- a/apps/as/src/entities/bns-if/index.ts
+++ b/apps/as/src/entities/bns-if/index.ts
@@ -1,5 +1,5 @@
export { bnsIfService } from "./api/bns-if-service";
-export { useBnsDetail, useBnsList, useEmCodeList } from "./api/queries";
+export { useBnsDetail, useBnsList, useEmCodeList, useFacilityList } from "./api/queries";
export { buildBnsSearchDto } from "./lib/build-bns-search-dto";
export type {
BnsDetailResponseDto,
@@ -10,4 +10,7 @@ export type {
BnsSearchDto,
EmCodeItemDto,
EmCodeListResponseDto,
+ FacilityListResponseWrapperDto,
+ FacilityResponseDto,
+ FacilitySearchDto,
} from "./model/bns-if-interface";
diff --git a/apps/as/src/entities/bns-if/model/bns-if-interface.ts b/apps/as/src/entities/bns-if/model/bns-if-interface.ts
index e13ab8500..833accca8 100644
--- a/apps/as/src/entities/bns-if/model/bns-if-interface.ts
+++ b/apps/as/src/entities/bns-if/model/bns-if-interface.ts
@@ -117,9 +117,89 @@ export interface EmCodeListResponseDto {
ddtlList: EmCodeItemDto[];
}
+/** 백엔드 FacilitySearchDto */
+export interface FacilitySearchDto {
+ sido?: string;
+ gugun?: string;
+}
+
+/** 백엔드 FacilityResponseDto */
+export interface FacilityResponseDto {
+ bscFctCd?: string;
+ bscFctNm?: string;
+ addr?: string;
+ fctCpc?: string;
+ fctCpc1?: string;
+ fctCpc2?: string;
+ fctCpc3?: string;
+ totInflAmntAve?: unknown;
+ pInflAmntAve?: unknown;
+ oInflAmntAve?: unknown;
+ aInflAmntAve?: unknown;
+ totTputAve?: unknown;
+ pTputAve?: unknown;
+ oTputAve?: unknown;
+ aTputAve?: unknown;
+ inflAssctBfrWqltBod?: unknown;
+ efflWqltBod?: unknown;
+ inflAssctBfrWqltCod?: unknown;
+ efflWqltCod?: unknown;
+ inflAssctBfrWqltSs?: unknown;
+ efflWqltSs?: unknown;
+ inflAssctBfrWqltTn?: unknown;
+ efflWqltTn?: unknown;
+ inflAssctBfrWqltTp?: unknown;
+ efflWqltTp?: unknown;
+ inflAssctBfrWqltClfgrpcnt?: unknown;
+ efflWqltClfgrpcnt?: unknown;
+ tmEfficiency?: unknown;
+ pcLoad?: unknown;
+ trtFrfHanNm?: string;
+ applctNtcheNm?: string;
+ excretionsTot?: unknown;
+ stkrsngWstwtrTot?: unknown;
+ lchtTot?: unknown;
+ etcTot?: unknown;
+ tel?: string;
+ rnDt?: string;
+ compDt?: string;
+ tot?: unknown;
+ oprClsNm?: string;
+ trstOprEtpNm?: string;
+ yrstpTrstCst?: string;
+ trstOprDt?: string;
+ trstAfr?: string;
+ dsfctNm?: string;
+ rvNm?: string;
+ mnstr?: string;
+ wbsnNm?: string;
+ efflWqltStndrdNm?: string;
+ elcpwAmnt?: unknown;
+ toeRt?: unknown;
+ toeSum?: unknown;
+ co2Emissions?: unknown;
+ toeQnty?: unknown;
+ toeRt2?: unknown;
+ useQnty?: unknown;
+ eqpPrmsDt?: string;
+ mdmbsn?: string;
+ lgbsn?: string;
+ lgbsnBod?: string;
+ cntrTrtFctNm?: string;
+ totWtrQrm?: unknown;
+ inflAssctBfrWqltToc?: unknown;
+ efflWqltToc?: unknown;
+}
+
+/** 백엔드 FacilityListResponseWrapperDto */
+export interface FacilityListResponseWrapperDto {
+ fctList: FacilityResponseDto[];
+}
+
/** Controller public 메서드명·시그니처와 동일 */
export interface BnsIfService {
getBnsList(params: BnsSearchDto): Promise;
getBnsDetail(gbn: string, yr: string, seqNo: string): Promise;
getEmCodeList(dpmCd?: string, lclgvrnmntCd?: string): Promise;
+ getFacilityList(params: FacilitySearchDto): Promise;
}
diff --git a/apps/as/src/entities/dc/api/dc-service.ts b/apps/as/src/entities/dc/api/dc-service.ts
new file mode 100644
index 000000000..650e81b23
--- /dev/null
+++ b/apps/as/src/entities/dc/api/dc-service.ts
@@ -0,0 +1,72 @@
+import { client } from "@/shared/api";
+import { ENDPOINT } from "@/shared/config";
+
+import type {
+ ClsfSystmParamsDto,
+ ClsfSystmResponseDto,
+ DcService,
+ FileDownloadParamsDto,
+ FileParamsDto,
+ LvNowDmgMtncGnsvyResponseDto,
+ LvNowDmgMtncGnsvySearchDto,
+ NowDmgMtncGnsvyResponseDto,
+ NowDmgMtncGnsvySearchDto,
+} from "../model/dc-interface";
+
+async function getNowDmgMtncGnsvy(
+ params: NowDmgMtncGnsvySearchDto,
+): Promise {
+ const response = await client.get(
+ `${ENDPOINT.DC_SERVICE}/nowDmgMtncGnsvy`,
+ { params },
+ );
+ return response.data;
+}
+
+async function getLvNowDmgMtncGnsvy(
+ params: LvNowDmgMtncGnsvySearchDto,
+): Promise {
+ const response = await client.get(
+ `${ENDPOINT.DC_SERVICE}/lvNowDmgMtncGnsvy`,
+ { params },
+ );
+ return response.data;
+}
+
+async function getClsfSystm(params: ClsfSystmParamsDto): Promise {
+ const response = await client.get(`${ENDPOINT.DC_SERVICE}/clsf`, {
+ params,
+ });
+ return response.data;
+}
+
+async function getFile(params: FileParamsDto): Promise {
+ const response = await client.get(`${ENDPOINT.DC_SERVICE}/file`, {
+ params,
+ });
+ return response.data;
+}
+
+async function getFilePrcs(fileSn: string): Promise {
+ const response = await client.get(`${ENDPOINT.DC_SERVICE}/fileprcs`, {
+ params: { fileSn },
+ });
+ return response.data;
+}
+
+async function fileDownload(params: FileDownloadParamsDto): Promise {
+ const response = await client.get(`${ENDPOINT.DC_SERVICE}/fileDownload`, {
+ params,
+ responseType: "blob",
+ });
+ return response.data;
+}
+
+export const dcService = {
+ getNowDmgMtncGnsvy,
+ getLvNowDmgMtncGnsvy,
+ getClsfSystm,
+ getFile,
+ getFilePrcs,
+ fileDownload,
+} satisfies DcService;
diff --git a/apps/as/src/entities/dc/api/queries.ts b/apps/as/src/entities/dc/api/queries.ts
new file mode 100644
index 000000000..043006305
--- /dev/null
+++ b/apps/as/src/entities/dc/api/queries.ts
@@ -0,0 +1,65 @@
+import { useQuery } from "@tanstack/react-query";
+
+import { ENDPOINT } from "@/shared/config";
+
+import type {
+ ClsfSystmParamsDto,
+ FileParamsDto,
+ LvNowDmgMtncGnsvySearchDto,
+ NowDmgMtncGnsvySearchDto,
+} from "../model/dc-interface";
+
+import { dcService } from "./dc-service";
+
+export const baseKey = [ENDPOINT.DC_SERVICE] as const;
+
+export function useNowDmgMtncGnsvy(params: NowDmgMtncGnsvySearchDto, enabled?: boolean) {
+ return useQuery({
+ queryKey: [...baseKey, "nowDmgMtncGnsvy", params],
+ queryFn: () => dcService.getNowDmgMtncGnsvy(params),
+ enabled: enabled ?? Boolean(params.swtrmtFcltCd && params.ctpvNm),
+ });
+}
+
+function isLvNowDmgMtncGnsvyEnabled(params: LvNowDmgMtncGnsvySearchDto): boolean {
+ if (!params.level || !params.ctpvNm) return false;
+ if (params.level === "1") return true;
+ if (params.level === "2") return Boolean(params.sggNm);
+ if (params.level === "3") return Boolean(params.sggNm && params.gnsvyYr);
+ return false;
+}
+
+export function useLvNowDmgMtncGnsvy(params: LvNowDmgMtncGnsvySearchDto) {
+ return useQuery({
+ queryKey: [...baseKey, "lvNowDmgMtncGnsvy", params],
+ queryFn: () => dcService.getLvNowDmgMtncGnsvy(params),
+ enabled: isLvNowDmgMtncGnsvyEnabled(params),
+ });
+}
+
+export function useClsfSystm(params: ClsfSystmParamsDto) {
+ return useQuery({
+ queryKey: [...baseKey, "clsfSystm", params],
+ queryFn: () => dcService.getClsfSystm(params),
+ // TODO: T4 — upClsfSystmSn 점진 로드 여부 확정 후 enabled/파라미터 조정
+ enabled: Boolean(params.clsfSystmNm),
+ });
+}
+
+export function useFile(params: FileParamsDto) {
+ return useQuery({
+ queryKey: [...baseKey, "file", params],
+ queryFn: () => dcService.getFile(params),
+ // TODO: T2 — getFile Response DTO 확정 후 타입 교체
+ enabled: Boolean(params.clsfSystmSn),
+ });
+}
+
+export function useFilePrcs(fileSn: string) {
+ return useQuery({
+ queryKey: [...baseKey, "filePrcs", fileSn],
+ queryFn: () => dcService.getFilePrcs(fileSn),
+ // TODO: T2 — getFilePrcs Response DTO 확정 후 타입 교체
+ enabled: Boolean(fileSn),
+ });
+}
diff --git a/apps/as/src/entities/dc/index.ts b/apps/as/src/entities/dc/index.ts
new file mode 100644
index 000000000..b9df34de5
--- /dev/null
+++ b/apps/as/src/entities/dc/index.ts
@@ -0,0 +1,19 @@
+export { dcService } from "./api/dc-service";
+export {
+ baseKey as nowDmgMtncGnsvyBaseKey,
+ useClsfSystm,
+ useFile,
+ useFilePrcs,
+ useLvNowDmgMtncGnsvy,
+ useNowDmgMtncGnsvy,
+} from "./api/queries";
+export type {
+ ClsfSystmParamsDto,
+ ClsfSystmResponseDto,
+ FileDownloadParamsDto,
+ FileParamsDto,
+ LvNowDmgMtncGnsvyResponseDto,
+ LvNowDmgMtncGnsvySearchDto,
+ NowDmgMtncGnsvyResponseDto,
+ NowDmgMtncGnsvySearchDto,
+} from "./model/dc-interface";
diff --git a/apps/as/src/entities/dc/model/dc-interface.ts b/apps/as/src/entities/dc/model/dc-interface.ts
new file mode 100644
index 000000000..59f0a7be2
--- /dev/null
+++ b/apps/as/src/entities/dc/model/dc-interface.ts
@@ -0,0 +1,115 @@
+export interface NowDmgMtncGnsvySearchDto {
+ swtrmtFcltCd?: string;
+ ctpvNm?: string;
+ sggNm?: string;
+ targetYear?: string;
+}
+
+export interface LvNowDmgMtncGnsvySearchDto {
+ level?: string;
+ ctpvNm?: string;
+ sggNm?: string;
+ gnsvyYr?: string;
+}
+
+export interface ClsfSystmParamsDto {
+ clsfSystmNm?: string;
+ upClsfSystmSn?: string;
+}
+
+export interface FileParamsDto {
+ sort?: string;
+ clsfSystmSn?: string;
+ vetPrcsYn?: string;
+}
+
+export interface FileDownloadParamsDto {
+ filePath?: string;
+ fileName?: string;
+}
+
+type NullableNumber = number | null;
+type NullableString = string | null;
+
+/** 현재·단계별(Dt/Dc) 공통 지표 필드 */
+interface NowDmgMtncGnsvyMetricFields {
+ planYr?: NullableString;
+ swtrmtPpltnCnt?: NullableNumber;
+ natrSwtrmtPpltnCnt?: NullableNumber;
+ scilSwtrmtPpltnCnt?: NullableNumber;
+ swtrmtZoneArea?: NullableNumber;
+ wtrUsqtyOrgnUnit?: NullableNumber;
+ drtwSwtcRt?: NullableNumber;
+ flctnLodRt?: NullableNumber;
+ drtwOrgnUnit?: NullableNumber;
+ planSwageQntSum?: NullableNumber;
+ lvngDrtwSum?: NullableNumber;
+ ugwtSum?: NullableNumber;
+ fctryWwtSum?: NullableNumber;
+ toursmDrtwSum?: NullableNumber;
+ mtbsSum?: NullableNumber;
+ linkPrcsCntSum?: NullableNumber;
+ etcDrtwSum?: NullableNumber;
+ ugwtUseSum?: NullableNumber;
+ fcltCpctSum?: NullableNumber;
+ exclsMethExpln?: NullableString;
+ brcPplSum?: NullableNumber;
+ icptgPplSum?: NullableNumber;
+ exclnPplSum?: NullableNumber;
+ stofcbrSum?: NullableNumber;
+ pmstnSum?: NullableNumber;
+ simplPubSwtrmtFcltSum?: NullableNumber;
+ swageImpdFcltSum?: NullableNumber;
+ mltvFclSum?: NullableNumber;
+}
+
+type StageSuffix = "Dt1" | "Dt2" | "Dt3" | "Dt4" | "Dc1" | "Dc2" | "Dc3" | "Dc4";
+
+type StagedMetricFields = {
+ [K in keyof NowDmgMtncGnsvyMetricFields as `${K & string}${StageSuffix}`]?: NowDmgMtncGnsvyMetricFields[K];
+};
+
+export interface NowDmgMtncGnsvyResponseDto
+ extends NowDmgMtncGnsvyMetricFields, StagedMetricFields {
+ swtrmtFcltCd?: NullableString;
+ swtrmtFcltNm?: NullableString;
+ gnsvyYr?: NullableString;
+ ctpvNm?: NullableString;
+ sggNm?: NullableString;
+}
+
+/**
+ * lvNowDmgMtncGnsvy 계층 노드.
+ * - level 1: ctpvNm + children(구군)
+ * - level 2: sggNm + children(연도)
+ * - level 3: gnsvyYr + children(처리시설)
+ * - 시설 리프: swtrmtFcltCd / swtrmtFcltNm (level 없음)
+ */
+export interface LvNowDmgMtncGnsvyResponseDto {
+ level?: number;
+ ctpvNm?: string;
+ sggNm?: string;
+ gnsvyYr?: string;
+ swtrmtFcltCd?: string;
+ swtrmtFcltNm?: string;
+ children?: LvNowDmgMtncGnsvyResponseDto[];
+}
+
+export interface ClsfSystmResponseDto {
+ clsfSystmSn: string;
+ upClsfSystmSn: string;
+ clsfSystmNm: string;
+ sortSeq: string;
+ droppAble: boolean;
+ path: string | null;
+ children: ClsfSystmResponseDto[];
+}
+
+export interface DcService {
+ getNowDmgMtncGnsvy(params: NowDmgMtncGnsvySearchDto): Promise;
+ getLvNowDmgMtncGnsvy(params: LvNowDmgMtncGnsvySearchDto): Promise;
+ getClsfSystm(params: ClsfSystmParamsDto): Promise;
+ getFile(params: FileParamsDto): Promise;
+ getFilePrcs(fileSn: string): Promise;
+ fileDownload(params: FileDownloadParamsDto): Promise;
+}
diff --git a/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts b/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts
index 6ead54c13..4bbd7ae24 100644
--- a/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts
+++ b/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts
@@ -39,11 +39,48 @@ export interface AdstZoneResponseDto {
export const INSTL_SNCN_REGISTER_SELECTION_KEY = "instlSncnRegisterSelection";
+/** 백엔드 InstlSncnMngSearchDto + Pageable */
export interface InstlSncnMngSearchParams extends PageableParams {
- bizNm?: string;
+ /** 설치인가문서번호 */
+ instlSncnDocNo?: string;
+ /** 행정구역코드 */
+ admdstCd?: string;
+ /** 환경청구분공통코드 */
+ envoSeComCd?: string;
+ /** 업무구분공통코드 */
taskSeComCd?: string;
- sidoComCd?: string;
- sggComCd?: string;
+ /** 예산사업코드 */
+ bgtBizCd?: string;
+ /** 예산신청연도 */
+ bgtAplyYr?: string;
+ /** 사업명 */
+ bizNm?: string;
+ /** 사업시작연도 */
+ bizBgngYr?: string;
+ /** 사업종료연도 */
+ bizEndYr?: string;
+ /** 사업내용 */
+ bizCn?: string;
+ /** 신청자명 */
+ aplcntNm?: string;
+ /** 신청자휴대전화번호 */
+ aplcntMblTelno?: string;
+ /** 신청자소속명 */
+ aplcntOgdpNm?: string;
+ /** 신청자이메일주소 */
+ aplcntEmlAddr?: string;
+ /** 설계자명 */
+ dsgnrNm?: string;
+ /** 설계자전화번호 */
+ dsgnrTelno?: string;
+ /** 설계자소속명 */
+ dsgnrOgdpNm?: string;
+ /** 설계자이메일주소 */
+ dsgnrEmlAddr?: string;
+ /** 첨부파일그룹아이디 */
+ atchFileGroupId?: string;
+ /** 진행상태공통코드 */
+ prgrsSttsComCd?: string;
}
/** 설치인가관리 저장 요청 DTO */
diff --git a/apps/as/src/entities/sewer-bsc-plan/api/queries.ts b/apps/as/src/entities/sewer-bsc-plan/api/queries.ts
new file mode 100644
index 000000000..063e1bd00
--- /dev/null
+++ b/apps/as/src/entities/sewer-bsc-plan/api/queries.ts
@@ -0,0 +1,138 @@
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+
+import { ENDPOINT } from "@/shared/config";
+
+import type {
+ SewerBscPlanApplySaveRequestDto,
+ SewerBscPlanSaveRequestDto,
+ SewerBscPlanSearchRequestDto,
+ SewerBscPlanUpdateRequestDto,
+} from "../model/sewer-bsc-plan-interface";
+
+import { sewerBscPlanService } from "./sewer-bsc-plan-service";
+
+export const sewerBscPlanBaseKey = [ENDPOINT.AS_SERVICE.SEWER_BSC_PLAN] as const;
+
+export function useSewerBscPlanPage(params: SewerBscPlanSearchRequestDto) {
+ return useQuery({
+ queryKey: [...sewerBscPlanBaseKey, "findPage", params],
+ queryFn: () => sewerBscPlanService.findPage(params),
+ });
+}
+
+export function useSewerBscPlan(sewerBscPlanDocNo: string | null | undefined) {
+ return useQuery({
+ queryKey: [...sewerBscPlanBaseKey, "findById", sewerBscPlanDocNo],
+ queryFn: () => sewerBscPlanService.findById(sewerBscPlanDocNo!),
+ enabled: Boolean(sewerBscPlanDocNo),
+ });
+}
+
+export function useSewerBscPlanDetails(
+ sewerBscPlanDocNo: string | null | undefined,
+ chgCycl: number | null | undefined,
+) {
+ return useQuery({
+ queryKey: [...sewerBscPlanBaseKey, "findDetails", sewerBscPlanDocNo, chgCycl],
+ queryFn: () => sewerBscPlanService.findDetails(sewerBscPlanDocNo!, chgCycl!),
+ enabled: Boolean(sewerBscPlanDocNo) && chgCycl != null,
+ });
+}
+
+export function useSaveSewerBscPlan() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (body: SewerBscPlanSaveRequestDto) => sewerBscPlanService.save(body),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: sewerBscPlanBaseKey });
+ },
+ });
+}
+
+export function useUpdateSewerBscPlan() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({
+ sewerBscPlanDocNo,
+ body,
+ }: {
+ sewerBscPlanDocNo: string;
+ body: SewerBscPlanUpdateRequestDto;
+ }) => sewerBscPlanService.update(sewerBscPlanDocNo, body),
+ onSuccess: (_data, { sewerBscPlanDocNo }) => {
+ queryClient.invalidateQueries({ queryKey: sewerBscPlanBaseKey });
+ queryClient.invalidateQueries({
+ queryKey: [...sewerBscPlanBaseKey, "findById", sewerBscPlanDocNo],
+ });
+ },
+ });
+}
+
+export function useDeleteSewerBscPlan() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (sewerBscPlanDocNo: string) => sewerBscPlanService.delete(sewerBscPlanDocNo),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: sewerBscPlanBaseKey });
+ },
+ });
+}
+
+export function useSaveApplySewerBscPlan() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (body: SewerBscPlanApplySaveRequestDto) => sewerBscPlanService.saveApply(body),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: sewerBscPlanBaseKey });
+ },
+ });
+}
+
+export function useSubmitSupplementSewerBscPlan() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, body }: { id: string; body: SewerBscPlanApplySaveRequestDto }) =>
+ sewerBscPlanService.submitSupplement(id, body),
+ onSuccess: (_data, { id }) => {
+ queryClient.invalidateQueries({ queryKey: sewerBscPlanBaseKey });
+ queryClient.invalidateQueries({
+ queryKey: [...sewerBscPlanBaseKey, "findById", id],
+ });
+ },
+ });
+}
+
+export function useSubmitReqSupplementSewerBscPlan() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({
+ id,
+ chgCycl,
+ body,
+ }: {
+ id: string;
+ chgCycl: number;
+ body: SewerBscPlanApplySaveRequestDto;
+ }) => sewerBscPlanService.submitReqSupplement(id, chgCycl, body),
+ onSuccess: (_data, { id }) => {
+ queryClient.invalidateQueries({ queryKey: sewerBscPlanBaseKey });
+ queryClient.invalidateQueries({
+ queryKey: [...sewerBscPlanBaseKey, "findById", id],
+ });
+ },
+ });
+}
+
+export function useTechReviewSewerBscPlan() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, body }: { id: string; body: SewerBscPlanApplySaveRequestDto }) =>
+ sewerBscPlanService.techReview(id, body),
+ onSuccess: (_data, { id }) => {
+ queryClient.invalidateQueries({ queryKey: sewerBscPlanBaseKey });
+ queryClient.invalidateQueries({
+ queryKey: [...sewerBscPlanBaseKey, "findById", id],
+ });
+ },
+ });
+}
diff --git a/apps/as/src/entities/sewer-bsc-plan/api/sewer-bsc-plan-service.ts b/apps/as/src/entities/sewer-bsc-plan/api/sewer-bsc-plan-service.ts
new file mode 100644
index 000000000..013b1d384
--- /dev/null
+++ b/apps/as/src/entities/sewer-bsc-plan/api/sewer-bsc-plan-service.ts
@@ -0,0 +1,98 @@
+import { client } from "@/shared/api";
+import { ENDPOINT } from "@/shared/config";
+import type { PageResponse } from "@/shared/model";
+
+import type {
+ SewerBscPlanApplySaveRequestDto,
+ SewerBscPlanApplySaveResponseDto,
+ SewerBscPlanDetResponseDto,
+ SewerBscPlanResponseDto,
+ SewerBscPlanSaveRequestDto,
+ SewerBscPlanSearchRequestDto,
+ SewerBscPlanService,
+ SewerBscPlanUpdateRequestDto,
+} from "../model/sewer-bsc-plan-interface";
+
+const BASE = ENDPOINT.AS_SERVICE.SEWER_BSC_PLAN;
+
+async function findPage(
+ params: SewerBscPlanSearchRequestDto,
+): Promise> {
+ const response = await client.get>(BASE, {
+ params,
+ });
+ return response.data;
+}
+
+async function findById(sewerBscPlanDocNo: string): Promise {
+ const response = await client.get(`${BASE}/${sewerBscPlanDocNo}`);
+ return response.data;
+}
+
+async function save(body: SewerBscPlanSaveRequestDto): Promise {
+ const response = await client.post(BASE, body);
+ return response.data;
+}
+
+async function update(
+ sewerBscPlanDocNo: string,
+ body: SewerBscPlanUpdateRequestDto,
+): Promise {
+ const response = await client.put(`${BASE}/${sewerBscPlanDocNo}`, body);
+ return response.data;
+}
+
+async function deleteFn(sewerBscPlanDocNo: string): Promise {
+ await client.delete(`${BASE}/${sewerBscPlanDocNo}`);
+}
+
+async function saveApply(
+ body: SewerBscPlanApplySaveRequestDto,
+): Promise {
+ const response = await client.post(`${BASE}/save-apply`, body);
+ return response.data;
+}
+
+async function findDetails(
+ sewerBscPlanDocNo: string,
+ chgCycl: number,
+): Promise {
+ const response = await client.get(
+ `${BASE}/${sewerBscPlanDocNo}/dtl/${chgCycl}`,
+ );
+ return response.data;
+}
+
+async function submitSupplement(
+ id: string,
+ body: SewerBscPlanApplySaveRequestDto,
+): Promise {
+ const response = await client.post(`${BASE}/${id}/submitsupplement`, body);
+ return response.data;
+}
+
+async function submitReqSupplement(
+ id: string,
+ chgCycl: number,
+ body: SewerBscPlanApplySaveRequestDto,
+): Promise {
+ const response = await client.post(`${BASE}/${id}/${chgCycl}/reqsupplement`, body);
+ return response.data;
+}
+
+async function techReview(id: string, body: SewerBscPlanApplySaveRequestDto): Promise {
+ await client.post(`${BASE}/${id}/tech-review`, body);
+}
+
+export const sewerBscPlanService = {
+ findPage,
+ findById,
+ save,
+ update,
+ delete: deleteFn,
+ saveApply,
+ findDetails,
+ submitSupplement,
+ submitReqSupplement,
+ techReview,
+} satisfies SewerBscPlanService;
diff --git a/apps/as/src/entities/sewer-bsc-plan/index.ts b/apps/as/src/entities/sewer-bsc-plan/index.ts
new file mode 100644
index 000000000..eecf354cd
--- /dev/null
+++ b/apps/as/src/entities/sewer-bsc-plan/index.ts
@@ -0,0 +1,47 @@
+export { sewerBscPlanBaseKey } from "./api/queries";
+export {
+ useDeleteSewerBscPlan,
+ useSaveApplySewerBscPlan,
+ useSaveSewerBscPlan,
+ useSewerBscPlan,
+ useSewerBscPlanDetails,
+ useSewerBscPlanPage,
+ useSubmitReqSupplementSewerBscPlan,
+ useSubmitSupplementSewerBscPlan,
+ useTechReviewSewerBscPlan,
+ useUpdateSewerBscPlan,
+} from "./api/queries";
+export { sewerBscPlanService } from "./api/sewer-bsc-plan-service";
+export type {
+ AtflMngClientResponseDto,
+ CklstResultItem,
+ CklstRvwRsltTreeDto,
+ EsntlAtchFileItem,
+ EsntlAtchFileMngTreeResponseDto,
+ SewerBscPlanApplySaveRequestDto,
+ SewerBscPlanApplySaveResponseDto,
+ SewerBscPlanBizCstItem,
+ SewerBscPlanBizCstResponseDto,
+ SewerBscPlanDetResponseDto,
+ SewerBscPlanDtlResponseDto,
+ SewerBscPlanDtlSaveRequestDto,
+ SewerBscPlanFnrsCstItem,
+ SewerBscPlanFnrsCstResponseDto,
+ SewerBscPlanOtlnItem,
+ SewerBscPlanOtlnResponseDto,
+ SewerBscPlanResponseDto,
+ SewerBscPlanRvwOpnnItem,
+ SewerBscPlanRvwOpnnResponseDto,
+ SewerBscPlanSaveRequestDto,
+ SewerBscPlanSearchRequestDto,
+ SewerBscPlanService,
+ SewerBscPlanSplmntDmndItem,
+ SewerBscPlanSplmntDmndResponseDto,
+ SewerBscPlanTmplntDtlItem,
+ SewerBscPlanTmplntDtlResponseDto,
+ SewerBscPlanTmplntItem,
+ SewerBscPlanTmplntResponseDto,
+ SewerBscPlanTmplntTmprStrgItem,
+ SewerBscPlanTmplntTmprStrgResponseDto,
+ SewerBscPlanUpdateRequestDto,
+} from "./model/sewer-bsc-plan-interface";
diff --git a/apps/as/src/entities/sewer-bsc-plan/model/sewer-bsc-plan-interface.ts b/apps/as/src/entities/sewer-bsc-plan/model/sewer-bsc-plan-interface.ts
new file mode 100644
index 000000000..cc5f533d8
--- /dev/null
+++ b/apps/as/src/entities/sewer-bsc-plan/model/sewer-bsc-plan-interface.ts
@@ -0,0 +1,351 @@
+import type {
+ AtflMngClientResponseDto,
+ CklstResultSaveBase,
+ CklstRvwRsltTreeDto,
+ EsntlAtchFileMngTreeResponseDto,
+ EsntlAtchFileSaveBase,
+ PageableParams,
+ PageResponse,
+ PbadmsApplySaveHeader,
+ RvwOpnnResponseExtension,
+ RvwOpnnSaveBase,
+ SplmntDmndResponseExtension,
+ SplmntDmndSaveBase,
+} from "@/shared/model";
+
+export type {
+ AtflMngClientResponseDto,
+ CklstRvwRsltTreeDto,
+ EsntlAtchFileMngTreeResponseDto,
+} from "@/shared/model";
+
+/** 백엔드 SewerBscPlanSaveRequestDto */
+export interface SewerBscPlanSaveRequestDto {
+ sewerBscPlanDocNo?: string;
+ admdstCd?: string;
+ envoSeComCd?: string;
+ sewerBscPlanSeComCd?: string;
+ sewerBscPlanNm?: string;
+ taskSeComCd?: string;
+ prgrsSttsComCd?: string;
+}
+
+/** 백엔드 SewerBscPlanUpdateRequestDto */
+export interface SewerBscPlanUpdateRequestDto {
+ admdstCd?: string;
+ envoSeComCd?: string;
+ sewerBscPlanSeComCd?: string;
+ sewerBscPlanNm?: string;
+ taskSeComCd?: string;
+ prgrsSttsComCd?: string;
+}
+
+/** 백엔드 SewerBscPlanSearchRequestDto + Pageable */
+export interface SewerBscPlanSearchRequestDto extends PageableParams {
+ sewerBscPlanDocNo?: string;
+ admdstCd?: string;
+ envoSeComCd?: string;
+ sewerBscPlanSeComCd?: string;
+ sewerBscPlanNm?: string;
+ taskSeComCd?: string;
+}
+
+/** 백엔드 SewerBscPlanResponseDto */
+export interface SewerBscPlanResponseDto {
+ sewerBscPlanDocNo: string;
+ admdstCd?: string;
+ envoSeComCd?: string;
+ envoSeCdNm?: string;
+ sewerBscPlanSeComCd?: string;
+ sewerBscPlanSeCdNm?: string;
+ sewerBscPlanNm?: string;
+ taskSeComCd?: string;
+ taskSeCdNm?: string;
+ prgrsSttsComCd?: string;
+ prgrsSttsCdNm?: string;
+ sido?: string;
+ gugun?: string;
+ chgCycl?: number;
+ regDt?: string;
+ rgtr?: string;
+ rgtrNm?: string;
+ mdfcnDt?: string;
+}
+
+/** 백엔드 SewerBscPlanDtlSaveRequestDto */
+export interface SewerBscPlanDtlSaveRequestDto {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+ aplcntNm?: string;
+ aplcntMblTelno?: string;
+ aplcntOgdpNm?: string;
+ aplcntEmlAddr?: string;
+ dsgnrNm?: string;
+ dsgnrTelno?: string;
+ dsgnrOgdpNm?: string;
+ dsgnrEmlAddr?: string;
+ planYr?: string;
+ crtrYr?: string;
+ bizOtlnCn?: string;
+ frlvlGoalYr?: string;
+ sclvlGoalYr?: string;
+ thlvlGoalYr?: string;
+ folvlGoalYr?: string;
+ atchFileGroupId?: string;
+}
+
+/** 백엔드 SewerBscPlanDtlResponseDto */
+export interface SewerBscPlanDtlResponseDto {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+ aplcntNm?: string;
+ aplcntMblTelno?: string;
+ aplcntOgdpNm?: string;
+ aplcntEmlAddr?: string;
+ dsgnrNm?: string;
+ dsgnrTelno?: string;
+ dsgnrOgdpNm?: string;
+ dsgnrEmlAddr?: string;
+ planYr?: string;
+ crtrYr?: string;
+ bizOtlnCn?: string;
+ frlvlGoalYr?: string;
+ sclvlGoalYr?: string;
+ thlvlGoalYr?: string;
+ folvlGoalYr?: string;
+ atchFileGroupId?: string;
+}
+
+/** 백엔드 SewerBscPlanOtlnResponseDto */
+export interface SewerBscPlanOtlnResponseDto {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+ planStpNo?: number;
+ sewerPlanPpltnCnt?: number;
+ sewerPrcsPpltnCnt?: number;
+ sewerPsplyRt?: number;
+}
+
+/** 백엔드 SewerBscPlanFnrsCstResponseDto */
+export interface SewerBscPlanFnrsCstResponseDto {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+ planStpNo?: number;
+ gvexpAmt?: number;
+ lcexpAmt?: number;
+ orgprBdchgAmt?: number;
+ etcAmt?: number;
+ sumAmt?: number;
+}
+
+/** 백엔드 SewerBscPlanBizCstResponseDto */
+export interface SewerBscPlanBizCstResponseDto {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+ planStpNo?: number;
+ acntgSeCd?: string;
+ cstrnAmt?: number;
+}
+
+/** 백엔드 SewerBscPlanSplmntDmndResponseDto */
+export interface SewerBscPlanSplmntDmndResponseDto
+ extends SplmntDmndSaveBase, SplmntDmndResponseExtension {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+}
+
+/** 백엔드 SewerBscPlanRvwOpnnResponseDto */
+export interface SewerBscPlanRvwOpnnResponseDto extends RvwOpnnSaveBase, RvwOpnnResponseExtension {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+}
+
+/** 백엔드 SewerBscPlanTmplntDtlResponseDto */
+export interface SewerBscPlanTmplntDtlResponseDto {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+ planStpNo?: number;
+ swtrmtFcltCd?: string;
+ prcsZoneArea?: number;
+ swtrmtPpltnCnt?: number;
+ planSwqty?: number;
+ fcltCpct?: number;
+ sprsysDrtwPplLen?: number;
+ sprsysExlnPplLen?: number;
+ cbsysPplLen?: number;
+ icptgPplLen?: number;
+ drgCnt?: number;
+ drtwRlyPumpCnt?: number;
+ drtwPumpCnt?: number;
+ exlnPumpCnt?: number;
+}
+
+/** 백엔드 SewerBscPlanTmplntResponseDto */
+export interface SewerBscPlanTmplntResponseDto {
+ sewerBscPlanDocNo?: string;
+ chgCycl?: number;
+ swtrmtFcltCd?: string;
+ swtrmtFcltNm?: string;
+ atchFileGroupId?: string;
+ atchFileInfo?: AtflMngClientResponseDto;
+ sewerBscPlanTmplntDtlList?: SewerBscPlanTmplntDtlResponseDto[];
+}
+
+/** 백엔드 SewerBscPlanTmplntTmprStrgResponseDto */
+export interface SewerBscPlanTmplntTmprStrgResponseDto {
+ swtrmtFcltCd?: string;
+ rgtr?: string;
+ dtStrgDt?: string;
+ prcsZoneArea?: number;
+ swtrmtPpltnCnt?: number;
+ planSwqty?: number;
+ fcltCpct?: number;
+ sprsysDrtwPplLen?: number;
+ sprsysExlnPplLen?: number;
+ cbsysPplLen?: number;
+ icptgPplLen?: number;
+ drgCnt?: number;
+ drtwRlyPumpCnt?: number;
+ drtwPumpCnt?: number;
+ exlnPumpCnt?: number;
+}
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanOtlnItem */
+export interface SewerBscPlanOtlnItem {
+ planStpNo?: number;
+ sewerPlanPpltnCnt?: number;
+ sewerPrcsPpltnCnt?: number;
+ sewerPsplyRt?: number;
+}
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.CklstResultItem */
+export interface CklstResultItem extends CklstResultSaveBase {
+ selected?: string;
+}
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanFnrsCstItem */
+export interface SewerBscPlanFnrsCstItem {
+ planStpNo?: number;
+ gvexpAmt?: number;
+ lcexpAmt?: number;
+ orgprBdchgAmt?: number;
+ etcAmt?: number;
+ sumAmt?: number;
+}
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanSplmntDmndItem */
+export type SewerBscPlanSplmntDmndItem = SplmntDmndSaveBase;
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanRvwOpnnItem */
+export type SewerBscPlanRvwOpnnItem = RvwOpnnSaveBase;
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.EsntlAtchFileItem */
+export type EsntlAtchFileItem = EsntlAtchFileSaveBase;
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanBizCstItem */
+export interface SewerBscPlanBizCstItem {
+ planStpNo?: number;
+ acntgSeCd?: string;
+ cstrnAmt?: number;
+}
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntDtlItem */
+export interface SewerBscPlanTmplntDtlItem {
+ planStpNo?: number;
+ swtrmtFcltCd?: string;
+ prcsZoneArea?: number;
+ swtrmtPpltnCnt?: number;
+ planSwqty?: number;
+ fcltCpct?: number;
+ sprsysDrtwPplLen?: number;
+ sprsysExlnPplLen?: number;
+ cbsysPplLen?: number;
+ icptgPplLen?: number;
+ drgCnt?: number;
+ drtwRlyPumpCnt?: number;
+ drtwPumpCnt?: number;
+ exlnPumpCnt?: number;
+}
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntItem */
+export interface SewerBscPlanTmplntItem {
+ swtrmtFcltCd?: string;
+ atchFileGroupId?: string;
+ sewerBscPlanTmplntDtlList?: SewerBscPlanTmplntDtlItem[];
+}
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntTmprStrgItem */
+export interface SewerBscPlanTmplntTmprStrgItem {
+ dtStrgDt?: string;
+ swtrmtFcltCd?: string;
+ prcsZoneArea?: number;
+ swtrmtPpltnCnt?: number;
+ planSwqty?: number;
+ fcltCpct?: number;
+ sprsysDrtwPplLen?: number;
+ sprsysExlnPplLen?: number;
+ cbsysPplLen?: number;
+ icptgPplLen?: number;
+ drgCnt?: number;
+ drtwRlyPumpCnt?: number;
+ drtwPumpCnt?: number;
+ exlnPumpCnt?: number;
+}
+
+/** 백엔드 SewerBscPlanApplySaveRequestDto */
+export interface SewerBscPlanApplySaveRequestDto extends PbadmsApplySaveHeader {
+ sewerBscPlan: SewerBscPlanSaveRequestDto;
+ sewerBscPlanDtl: SewerBscPlanDtlSaveRequestDto;
+ sewerBscPlanOtlnList?: SewerBscPlanOtlnItem[];
+ cklstResultList?: CklstResultItem[];
+ fnrsCstList?: SewerBscPlanFnrsCstItem[];
+ splmntDmndList?: SewerBscPlanSplmntDmndItem[];
+ rvwOpnnList?: SewerBscPlanRvwOpnnItem[];
+ esntlAtchFileList?: EsntlAtchFileItem[];
+ bizCstList?: SewerBscPlanBizCstItem[];
+ sewerBscPlanTmplntList?: SewerBscPlanTmplntItem[];
+ tmplntTmprStrgList?: SewerBscPlanTmplntTmprStrgItem[];
+}
+
+/** 백엔드 SewerBscPlanApplySaveResponseDto */
+export interface SewerBscPlanApplySaveResponseDto {
+ sewerBscPlanDocNo: string;
+ chgCycl: number;
+}
+
+/** 백엔드 SewerBscPlanDetResponseDto */
+export interface SewerBscPlanDetResponseDto {
+ detDto?: SewerBscPlanResponseDto;
+ activeDtlDto?: SewerBscPlanDtlResponseDto;
+ otlnList?: SewerBscPlanOtlnResponseDto[];
+ cklstResultRows?: CklstRvwRsltTreeDto[];
+ fnrsCstList?: SewerBscPlanFnrsCstResponseDto[];
+ splmntDmndList?: SewerBscPlanSplmntDmndResponseDto[];
+ rvwOpnnList?: SewerBscPlanRvwOpnnResponseDto[];
+ esntlFileList?: EsntlAtchFileMngTreeResponseDto[];
+ etcFileList?: AtflMngClientResponseDto[];
+ bizCstList?: SewerBscPlanBizCstResponseDto[];
+ tmplntList?: SewerBscPlanTmplntResponseDto[];
+ tmplntTmprStrgList?: SewerBscPlanTmplntTmprStrgResponseDto[];
+}
+
+/** Controller public 메서드명·시그니처와 동일 */
+export interface SewerBscPlanService {
+ findPage(params: SewerBscPlanSearchRequestDto): Promise>;
+ findById(sewerBscPlanDocNo: string): Promise;
+ save(body: SewerBscPlanSaveRequestDto): Promise;
+ update(
+ sewerBscPlanDocNo: string,
+ body: SewerBscPlanUpdateRequestDto,
+ ): Promise;
+ delete(sewerBscPlanDocNo: string): Promise;
+ saveApply(body: SewerBscPlanApplySaveRequestDto): Promise;
+ findDetails(sewerBscPlanDocNo: string, chgCycl: number): Promise;
+ submitSupplement(id: string, body: SewerBscPlanApplySaveRequestDto): Promise;
+ submitReqSupplement(
+ id: string,
+ chgCycl: number,
+ body: SewerBscPlanApplySaveRequestDto,
+ ): Promise;
+ techReview(id: string, body: SewerBscPlanApplySaveRequestDto): Promise;
+}
diff --git a/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts b/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts
index a34888317..9ba03d856 100644
--- a/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts
+++ b/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts
@@ -70,7 +70,12 @@ export function mapDetailToFormData(data: DesignCnstnMngDetResponseDto): DesignC
export function getCreateDefaultValues(
userInfo: IUserInfo | null,
- taskSeComCd?: string[],
+ options?: {
+ taskSeComCd?: string[];
+ admdstCd?: string;
+ sido?: string;
+ gugun?: string;
+ },
): DesignCnstnRegistFormData {
return {
designCnstnMng: {
@@ -78,7 +83,7 @@ export function getCreateDefaultValues(
bizBgngYr: "",
bizEndYr: "",
bizCn: "",
- taskSeComCd: taskSeComCd ?? [],
+ taskSeComCd: options?.taskSeComCd ?? [],
dsgnrNm: "",
dsgnrTelno: "",
dsgnrOgdpNm: "",
@@ -87,6 +92,9 @@ export function getCreateDefaultValues(
aplcntMblTelno: userInfo?.mphn ?? "",
aplcntOgdpNm: userInfo?.ogdpSeNm ?? "",
aplcntEmlAddr: userInfo?.email ?? "",
+ admdstCd: options?.admdstCd ?? "",
+ sido: options?.sido ?? "",
+ gugun: options?.gugun ?? "",
prgrsSttsComCd: "CV",
atchFileGroupId: "",
},
diff --git a/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx b/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx
index 6c09a5320..63d4eda75 100644
--- a/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx
+++ b/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx
@@ -36,10 +36,15 @@ interface DesignFormPageProps {
id?: string;
mode: "create" | "edit";
initialData?: DesignCnstnMngDetResponseDto;
- taskSeComCd?: string[];
+ createDefaults?: {
+ taskSeComCd?: string[];
+ admdstCd?: string;
+ sido?: string;
+ gugun?: string;
+ };
}
-function DesignFormPage({ id, mode, initialData, taskSeComCd }: DesignFormPageProps) {
+function DesignFormPage({ id, mode, initialData, createDefaults }: DesignFormPageProps) {
const queryClient = useQueryClient();
const router = useRouter();
const { confirm } = useConfirm();
@@ -54,7 +59,7 @@ function DesignFormPage({ id, mode, initialData, taskSeComCd }: DesignFormPagePr
defaultValues:
isEditMode && initialData
? mapDetailToFormData(initialData)
- : getCreateDefaultValues(userInfo, taskSeComCd),
+ : getCreateDefaultValues(userInfo, createDefaults),
});
function buildFormPayload(
diff --git a/apps/as/src/pages/design-cnstn/ui/design-list.tsx b/apps/as/src/pages/design-cnstn/ui/design-list.tsx
index 8a0986379..a23f2e9ea 100644
--- a/apps/as/src/pages/design-cnstn/ui/design-list.tsx
+++ b/apps/as/src/pages/design-cnstn/ui/design-list.tsx
@@ -11,7 +11,7 @@ import {
type DesignCnstnMngSearchDto,
useDesignCnstnMngPage,
} from "@/entities/design-cnstn";
-import { TaskSePickerDialog } from "@/widgets/pbadms-sprt";
+import { PbadmsTaskApplyDialog, type PbadmsTaskApplySelection } from "@/widgets/pbadms-sprt";
import { Button } from "@common/components";
import { formatTaskSeComCdForApi } from "../lib/design-cnstn-regist-schema";
@@ -71,9 +71,15 @@ function DesignList({ searchParams, pagination, onPaginationChange }: DesignList
},
});
- const handleConfirmTaskSe = (selectedCodes: string[]) => {
+ const handleConfirm = (selection: PbadmsTaskApplySelection) => {
setIsTaskSeDialogOpen(false);
- router.push(`/design-cnstn/regist?taskSeComCd=${formatTaskSeComCdForApi(selectedCodes)}`);
+ const qs = new URLSearchParams({
+ taskSeComCd: formatTaskSeComCdForApi(selection.selectedCodes),
+ admdstCd: selection.admdstCd,
+ sido: selection.sido,
+ gugun: selection.gugun,
+ });
+ router.push(`/design-cnstn/regist?${qs}`);
};
return (
@@ -87,10 +93,10 @@ function DesignList({ searchParams, pagination, onPaginationChange }: DesignList
{tableComponent}
-