This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
import { DashboardPage } from "@/pages/dashboard/ui/dashboard-page";
|
||||
|
||||
export default DashboardPage;
|
||||
@@ -1 +0,0 @@
|
||||
export { MainPage as default } from "@/pages/home";
|
||||
@@ -0,0 +1,498 @@
|
||||
# 화면설계서: 전국 행정지원 지도 (대시보드 메인)
|
||||
|
||||
## 1. 기본 정보
|
||||
|
||||
| 항목 | 내용 |
|
||||
| ---------- | -------------------------------------------- |
|
||||
| 버전 | 1.2 |
|
||||
| 프로세스명 | UI설계 |
|
||||
| 화면명 | 전국 행정지원 지도 (대시보드 메인) |
|
||||
| 화면 ID | TBD |
|
||||
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
|
||||
| 메뉴 경로 | 메인 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 화면 목적
|
||||
|
||||
로그인 사용자의 **기관 권한·당해 연도** 기준으로 문서별 진행상태 통계와 행정지원 알림을 한 화면에서 조회한다. 좌측 지도에서 전국 행정지원 현황을 보고, 우측에서 진행상황 카드·알림이력·공지사항을 확인한 뒤 알림 [처리하기]로 연계 업무 화면으로 이동한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 화면 구성
|
||||
|
||||
### 3-1. 전체 레이아웃
|
||||
|
||||
브레드크럼·페이지 타이틀은 스크린샷에 없음. 홈(`/`) 메인 콘텐츠로 구성한다.
|
||||
|
||||
```
|
||||
┌────────────────────────────────┬─────────────────────────────────────────────────┐
|
||||
│ [영역 A] 전국 행정지원 지도 │ [영역 B] 문서별 진행상황 카드 (2×2) │
|
||||
│ 타이틀: 전국 행정지원 지도 │ ┌───────────────┐ ┌───────────────┐ │
|
||||
│ │ │ 재원협의 │ │ 설치인가 │ │
|
||||
│ (대한민국 시도·시군구 경계) │ │ 신청/진행/완료│ │ 신청/진행/완료│ │
|
||||
│ 마커: 서울 강남구, │ ├───────────────┤ ├───────────────┤ │
|
||||
│ 대전 유성구, │ │ 설계자문 │ │ 기본계획 │ │
|
||||
│ 부산 해운대구 │ │ 신청/진행/완료│ │ 신청/진행/완료│ │
|
||||
│ │ └───────────────┘ └───────────────┘ │
|
||||
│ ├─────────────────────────────────────────────────┤
|
||||
│ │ [영역 C] 알림이력 │
|
||||
│ │ 알림이력 업데이트 HH:mm [전체보기]│
|
||||
│ │ [오늘 N건] [지연 N건] [완료대기 N건] │
|
||||
│ │ [뱃지] [일자] 제목 [처리하기]│
|
||||
│ │ … (최대 5건) │
|
||||
│ ├─────────────────────────────────────────────────┤
|
||||
│ │ [영역 D] 공지사항 [더보기] │
|
||||
│ │ ┌────┬────────┬────────┬────────┬──────┐ │
|
||||
│ │ │번호│ 제목 │ 작성자 │ 작성일 │조회수│ │
|
||||
│ │ └────┴────────┴────────┴────────┴──────┘ │
|
||||
└────────────────────────────────┴─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3-2. 화면 데이터 흐름
|
||||
|
||||
```
|
||||
BEGIN
|
||||
|
||||
// 최초 진입
|
||||
// searchYear 미전달 → 서버가 당해 연도로 고정. 알림 목록은 최신 5건
|
||||
CALL getMainDashboard(DashboardMainSearchDto) // Authorization은 client 인터셉터
|
||||
DISPLAY 진행상황 카드 (영역 B) // docPrgrsSttsList, TOTAL 행 제외
|
||||
DISPLAY 알림 통계 칩 · 알림 목록 (영역 C) // notiStats, notiList
|
||||
|
||||
CALL getUserPostList(UserPostSearchParams) // board-service, AS_DASHBOARD_BBS_ID
|
||||
DISPLAY 공지사항 그리드 (영역 D)
|
||||
|
||||
CALL 지도 마커/음영 조회(…) // TBD — 대시보드 API에 없음
|
||||
DISPLAY 전국 지도 (영역 A)
|
||||
|
||||
ON 영역 C [전체보기] 버튼 CLICK DO
|
||||
NAVIGATE /nti-hstry
|
||||
END ON
|
||||
|
||||
ON 영역 C [처리하기] 버튼 CLICK DO
|
||||
CALL readNoti(notiId) // entities/noti
|
||||
NAVIGATE notiUrl // 비어 있으면 이동하지 않음
|
||||
CALL getMainDashboard(…) // → 영역 C 갱신 (invalidate)
|
||||
END ON
|
||||
|
||||
ON 영역 D [더보기] 버튼 CLICK DO
|
||||
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID} // as 앱 공지 목록 라우트 미구현. st/cm 관례
|
||||
END ON
|
||||
|
||||
ON 영역 D 행 CLICK DO
|
||||
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}
|
||||
END ON
|
||||
|
||||
ON 영역 B 카드 CLICK DO
|
||||
NAVIGATE 해당 문서 목록 // §5-3
|
||||
END ON
|
||||
|
||||
ON 영역 A 마커 CLICK DO
|
||||
// TBD — 팝업/필터/이동 여부 미확정
|
||||
END ON
|
||||
|
||||
END
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 영역 A — 전국 행정지원 지도
|
||||
|
||||
스크린샷 타이틀: **전국 행정지원 지도**.
|
||||
|
||||
`getMainDashboard` 응답에 지도·좌표·행정구역 음영 데이터는 **없다**. 마커·음영 소스 API는 **TBD**.
|
||||
|
||||
### 4-1. 표시 요소
|
||||
|
||||
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||
| ----------- | --------- | ---- | ------ | ----------------- | ----------------------------------------------------------------- |
|
||||
| 섹션 타이틀 | Text | — | — | — | 고정 문구 `전국 행정지원 지도` |
|
||||
| 전국 지도 | Map | — | — | TBD | 시도·시군구 경계. 북·동부 음영(청) / 남서부 비음영(백) — 의미 TBD |
|
||||
| 마커 라벨 | Marker | — | — | TBD | 스크린샷 샘플: `서울 강남구`, `대전 유성구`, `부산 해운대구` |
|
||||
|
||||
### 4-2. 상호작용
|
||||
|
||||
| 동작 | 설명 |
|
||||
| ---------- | ----------------------------------------- |
|
||||
| 마커 클릭 | TBD (상세 팝업 / 해당 지자체 필터 / 이동) |
|
||||
| 지도 줌·팬 | TBD (스크린샷에 컨트롤 없음) |
|
||||
|
||||
`entities/adstdstr`는 시도·구군 **콤보**만 제공하며 좌표를 주지 않는다. 지도 라이브러리도 as 앱에 없음 — 선정 TBD.
|
||||
|
||||
---
|
||||
|
||||
## 5. 영역 B — 문서별 진행상황 카드
|
||||
|
||||
`DashboardMainResponseDto.docPrgrsSttsList`를 **`docSeComCd`로 매핑**해 4장 카드를 그린다. 배열 순서에 의존하지 않는다.
|
||||
|
||||
스크린샷 배치(좌→우, 상→하):
|
||||
|
||||
| 화면 카드 타이틀 | `docSeComCd` | API `docSeNm` |
|
||||
| ----------------- | ------------ | -------------- |
|
||||
| 재원협의 진행상황 | `FNRS` | 재원협의 |
|
||||
| 설치인가 진행상황 | `INSTL` | 설치인가 |
|
||||
| 설계자문 진행상황 | `CNFM` | 설계자문 |
|
||||
| 기본계획 진행상황 | `SEWER` | 하수도기본계획 |
|
||||
|
||||
서버 SQL `ORDER BY`는 `INSTL → FNRS → SEWER → CNFM` 이며, `GROUP BY ROLLUP`으로 **`docSeComCd = TOTAL`** 합계 행이 추가된다. 카드에는 TOTAL 행을 **표시하지 않는다**.
|
||||
|
||||
카드 타이틀은 스크린샷 문구(`… 진행상황`)를 쓴다. `SEWER`의 API명 `하수도기본계획`은 화면에서 `기본계획`으로 축약한다.
|
||||
|
||||
### 5-1. 카드 내부 필드
|
||||
|
||||
각 카드 공통 구조. `totCnt`는 화면에 없음.
|
||||
|
||||
| 표시명 | Field | 비고 |
|
||||
| ------ | ---------- | ------------------ |
|
||||
| 신청 | `reqCnt` | 파란 아이콘 + 건수 |
|
||||
| 진행 | `prgrsCnt` | 초록 아이콘 + 건수 |
|
||||
| 완료 | `cmptnCnt` | 주황 아이콘 + 건수 |
|
||||
|
||||
상태 그룹(서버): 임시저장(`SV`/`CV`) 제외. 문서별 `prgrs_stts_com_cd` → `REQ`/`PRG`/`CMP`.
|
||||
|
||||
### 5-2. 검색 파라미터 (화면 미표시)
|
||||
|
||||
영역 B에 연도·필터 UI는 없다. 서버 기본값을 따른다.
|
||||
|
||||
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||
| ------ | --------- | ---- | --------- | ----------------- | --------------------------------------------------------------------- |
|
||||
| (없음) | — | — | 당해 연도 | `searchYear` | 미전달 시 서버 `LocalDate.now().getYear()`. 연도 셀렉터 추가 여부 TBD |
|
||||
|
||||
`docSeComCd` 쿼리는 **알림 목록 필터**에만 쓰이며, 진행상황 SQL에는 전달되지 않는다. 카드 4종은 항상 전원 집계한다.
|
||||
|
||||
### 5-3. 상호작용
|
||||
|
||||
카드 클릭 시 **해당 문서 목록 페이지**로 이동한다. `docSeComCd` → 라우트 고정 매핑.
|
||||
|
||||
| 동작 | `docSeComCd` | 이동 경로 |
|
||||
| --------- | ------------ | ----------------- |
|
||||
| 카드 클릭 | `FNRS` | `/fnrs-cnsltn` |
|
||||
| 카드 클릭 | `INSTL` | `/instl-sncn` |
|
||||
| 카드 클릭 | `CNFM` | `/design-cnstn` |
|
||||
| 카드 클릭 | `SEWER` | `/sewer-bsc-plan` |
|
||||
|
||||
---
|
||||
|
||||
## 6. 영역 C — 알림이력
|
||||
|
||||
서버가 cms-service 알림을 대시보드 응답에 합친다. 목록은 **page=0, size=5** (최신 5건). 페이지네이션 없음.
|
||||
|
||||
### 6-1. 헤더
|
||||
|
||||
| 표시명 | Field / 소스 | 비고 |
|
||||
| ------------- | ------------ | --------------------------------------------------------------------------------------- |
|
||||
| 섹션 타이틀 | — | 고정 문구 `알림이력` |
|
||||
| 업데이트 시각 | (client) | 스크린샷 `업데이트 10:32`. API 필드 없음. `getMainDashboard` 마지막 fetch 시각(`HH:mm`) |
|
||||
| 전체보기 | Button | `/nti-hstry`로 이동 |
|
||||
|
||||
### 6-2. 통계 칩
|
||||
|
||||
`NotiAsStatResponseDto`.
|
||||
|
||||
| 표시명 | Field | 비고 |
|
||||
| ------------ | -------------- | -------------------------------------- |
|
||||
| 오늘 N건 | `todayCount` | 당일 00:00 이후 수신 |
|
||||
| 지연 N건 | `delayCount` | `notReadYn=N` 이고 수신 후 3일 이상 |
|
||||
| 완료대기 N건 | `pendingCount` | `notReadYn=Y` 이고 `notiPrcsCmptnYn=N` |
|
||||
|
||||
### 6-3. 목록 행
|
||||
|
||||
| 표시명 | Field | 비고 |
|
||||
| ------ | --------- | --------------------------------------------- |
|
||||
| 일자 | `regDt` | 표시 `YYYY-MM-DD`. 응답 `yyyy-MM-dd HH:mm:ss` |
|
||||
| 제목 | `notiTtl` | 알림 제목 |
|
||||
| 처리 | (action) | 스크린샷 라벨 `처리하기`. 클릭 §6-4 |
|
||||
|
||||
응답에 있으나 이 목록에 **표시하지 않음**:
|
||||
|
||||
| Field | 비고 |
|
||||
| ----------------- | ----------------------------------------- |
|
||||
| `notiId` | 행 키 · `readNoti` 파라미터 |
|
||||
| `recvUnqId` | 수신 회원 UUID (서버 로그인 사용자 고정) |
|
||||
| `senderUnqId` | 발신 회원 UUID |
|
||||
| `notiType` | `ADSS` |
|
||||
| `notiCn` | 본문 |
|
||||
| `notiUrl` | 처리 후 이동 URL. 컬럼 미표시 |
|
||||
| `notiLnkgUnqNo` | 알림연결고유번호 |
|
||||
| `docSeComCd` | 문서구분 코드 |
|
||||
| `docSeComNm` | 문서구분명 (알림이력 화면에서는 연계업무) |
|
||||
| `notReadYn` | 뱃지 판정 TBD에 사용 가능 |
|
||||
| `notiIdntyYn` | 확인 여부 |
|
||||
| `notiIdntyDt` | 확인 일시 |
|
||||
| `notiPrcsCmptnYn` | 처리 완료 여부 |
|
||||
| `notiPrcsCmptnDt` | 처리 완료 일시 |
|
||||
|
||||
### 6-4. 행 상호작용
|
||||
|
||||
| 동작 | 설명 |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| [전체보기] 클릭 | `/nti-hstry` (알림이력 목록)로 이동 |
|
||||
| [처리하기] 클릭 | `readNoti(notiId)` 후 `notiUrl`로 이동. URL이 비면 이동하지 않음(읽음 처리는 수행). `dashboardBaseKey` invalidate |
|
||||
|
||||
`DashboardController`에 읽음 API는 없다. **`entities/noti`의 `readNoti`** 를 쓴다. 알림이력 화면과 동일 (`PUT /cms-service/api/v1/noti/{notiId}/read`).
|
||||
|
||||
### 6-5. 검색 파라미터 (화면 미표시)
|
||||
|
||||
서버 `toNotiSearchDto()` 기본: `notiType = ADSS`. 화면 필터 없음.
|
||||
|
||||
| DashboardMainSearchDto | 화면 | 비고 |
|
||||
| ---------------------- | ---- | --------------------------------- |
|
||||
| `notiType` | 없음 | 서버 기본 `ADSS` |
|
||||
| `notReadYn` | 없음 | 통계 칩 클릭 필터 시 후보 (TBD) |
|
||||
| `startDt` / `endDt` | 없음 | 대시보드 기본 미전달 |
|
||||
| `docSeComCd` | 없음 | 알림만 필터. 진행상황 카드와 무관 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 영역 D — 공지사항
|
||||
|
||||
`getMainDashboard`에 공지 필드는 없다. **board-service** `getUserPostList`로 별도 조회한다.
|
||||
|
||||
- Base: `GET /board-service/api/v1/posts/user/{AS_DASHBOARD_BBS_ID}`
|
||||
- `AS_DASHBOARD_BBS_ID` = `NEXT_PUBLIC_AS_DASHBOARD_BBS_ID` (로컬 예: `7`)
|
||||
- 클라이언트: `@/entities/board` — `postApi.getUserPostList` / `useUserPostList`
|
||||
|
||||
페이지네이션 컨트롤은 없다. 요약 목록만 표시한다 (`page=0`, `size=3`).
|
||||
|
||||
### 7-1. 헤더·버튼
|
||||
|
||||
| 버튼명 | 동작 |
|
||||
| ------ | ------------------------------------------------------------------------------------------- |
|
||||
| 더보기 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}` 로 이동. as 앱에 공지 목록 라우트는 없음(st/cm 관례) |
|
||||
|
||||
### 7-2. 그리드 컬럼
|
||||
|
||||
스크린샷 헤더 순서. `UserPost`.
|
||||
|
||||
| 표시명 | Field | 비고 |
|
||||
| ------ | --------- | ---------------------------------------------------- |
|
||||
| 번호 | `rowNo` | `upndFixYn` = `Y` 이면 `[공지]` Badge, 그 외 `rowNo` |
|
||||
| 제목 | `pstTtl` | truncate |
|
||||
| 작성자 | `wrtrNm` | |
|
||||
| 작성일 | `regDt` | 표시 `YYYY-MM-DD` (응답 datetime) |
|
||||
| 조회수 | `inqNocs` | 숫자 |
|
||||
|
||||
조회 파라미터 (`UserPostSearchParams`):
|
||||
|
||||
| 필드 | 값 | 비고 |
|
||||
| ------------- | ----------- | ------------------------------------------ |
|
||||
| `page` | `0` | Spring 0-based |
|
||||
| `size` | `3` | 대시보드 요약. 스크린샷 3행 |
|
||||
| `keywordType` | `bbsTypeCd` | st 대시보드 공지 패널과 동일 |
|
||||
| `keyword` | `BBSNOTICE` | 게시판 유형. path의 bbsId와 함께 공지 한정 |
|
||||
|
||||
응답에 있으나 그리드에 **표시하지 않음**: `bbsId`, `pstNo`(행 키·상세 이동), `pstCn`, `upndFixYn`(번호 열 판정만), `rgtrNm`, `atflCd` 등.
|
||||
|
||||
### 7-3. 행 상호작용
|
||||
|
||||
| 동작 | 설명 |
|
||||
| ------- | ------------------------------------------------------------------------------ |
|
||||
| 행 클릭 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}` (as 앱 공지 상세 라우트 미구현) |
|
||||
|
||||
---
|
||||
|
||||
## 8. API/DTO 명세
|
||||
|
||||
### 8-1. 공통
|
||||
|
||||
| 항목 | 값 |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| Base prefix | `/as-service/api/v1/dashboard` (`ENDPOINT.AS_SERVICE.DASHBOARD`) |
|
||||
| 인증 | `Authorization` 헤더. 프론트는 `client` 인터셉터. 로그인 기관 권한은 서버가 사용자 캐시로 적용 |
|
||||
| 클라이언트 모듈 | `@/entities/dashboard` — `dashboardService` / `useGetMainDashboard` |
|
||||
| 알림 읽음 | `@/entities/noti` — `notiService.readNoti` / `useReadNoti` |
|
||||
| 공지사항 | `@/entities/board` — `postApi.getUserPostList` / `useUserPostList` |
|
||||
|
||||
영역 A(지도)는 본 컨트롤러·board-service 범위 밖 → **TBD**.
|
||||
|
||||
### 8-2. 대시보드 메인 조회 — `getMainDashboard`
|
||||
|
||||
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||
| -------------------------------------------- | ------ | ------------- | ------------------------------- | -------------------------- | ----------------------------------------------------------- |
|
||||
| 대시보드 메인 통합진행상태 및 알림 정보 조회 | GET | `{BASE}/main` | Query: `DashboardMainSearchDto` | `DashboardMainResponseDto` | `dashboardService.getMainDashboard` / `useGetMainDashboard` |
|
||||
|
||||
**Request (`DashboardMainSearchDto`)**
|
||||
|
||||
| 필드 | 필수 | 설명 |
|
||||
| ------------ | ---- | ---------------------------------------------------------------------- |
|
||||
| `searchYear` | N | 검색 연도. 미전달 시 서버 당해 연도. 영역 B 집계 기간 |
|
||||
| `notiType` | N | 알림 종류. 미전달 시 서버 `ADSS` |
|
||||
| `notReadYn` | N | 알림 확인 여부 `Y`/`N`. 본 화면 미사용 |
|
||||
| `startDt` | N | 알림 시작일 `YYYY-MM-DD`. 본 화면 미사용 |
|
||||
| `endDt` | N | 알림 종료일 `YYYY-MM-DD`. 본 화면 미사용 |
|
||||
| `docSeComCd` | N | 알림 문서구분 (`INSTL`/`FNRS`/`SEWER`/`CNFM` 등). 진행상황 집계와 무관 |
|
||||
|
||||
`Authorization`은 service 시그니처에 넣지 않는다.
|
||||
|
||||
**Response (`DashboardMainResponseDto`)**
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
| ------------------ | --------------------------- | --------------------------- |
|
||||
| `docPrgrsSttsList` | `DocPrgrsSttsResponseDto[]` | 문서별 진행상태 + TOTAL 행 |
|
||||
| `notiList` | `NotiAsUserResponseDto[]` | 최신 최대 5건 |
|
||||
| `notiStats` | `NotiAsStatResponseDto` | 오늘 / 지연 / 완료대기 건수 |
|
||||
|
||||
**`DocPrgrsSttsResponseDto`**
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
| ------------ | ------ | --------------------------------------------- |
|
||||
| `docSeNm` | string | 문서 구분 명칭 |
|
||||
| `docSeComCd` | string | `INSTL` / `FNRS` / `SEWER` / `CNFM` / `TOTAL` |
|
||||
| `reqCnt` | number | 신청 건수 |
|
||||
| `prgrsCnt` | number | 진행 건수 |
|
||||
| `cmptnCnt` | number | 완료 건수 |
|
||||
| `totCnt` | number | 총 건수 (화면 미표시) |
|
||||
|
||||
**`NotiAsStatResponseDto`**
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
| -------------- | ------ | --------- |
|
||||
| `todayCount` | number | 오늘 알림 |
|
||||
| `delayCount` | number | 지연 알림 |
|
||||
| `pendingCount` | number | 완료대기 |
|
||||
|
||||
**`NotiAsUserResponseDto`** — 필드 상세는 `docs/spec/nti-hstry` §6-2와 동일 (`notiId`, `notiTtl`, `regDt`, `notiUrl`, `notReadYn`, `notiPrcsCmptnYn` 등).
|
||||
|
||||
### 8-3. 알림 읽음 처리 — `readNoti`
|
||||
|
||||
대시보드 컨트롤러가 아니라 cms-service. 영역 C [처리하기]에서 호출.
|
||||
|
||||
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||
| -------------- | ------ | ---------------------------------------- | ----------------------- | ---------------- | -------------------------------------- |
|
||||
| 알림 읽음 처리 | PUT | `/cms-service/api/v1/noti/{notiId}/read` | Path: `notiId` (`Long`) | `Long` (알림 ID) | `notiService.readNoti` / `useReadNoti` |
|
||||
|
||||
Body 없음. 성공 시 `dashboardBaseKey`(및 필요 시 `notiBaseKey`) invalidate.
|
||||
|
||||
### 8-4. 전국 행정지원 지도 — TBD
|
||||
|
||||
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||
| ------------------- | ------ | --- | ----------- | ------------ | --- |
|
||||
| 지도 마커·음영 조회 | TBD | TBD | TBD | TBD | TBD |
|
||||
|
||||
### 8-5. 공지사항 — `getUserPostList`
|
||||
|
||||
board-service. bbsId는 환경변수 `AS_DASHBOARD_BBS_ID`로 path에 고정한다.
|
||||
|
||||
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||
| ----------------------- | ------ | ------------------------------------------ | ----------------------------- | ------------------------ | --------------------------------------------- |
|
||||
| 대시보드 공지 요약 목록 | GET | `/board-service/api/v1/posts/user/{bbsId}` | Query: `UserPostSearchParams` | `PageResponse<UserPost>` | `postApi.getUserPostList` / `useUserPostList` |
|
||||
|
||||
**Request (`UserPostSearchParams`)** — 본 화면 고정값
|
||||
|
||||
| 필드 | 필수 | 설명 |
|
||||
| ------------- | ---- | -------------- |
|
||||
| `page` | N | `0` |
|
||||
| `size` | N | `3` |
|
||||
| `keywordType` | N | `bbsTypeCd` |
|
||||
| `keyword` | N | `BBSNOTICE` |
|
||||
| `popupYn` 등 | N | 본 화면 미사용 |
|
||||
|
||||
**Response (`UserPost`) — 화면 매핑 필드**
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
| ----------- | ------ | --------------------------- |
|
||||
| `rowNo` | number | 번호. 고정글이 아니면 표시 |
|
||||
| `pstNo` | number | 게시 번호. 상세 이동 키 |
|
||||
| `bbsId` | number | 게시판 ID |
|
||||
| `pstTtl` | string | 제목 |
|
||||
| `wrtrNm` | string | 작성자 |
|
||||
| `regDt` | string | 작성일 |
|
||||
| `inqNocs` | number | 조회수 |
|
||||
| `upndFixYn` | string | 상단고정 `Y`/`N` → `[공지]` |
|
||||
|
||||
---
|
||||
|
||||
## 9. UI 구현 가이드
|
||||
|
||||
### 9-1. 기술 스택·원칙
|
||||
|
||||
- 라우트: 기존 홈 `app/(main)/(with-base-layout)/page.tsx` → `pages/home` (현재 `MainPage` 플레이스홀더)
|
||||
- UI: `@common/components` — Card, Badge, Button, Table(공지). 지도 라이브러리 TBD
|
||||
- 데이터: React Query `useGetMainDashboard()`. 처리하기는 `useReadNoti()`. 공지는 `useUserPostList()`
|
||||
- 진행상황 카드: `docSeComCd`로 4슬롯 매핑. `TOTAL` 제외
|
||||
- 알림 목록: 테이블이 아닌 행 리스트(뱃지 + 일자 + 제목 + 버튼)
|
||||
- 공지: `@tanstack/react-table` + `useDataTable`, 페이지네이션 없음
|
||||
- 검색 조건 URL 동기화: 본 화면 필터 없음. `searchYear` UI가 생기면 그때 검토
|
||||
|
||||
### 9-2. 모듈·파일 배치
|
||||
|
||||
| 구분 | 경로 |
|
||||
| ------------- | ------------------------------------------------------------ |
|
||||
| 라우트 | `app/(main)/(with-base-layout)/page.tsx` |
|
||||
| 페이지 | `pages/dashboard/ui/dashboard-page.tsx` |
|
||||
| 지도 | `pages/dashboard/ui/national-as-map.tsx` (API TBD, Skeleton) |
|
||||
| 진행상황 카드 | `pages/dashboard/ui/doc-prgrs-stts-cards.tsx` |
|
||||
| 알림이력 | `pages/dashboard/ui/dashboard-noti-list.tsx` |
|
||||
| 공지사항 | `pages/dashboard/ui/dashboard-notice-table.tsx` |
|
||||
| 공지 컬럼 | `pages/dashboard/model/dashboard-notice-table-columns.tsx` |
|
||||
| Entity API | `entities/dashboard/api/dashboard-service.ts` |
|
||||
| Entity Hook | `entities/dashboard/api/queries.ts` → `useGetMainDashboard` |
|
||||
| Entity Model | `entities/dashboard/model/dashboard-interface.ts` |
|
||||
| 알림 읽음 | `entities/noti` → `useReadNoti` |
|
||||
| 공지 Entity | `entities/board` → `useUserPostList` |
|
||||
|
||||
Entity(`getMainDashboard`, `getUserPostList`) 구현됨. 영역 B·C·D 화면 구현. 영역 A는 Skeleton.
|
||||
|
||||
### 9-3. 영역별 UI 매핑
|
||||
|
||||
| 영역 | 컴포넌트 | 데이터 |
|
||||
| ---- | ------------------------ | ---------------------------------------------------------------------------- |
|
||||
| A | `national-as-map` | Skeleton (지도 API TBD) |
|
||||
| B | `doc-prgrs-stts-cards` | `useGetMainDashboard` → `docPrgrsSttsList` (`TOTAL` 제외, 코드 매핑) |
|
||||
| C | `dashboard-noti-list` | 동 훅 → `notiStats`, `notiList`. 처리 → `useReadNoti`. 전체보기 `/nti-hstry` |
|
||||
| D | `dashboard-notice-table` | `useUserPostList` → `content[]`. [더보기]·행 클릭 → `/cms/bbsnotice/...` |
|
||||
|
||||
### 9-4. 다이얼로그·라우팅
|
||||
|
||||
| 항목 | 내용 |
|
||||
| -------------- | -------------------------------------------------------------------------------------- |
|
||||
| 다이얼로그 | 없음 |
|
||||
| 본 화면 라우트 | `/` (홈) |
|
||||
| 알림 처리 이동 | [처리하기] → `readNoti` 후 `notiUrl` (알림이력과 동일, SEWER URL은 상세 세그먼트 제거) |
|
||||
| 알림 전체보기 | `/nti-hstry` |
|
||||
| 공지 더보기 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}` (as 앱 공지 라우트 미구현, st/cm 관례) |
|
||||
| 공지 행 클릭 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}` |
|
||||
| 진행상황 카드 | §5-3 목록 라우트 (`/fnrs-cnsltn`, `/instl-sncn`, `/design-cnstn`, `/sewer-bsc-plan`) |
|
||||
|
||||
### 9-5. 데이터 흐름
|
||||
|
||||
```
|
||||
BEGIN
|
||||
|
||||
// 최초 진입 (홈)
|
||||
CALL getMainDashboard({})
|
||||
DISPLAY 영역 B // docPrgrsSttsList — FNRS/INSTL/CNFM/SEWER 슬롯
|
||||
DISPLAY 영역 C // notiStats + notiList(≤5)
|
||||
|
||||
CALL getUserPostList({ page: 0, size: 3, keywordType: "bbsTypeCd", keyword: "BBSNOTICE" })
|
||||
DISPLAY 영역 D
|
||||
|
||||
CALL 지도 데이터 // TBD → 영역 A
|
||||
|
||||
ON 영역 C [전체보기] CLICK DO
|
||||
NAVIGATE /nti-hstry
|
||||
END ON
|
||||
|
||||
ON 영역 C [처리하기] CLICK DO
|
||||
CALL readNoti(notiId)
|
||||
IF notiUrl THEN
|
||||
NAVIGATE notiUrl
|
||||
END IF
|
||||
CALL getMainDashboard // invalidate dashboardBaseKey
|
||||
END ON
|
||||
|
||||
ON 영역 B 카드 CLICK DO
|
||||
NAVIGATE 문서 목록 // §5-3
|
||||
END ON
|
||||
|
||||
ON 영역 D [더보기] CLICK DO
|
||||
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}
|
||||
END ON
|
||||
|
||||
ON 영역 D 행 CLICK DO
|
||||
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}
|
||||
END ON
|
||||
|
||||
END
|
||||
```
|
||||
@@ -71,10 +71,10 @@ export function middleware(req: NextRequest) {
|
||||
return addSecurityHeaders(response);
|
||||
}
|
||||
|
||||
// if (pathname === "/") {
|
||||
// const response = NextResponse.redirect(new URL("/dashboard", req.url));
|
||||
// return addSecurityHeaders(response);
|
||||
// }
|
||||
if (pathname === "/") {
|
||||
const response = NextResponse.redirect(new URL("/dashboard", req.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
console.log(logMessages.join("\n"));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const subDomain = process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
@@ -7,6 +9,31 @@ const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
globalNotFound: true,
|
||||
},
|
||||
|
||||
async rewrites() {
|
||||
return {
|
||||
beforeFiles: [
|
||||
{
|
||||
source: "/_next/static/:path*",
|
||||
destination: `${subDomain}/_next/static/:path*`,
|
||||
has: [
|
||||
{
|
||||
type: "header",
|
||||
key: "referer",
|
||||
value: "(.*)/cms(.*)",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
afterFiles: [
|
||||
{
|
||||
source: "/cms/:path*",
|
||||
destination: `${subDomain}/cms/:path*`,
|
||||
},
|
||||
],
|
||||
fallback: [],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type { UserPostSearchParams } from "../model/post-interface";
|
||||
|
||||
import { postApi } from "./post-service";
|
||||
|
||||
export function useUserPostList(params?: UserPostSearchParams) {
|
||||
return useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, params],
|
||||
queryFn: () => postApi.getUserPostList(params),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { AS_DASHBOARD_BBS_ID, ENDPOINT } from "@/shared/config";
|
||||
|
||||
import { PostService, UserPostListResponse, UserPostSearchParams } from "../model/post-interface";
|
||||
|
||||
async function getUserPostList(params?: UserPostSearchParams): Promise<UserPostListResponse> {
|
||||
const response = await client.get<UserPostListResponse>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/user/${AS_DASHBOARD_BBS_ID}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const postApi: PostService = {
|
||||
getUserPostList,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export { useUserPostList } from "./api/post-queries";
|
||||
export { postApi } from "./api/post-service";
|
||||
export type {
|
||||
PostService,
|
||||
UserPost,
|
||||
UserPostListResponse,
|
||||
UserPostSearchParams,
|
||||
} from "./model/post-interface";
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { PageableParams, PageResponse } from "@/shared/model";
|
||||
|
||||
export interface UserPostSearchParams extends PageableParams {
|
||||
keywordType?: string;
|
||||
keyword?: string;
|
||||
popupYn?: string;
|
||||
upndFixYn?: string;
|
||||
prdPstgYn?: string;
|
||||
siteId?: string;
|
||||
bbsTypeCd?: string;
|
||||
ansUseYn?: string;
|
||||
}
|
||||
|
||||
export interface UserPost {
|
||||
rowNo: number;
|
||||
bbsId: number;
|
||||
pstNo: number;
|
||||
pstTtl: string;
|
||||
pstCn: string;
|
||||
inqNocs: number;
|
||||
rfrncUrl: string;
|
||||
popupYn: string;
|
||||
upndFixYn: string;
|
||||
prdPstgYn: string;
|
||||
rootPath: string;
|
||||
pstPath: string;
|
||||
ansPstn: number;
|
||||
wrtrNm: string;
|
||||
wrtrCplc: string;
|
||||
atflCd: string;
|
||||
regDt: string;
|
||||
rgtrNm: string;
|
||||
cmntCnt: number;
|
||||
}
|
||||
|
||||
export type UserPostListResponse = PageResponse<UserPost>;
|
||||
|
||||
export interface PostService {
|
||||
getUserPostList(params?: UserPostSearchParams): Promise<UserPostListResponse>;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
DashboardMainResponseDto,
|
||||
DashboardMainSearchDto,
|
||||
DashboardService,
|
||||
} from "../model/dashboard-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.DASHBOARD;
|
||||
|
||||
async function getMainDashboard(
|
||||
searchDto: DashboardMainSearchDto,
|
||||
): Promise<DashboardMainResponseDto> {
|
||||
const response = await client.get<DashboardMainResponseDto>(`${BASE}/main`, {
|
||||
params: searchDto,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const dashboardService = {
|
||||
getMainDashboard,
|
||||
} satisfies DashboardService;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type { DashboardMainSearchDto } from "../model/dashboard-interface";
|
||||
|
||||
import { dashboardService } from "./dashboard-service";
|
||||
|
||||
export const dashboardBaseKey = [ENDPOINT.AS_SERVICE.DASHBOARD] as const;
|
||||
|
||||
const EMPTY_SEARCH: DashboardMainSearchDto = {};
|
||||
|
||||
export function useGetMainDashboard(params: DashboardMainSearchDto = EMPTY_SEARCH) {
|
||||
return useQuery({
|
||||
queryKey: [...dashboardBaseKey, "getMainDashboard", params],
|
||||
queryFn: () => dashboardService.getMainDashboard(params),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export { dashboardService } from "./api/dashboard-service";
|
||||
export { dashboardBaseKey, useGetMainDashboard } from "./api/queries";
|
||||
export type {
|
||||
DashboardMainResponseDto,
|
||||
DashboardMainSearchDto,
|
||||
DashboardService,
|
||||
DocPrgrsSttsResponseDto,
|
||||
NotiAsStatResponseDto,
|
||||
NotiAsUserResponseDto,
|
||||
} from "./model/dashboard-interface";
|
||||
@@ -0,0 +1,58 @@
|
||||
/** 백엔드 DashboardMainSearchDto */
|
||||
export interface DashboardMainSearchDto {
|
||||
searchYear?: number;
|
||||
notiType?: string;
|
||||
notReadYn?: "Y" | "N";
|
||||
startDt?: string;
|
||||
endDt?: string;
|
||||
docSeComCd?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 DocPrgrsSttsResponseDto */
|
||||
export interface DocPrgrsSttsResponseDto {
|
||||
docSeNm: string;
|
||||
docSeComCd: string;
|
||||
reqCnt: number;
|
||||
prgrsCnt: number;
|
||||
cmptnCnt: number;
|
||||
totCnt: number;
|
||||
}
|
||||
|
||||
/** 백엔드 NotiAsUserResponseDto */
|
||||
export interface NotiAsUserResponseDto {
|
||||
notiId: number;
|
||||
recvUnqId: string;
|
||||
senderUnqId: string;
|
||||
notiType: string;
|
||||
notiTtl: string;
|
||||
notiCn?: string;
|
||||
notReadYn?: "Y" | "N";
|
||||
regDt?: string;
|
||||
notiUrl?: string;
|
||||
notiLnkgUnqNo?: string;
|
||||
docSeComCd?: string;
|
||||
docSeComNm?: string;
|
||||
notiIdntyDt?: string;
|
||||
notiIdntyYn?: "Y" | "N";
|
||||
notiPrcsCmptnYn?: "Y" | "N";
|
||||
notiPrcsCmptnDt?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 NotiAsStatResponseDto */
|
||||
export interface NotiAsStatResponseDto {
|
||||
todayCount: number;
|
||||
delayCount: number;
|
||||
pendingCount: number;
|
||||
}
|
||||
|
||||
/** 백엔드 DashboardMainResponseDto */
|
||||
export interface DashboardMainResponseDto {
|
||||
docPrgrsSttsList: DocPrgrsSttsResponseDto[];
|
||||
notiList: NotiAsUserResponseDto[];
|
||||
notiStats: NotiAsStatResponseDto;
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface DashboardService {
|
||||
getMainDashboard(searchDto: DashboardMainSearchDto): Promise<DashboardMainResponseDto>;
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export function useReadNoti() {
|
||||
mutationFn: (notiId: number) => notiService.readNoti(notiId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: notiBaseKey });
|
||||
queryClient.invalidateQueries({ queryKey: [ENDPOINT.AS_SERVICE.DASHBOARD] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { DashboardPage as MainPage } from "./ui/dashboard-page";
|
||||
@@ -0,0 +1,39 @@
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { UserPost } from "@/entities/board";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<UserPost>();
|
||||
|
||||
export const dashboardNoticeColumns = [
|
||||
columnHelper.display({
|
||||
id: "no",
|
||||
header: "번호",
|
||||
meta: { align: "center" },
|
||||
cell: ({ row }) => {
|
||||
if (row.original.upndFixYn === "Y") {
|
||||
return <Badge size="lg">공지</Badge>;
|
||||
}
|
||||
return row.original.rowNo;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("pstTtl", {
|
||||
header: "제목",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => <div className="min-w-[12rem] truncate px-2 text-left">{getValue()}</div>,
|
||||
}),
|
||||
columnHelper.accessor("wrtrNm", {
|
||||
header: "작성자",
|
||||
meta: { align: "center" },
|
||||
}),
|
||||
columnHelper.accessor("regDt", {
|
||||
header: "작성일",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => (getValue() ? dayjs(getValue()).format("YYYY-MM-DD") : "-"),
|
||||
}),
|
||||
columnHelper.accessor("inqNocs", {
|
||||
header: "조회수",
|
||||
meta: { align: "center" },
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,143 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import { NoData } from "@/shared/ui/no-data";
|
||||
import type { NotiAsStatResponseDto, NotiAsUserResponseDto } from "@/entities/dashboard";
|
||||
import { useReadNoti } from "@/entities/noti";
|
||||
import { Badge, Button, Skeleton } from "@common/components";
|
||||
|
||||
interface DashboardNotiListProps {
|
||||
notiList: NotiAsUserResponseDto[] | undefined;
|
||||
notiStats: NotiAsStatResponseDto | undefined;
|
||||
isPending: boolean;
|
||||
isError: boolean;
|
||||
dataUpdatedAt: number;
|
||||
}
|
||||
|
||||
export function DashboardNotiList({
|
||||
notiList,
|
||||
notiStats,
|
||||
isPending,
|
||||
isError,
|
||||
dataUpdatedAt,
|
||||
}: DashboardNotiListProps) {
|
||||
const router = useRouter();
|
||||
const { mutateAsync: readNoti, isPending: isReadPending } = useReadNoti();
|
||||
|
||||
const handleAction = useCallback(
|
||||
async (row: NotiAsUserResponseDto) => {
|
||||
if (row.notReadYn === "N") await readNoti(row.notiId);
|
||||
|
||||
const notiUrl = resolveNotiUrl(row);
|
||||
if (!notiUrl) return;
|
||||
|
||||
router.push(notiUrl);
|
||||
},
|
||||
[readNoti, router],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card-header flex items-center justify-between gap-4">
|
||||
<h3 className="text-2xl font-semibold">
|
||||
알림이력
|
||||
{dataUpdatedAt > 0 ? (
|
||||
<span className="pl-2 text-lg font-medium text-muted-foreground">
|
||||
(업데이트 일시: {dayjs(dataUpdatedAt).format("YYYY-MM-DD HH:mm")})
|
||||
</span>
|
||||
) : null}
|
||||
</h3>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button type="button" size="sm" color="white" onClick={() => router.push("/nti-hstry")}>
|
||||
전체보기
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 p-2">
|
||||
<div className="flex flex-wrap gap-2 pt-2">
|
||||
{NOTI_STAT_CHIPS.map((chip) => (
|
||||
<Badge key={chip.field} color={chip.color}>
|
||||
{chip.label} {isPending ? "-" : (notiStats?.[chip.field] ?? 0)}건
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isPending ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-14 w-full" />
|
||||
<Skeleton className="h-14 w-full" />
|
||||
<Skeleton className="h-14 w-full" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isError ? (
|
||||
<p className="flex min-h-[12rem] items-center justify-center text-muted-foreground">
|
||||
알림이력을 불러오지 못했습니다.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{!isPending && !isError && (notiList?.length ?? 0) === 0 ? <NoData /> : null}
|
||||
|
||||
<div>
|
||||
{!isPending && !isError && notiList && notiList.length > 0
|
||||
? notiList.map((row) => (
|
||||
<DashboardNotiRow
|
||||
key={row.notiId}
|
||||
row={row}
|
||||
isActionPending={isReadPending}
|
||||
onAction={() => {
|
||||
void handleAction(row);
|
||||
}}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
interface DashboardNotiRowProps {
|
||||
row: NotiAsUserResponseDto;
|
||||
isActionPending: boolean;
|
||||
onAction: () => void;
|
||||
}
|
||||
|
||||
function DashboardNotiRow({ row, isActionPending, onAction }: DashboardNotiRowProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-4 border-b px-2 py-4 last:border-b-0">
|
||||
<span className="shrink-0 text-lg text-muted-foreground">
|
||||
{row.regDt ? dayjs(row.regDt).format("YYYY-MM-DD") : "-"}
|
||||
</span>
|
||||
<p className="min-w-0 flex-1 truncate text-xl">{row.notiTtl}</p>
|
||||
<Button type="button" size="sm" color="white" disabled={isActionPending} onClick={onAction}>
|
||||
처리하기
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function resolveNotiUrl(row: NotiAsUserResponseDto) {
|
||||
const notiUrl = row.notiUrl?.trim();
|
||||
if (!notiUrl) return null;
|
||||
|
||||
if (row.docSeComCd === "SEWER") {
|
||||
const parts = notiUrl.split("/");
|
||||
if (parts.length > 1) {
|
||||
parts.pop();
|
||||
return parts.join("/");
|
||||
}
|
||||
}
|
||||
|
||||
return notiUrl;
|
||||
}
|
||||
|
||||
const NOTI_STAT_CHIPS = [
|
||||
{ field: "todayCount", label: "오늘", color: "green" },
|
||||
{ field: "delayCount", label: "지연", color: "orange" },
|
||||
{ field: "pendingCount", label: "완료대기", color: "gray" },
|
||||
] as const;
|
||||
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel } from "@tanstack/react-table";
|
||||
|
||||
import { AS_DASHBOARD_BBS_ID } from "@/shared/config";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { type UserPost, type UserPostSearchParams, useUserPostList } from "@/entities/board";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { dashboardNoticeColumns } from "../model/dashboard-notice-table-columns";
|
||||
|
||||
const DASHBOARD_NOTICE_LIST_PARAMS: UserPostSearchParams = {
|
||||
page: 0,
|
||||
size: 3,
|
||||
keywordType: "bbsTypeCd",
|
||||
keyword: "BBSNOTICE",
|
||||
};
|
||||
|
||||
export function DashboardNoticeTable() {
|
||||
const router = useRouter();
|
||||
const userPostListQuery = useUserPostList(DASHBOARD_NOTICE_LIST_PARAMS);
|
||||
|
||||
const handleRowClick = (post: UserPost) => {
|
||||
if (!AS_DASHBOARD_BBS_ID) return;
|
||||
router.push(`/cms/bbsnotice/${AS_DASHBOARD_BBS_ID}/${post.pstNo}`);
|
||||
};
|
||||
|
||||
const handleMoreClick = () => {
|
||||
if (!AS_DASHBOARD_BBS_ID) return;
|
||||
router.push(`/cms/bbsnotice/${AS_DASHBOARD_BBS_ID}`);
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query: userPostListQuery,
|
||||
tableOptions: {
|
||||
columns: dashboardNoticeColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
variant: "line",
|
||||
onRowClick: handleRowClick,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">공지사항</h3>
|
||||
<Button type="button" size="sm" color="white" onClick={handleMoreClick}>
|
||||
더보기
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { useGetMainDashboard } from "@/entities/dashboard";
|
||||
|
||||
import { DashboardNotiList } from "./dashboard-noti-list";
|
||||
import { DashboardNoticeTable } from "./dashboard-notice-table";
|
||||
import { DocPrgrsSttsCards } from "./doc-prgrs-stts-cards";
|
||||
import { NationalAsMap } from "./national-as-map";
|
||||
|
||||
export function DashboardPage() {
|
||||
const { data, isPending, isError, dataUpdatedAt } = useGetMainDashboard();
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 px-[1.8rem] pb-6 lg:grid-cols-2">
|
||||
<NationalAsMap />
|
||||
<div className="flex min-h-0 flex-col gap-4">
|
||||
<DocPrgrsSttsCards items={data?.docPrgrsSttsList} isPending={isPending} isError={isError} />
|
||||
<DashboardNotiList
|
||||
notiList={data?.notiList}
|
||||
notiStats={data?.notiStats}
|
||||
isPending={isPending}
|
||||
isError={isError}
|
||||
dataUpdatedAt={dataUpdatedAt}
|
||||
/>
|
||||
<DashboardNoticeTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { CircleCheck, FilePlus, type LucideIcon, RefreshCw } from "lucide-react";
|
||||
|
||||
import type { DocPrgrsSttsResponseDto } from "@/entities/dashboard";
|
||||
import { cn, Skeleton } from "@common/components";
|
||||
|
||||
interface DocPrgrsSttsCardsProps {
|
||||
items: DocPrgrsSttsResponseDto[] | undefined;
|
||||
isPending: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
export function DocPrgrsSttsCards({ items, isPending, isError }: DocPrgrsSttsCardsProps) {
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{DOC_PRGRS_STTS_SLOTS.map((slot) => (
|
||||
<Skeleton key={slot.docSeComCd} className="h-[14rem] rounded-xl" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="card card-border mx-0 flex min-h-[14rem] items-center justify-center bg-background text-muted-foreground">
|
||||
진행상황을 불러오지 못했습니다.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const itemByCd = new Map(items?.map((item) => [item.docSeComCd, item]));
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{DOC_PRGRS_STTS_SLOTS.map((slot) => {
|
||||
const item = itemByCd.get(slot.docSeComCd);
|
||||
return (
|
||||
<div
|
||||
key={slot.docSeComCd}
|
||||
className="card card-border hover:bg-muted/40 mx-0 block bg-background transition-colors"
|
||||
>
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">{slot.title}</h3>
|
||||
</div>
|
||||
<Link key={slot.docSeComCd} href={slot.href}>
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
{STAT_METRICS.map((metric) => (
|
||||
<DocPrgrsSttsMetric
|
||||
key={metric.field}
|
||||
label={metric.label}
|
||||
count={item?.[metric.field] ?? 0}
|
||||
iconClassName={metric.iconClassName}
|
||||
bgColor={metric.bgColor}
|
||||
Icon={metric.Icon}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface DocPrgrsSttsMetricProps {
|
||||
label: string;
|
||||
count: number;
|
||||
iconClassName: string;
|
||||
bgColor: string;
|
||||
Icon: LucideIcon;
|
||||
}
|
||||
|
||||
function DocPrgrsSttsMetric({
|
||||
label,
|
||||
count,
|
||||
iconClassName,
|
||||
bgColor,
|
||||
Icon,
|
||||
}: DocPrgrsSttsMetricProps) {
|
||||
return (
|
||||
<div className={cn("card flex flex-1 flex-col items-center gap-2 rounded-2xl p-4", bgColor)}>
|
||||
<span
|
||||
className={cn(
|
||||
"flex size-12 items-center justify-center rounded-full text-white",
|
||||
iconClassName,
|
||||
)}
|
||||
>
|
||||
<Icon className="size-6" />
|
||||
</span>
|
||||
<span className="text-xl text-muted-foreground">{label}</span>
|
||||
<span className="text-3xl font-semibold tabular-nums">{count}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const DOC_PRGRS_STTS_SLOTS = [
|
||||
{ docSeComCd: "FNRS", title: "재원협의 진행상황", href: "/fnrs-cnsltn" },
|
||||
{ docSeComCd: "INSTL", title: "설치인가 진행상황", href: "/instl-sncn" },
|
||||
{ docSeComCd: "CNFM", title: "설계자문 진행상황", href: "/design-cnstn" },
|
||||
{ docSeComCd: "SEWER", title: "기본계획 진행상황", href: "/sewer-bsc-plan" },
|
||||
] as const;
|
||||
|
||||
const STAT_METRICS = [
|
||||
{
|
||||
field: "reqCnt",
|
||||
label: "신청",
|
||||
iconClassName: "bg-blue-500",
|
||||
bgColor: "bg-blue-100",
|
||||
Icon: FilePlus,
|
||||
},
|
||||
{
|
||||
field: "prgrsCnt",
|
||||
label: "진행",
|
||||
iconClassName: "bg-emerald-500",
|
||||
bgColor: "bg-emerald-100",
|
||||
Icon: RefreshCw,
|
||||
},
|
||||
{
|
||||
field: "cmptnCnt",
|
||||
label: "완료",
|
||||
iconClassName: "bg-orange-500",
|
||||
bgColor: "bg-orange-100",
|
||||
Icon: CircleCheck,
|
||||
},
|
||||
] as const;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
export function NationalAsMap() {
|
||||
return (
|
||||
<section className="card card-border mx-0 flex h-full min-h-[40rem] flex-col bg-background">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">전국 행정지원 지도</h3>
|
||||
</div>
|
||||
<div className="flex-1 p-4">
|
||||
<Skeleton className="h-full min-h-[36rem] w-full rounded-xl" />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { MainPage } from "./ui/main-page";
|
||||
@@ -1,9 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export function MainPage() {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center py-10">
|
||||
<h1 className="mb-4 text-3xl font-bold">Home</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export const ENDPOINT = {
|
||||
DESIGN_CNSTN_ESNTL_ATCH_FILE: "/as-service/api/v1/design-cnstn-esntl-atch-file",
|
||||
DESIGN_CNSTN_RVW_OPNN: "/as-service/api/v1/design-cnstn-rvw-opnn",
|
||||
SEWER_BSC_PLAN: "/as-service/api/v1/sewer-bsc-plans",
|
||||
DASHBOARD: "/as-service/api/v1/dashboard",
|
||||
},
|
||||
USER_SERVICE: {
|
||||
LOGIN: "/user-service/login",
|
||||
@@ -46,6 +47,9 @@ export const ENDPOINT = {
|
||||
ST_ANLS_MST: "/statverify-service/api/v1/st-anls-mst",
|
||||
},
|
||||
DC_SERVICE: "/dc-service/api/v1",
|
||||
BOARD_SERVICE: {
|
||||
POSTS: "/board-service/api/v1/posts",
|
||||
},
|
||||
};
|
||||
|
||||
export const JWT_EXPIRATION_TIME = 1800000;
|
||||
|
||||
@@ -9,3 +9,4 @@ export const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-toke
|
||||
export const REFRESH_TOKEN = process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token";
|
||||
export const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
|
||||
export const COOKIE_DOMAIN = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;
|
||||
export const AS_DASHBOARD_BBS_ID = process.env.NEXT_PUBLIC_AS_DASHBOARD_BBS_ID;
|
||||
|
||||
@@ -38,6 +38,11 @@ export function useDataTable<T>({ query, tableOptions, dataTableOptions }: UseDa
|
||||
</div>
|
||||
),
|
||||
Empty: <NoData />,
|
||||
Error: (
|
||||
<p className="py-6 text-center text-lg text-muted-foreground">
|
||||
데이터를 불러오는 중 오류가 발생했습니다.
|
||||
</p>
|
||||
),
|
||||
Success: () => <DataTable table={table} {...dataTableOptions} />,
|
||||
}),
|
||||
tableInstance: table,
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* RealGrid Excel export — applyDynamicStyles가 class background-color를 읽을 수 있도록 */
|
||||
.rg-cell-rowtype-biz-sum {
|
||||
background-color: #ffe9cf !important;
|
||||
}
|
||||
.rg-cell-rowtype-summary {
|
||||
background-color: #feee85 !important;
|
||||
}
|
||||
.rg-cell-rowtype-other {
|
||||
background-color: #fff7bf !important;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
|
||||
@apply !bg-white;
|
||||
@@ -14,6 +25,15 @@
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly {
|
||||
@apply !bg-[#f0f0f0] !text-[#666];
|
||||
}
|
||||
/* 고정 컬럼 — readonly와 동일한 화면 스타일 (엑셀 export 시 exportStyleName에서 제외) */
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-fixed,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-fixed {
|
||||
@apply !bg-[#f0f0f0] !text-[#666];
|
||||
}
|
||||
.rg-grid .rg-fixed-body .rg-table .rg-data-row td.rg-cell-fixed,
|
||||
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-fixed {
|
||||
@apply !bg-[#f0f0f0] !text-[#666];
|
||||
}
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-rowtype-curr {
|
||||
@apply !bg-white !text-[#333];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState, type MutableRefObject } from "react";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState, type MutableRefObject } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import JSZip from "jszip";
|
||||
@@ -32,33 +32,64 @@ import {
|
||||
} from "@/components/util/input/verification-result-display";
|
||||
import {
|
||||
buildExcelExportFileBaseName,
|
||||
buildFixedColumnNameSet,
|
||||
getExcelImportColumnFieldNames,
|
||||
getNonEditableColumnFieldNames,
|
||||
hasAdmdstCdColumn,
|
||||
isExcelImportAdmdstCdInvalid,
|
||||
mergeExcelImportWithExistingRows,
|
||||
prepareGridColumnsForExcelExport,
|
||||
releaseFixedOptionsForExcelExport,
|
||||
restoreFixedOptionsAfterExcelExport,
|
||||
restoreGridColumnsAfterExcelExport,
|
||||
resolveColumnStyleName,
|
||||
shouldPrepareColumnsForExcelExport,
|
||||
stripFixedStylesForExport,
|
||||
} from "@/components/util/gridExcelExport";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
function mergeCellStyleCallbackResult(
|
||||
baseStyleName: string | undefined,
|
||||
extra: CellStyleCallbackResult | undefined,
|
||||
options: {
|
||||
mirrorExportStyle?: boolean;
|
||||
stripFixedForExport?: boolean;
|
||||
} = {},
|
||||
): CellStyleCallbackResult {
|
||||
const { mirrorExportStyle = false, stripFixedForExport = false } = options;
|
||||
|
||||
let styleName: string | undefined;
|
||||
let extraObj: Extract<CellStyleCallbackResult, object> | undefined;
|
||||
|
||||
if (!extra) {
|
||||
return baseStyleName ? { styleName: baseStyleName } : {};
|
||||
styleName = baseStyleName || undefined;
|
||||
} else if (typeof extra === "string") {
|
||||
styleName = [baseStyleName, extra].filter(Boolean).join(" ") || undefined;
|
||||
} else {
|
||||
extraObj = extra;
|
||||
styleName =
|
||||
[baseStyleName, extra.styleName].filter(Boolean).join(" ") ||
|
||||
undefined;
|
||||
}
|
||||
if (typeof extra === "string") {
|
||||
const styleName = [baseStyleName, extra].filter(Boolean).join(" ");
|
||||
return styleName ? { styleName } : {};
|
||||
}
|
||||
const styleName = [baseStyleName, extra.styleName].filter(Boolean).join(" ");
|
||||
return {
|
||||
...extra,
|
||||
|
||||
if (!styleName && !extraObj) return {};
|
||||
|
||||
const result: CellStyleCallbackResult = {
|
||||
...extraObj,
|
||||
...(styleName ? { styleName } : {}),
|
||||
};
|
||||
|
||||
if (typeof result !== "object") return result;
|
||||
|
||||
if (mirrorExportStyle && styleName) {
|
||||
result.exportStyleName = extraObj?.exportStyleName ?? styleName;
|
||||
} else if (stripFixedForExport && styleName) {
|
||||
const stripped = stripFixedStylesForExport(styleName);
|
||||
if (stripped) result.exportStyleName = stripped;
|
||||
else delete result.exportStyleName;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 컬럼 footer.styleName 또는 footerVerificationResult(fieldName) → footer styleName */
|
||||
@@ -159,19 +190,26 @@ function applyCellStyleCallbacksToColumns(
|
||||
columns: ColumnConfig[],
|
||||
rowDataRef: MutableRefObject<Record<string, string | number>[]>,
|
||||
cellStyleCallbackRef: MutableRefObject<CellStyleCallback | undefined>,
|
||||
excelExportApplyStylesRef: MutableRefObject<boolean>,
|
||||
fixedColumnNameSet: Set<string>,
|
||||
) {
|
||||
if (!cellStyleCallbackRef.current || !gridView) return;
|
||||
if (!gridView) return;
|
||||
|
||||
const gv = gridView as {
|
||||
columnByName?: (name: string) => GridColumnWithStyleCallback | null | undefined;
|
||||
};
|
||||
if (typeof gv.columnByName !== "function") return;
|
||||
|
||||
const hasUserCellStyleCallback = Boolean(cellStyleCallbackRef.current);
|
||||
|
||||
columns.forEach((column) => {
|
||||
const baseStyleName = column.styleName;
|
||||
const isFixedColumn = fixedColumnNameSet.has(column.name);
|
||||
// 고정 컬럼은 styleCallback 필수(고정 pane에 column styleName만으로는 클래스 미반영)
|
||||
if (!isFixedColumn && !hasUserCellStyleCallback) return;
|
||||
|
||||
const baseStyleName = resolveColumnStyleName(column, fixedColumnNameSet);
|
||||
let gridCol: GridColumnWithStyleCallback | null | undefined;
|
||||
try {
|
||||
// columnByName은 GridView 인스턴스 메서드 — this 바인딩을 위해 gv에서 직접 호출
|
||||
gridCol = gv.columnByName!(column.name);
|
||||
} catch {
|
||||
return;
|
||||
@@ -184,17 +222,32 @@ function applyCellStyleCallbacksToColumns(
|
||||
const fieldName =
|
||||
dataCell?.dataField ?? dataCell?.fieldName ?? column.fieldName;
|
||||
const data = rowDataRef.current;
|
||||
const styleOptions = {
|
||||
mirrorExportStyle: excelExportApplyStylesRef.current,
|
||||
stripFixedForExport:
|
||||
isFixedColumn && !excelExportApplyStylesRef.current,
|
||||
};
|
||||
if (itemIndex < 0 || itemIndex >= data.length) {
|
||||
return mergeCellStyleCallbackResult(baseStyleName, undefined);
|
||||
return mergeCellStyleCallbackResult(
|
||||
baseStyleName,
|
||||
undefined,
|
||||
styleOptions,
|
||||
);
|
||||
}
|
||||
const row = data[itemIndex] as Record<string, string | number>;
|
||||
const extra = cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
});
|
||||
return mergeCellStyleCallbackResult(baseStyleName, extra);
|
||||
const extra = hasUserCellStyleCallback
|
||||
? cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
})
|
||||
: undefined;
|
||||
return mergeCellStyleCallbackResult(
|
||||
baseStyleName,
|
||||
extra,
|
||||
styleOptions,
|
||||
);
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -815,6 +868,7 @@ const DataGrid = ({
|
||||
rowStartNum,
|
||||
excelExportFileSuffix,
|
||||
excelExportSearchContext,
|
||||
excelExportApplyStyles = false,
|
||||
isExcelImport = true,
|
||||
excelImportSkipNonEditable = false,
|
||||
excelImportSewerBzmnCd,
|
||||
@@ -844,6 +898,10 @@ const DataGrid = ({
|
||||
fitStyle = GridFitStyle.EVEN,
|
||||
}: DataGridProps) => {
|
||||
const { columns, columnLayout } = columnInfo;
|
||||
const fixedColumnNameSet = useMemo(
|
||||
() => buildFixedColumnNameSet(columns, columnLayout, fixedColumns),
|
||||
[columns, columnLayout, fixedColumns],
|
||||
);
|
||||
const gridRef = useRef<RealGridReact>(null);
|
||||
const gridHostRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -856,6 +914,7 @@ const DataGrid = ({
|
||||
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(
|
||||
cellStyleCallback,
|
||||
);
|
||||
const excelExportApplyStylesRef = useRef(excelExportApplyStyles);
|
||||
const cellTooltipCallbackRef = useRef<CellTooltipCallback | undefined>(
|
||||
cellTooltipCallback,
|
||||
);
|
||||
@@ -885,6 +944,7 @@ const DataGrid = ({
|
||||
columnsRef.current = columns;
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
excelExportApplyStylesRef.current = excelExportApplyStyles;
|
||||
cellTooltipCallbackRef.current = cellTooltipCallback;
|
||||
footerVerificationResultRef.current = footerVerificationResult;
|
||||
onScrollToBottomRef.current = onScrollToBottom;
|
||||
@@ -1039,7 +1099,8 @@ useEffect(() => {
|
||||
}, [onCellDblClick]);
|
||||
useLayoutEffect(() => {
|
||||
const gv = gridRef.current?.gridView;
|
||||
if (!gv || !cellStyleCallback) return;
|
||||
if (!gv) return;
|
||||
if (!cellStyleCallback && fixedColumnNameSet.size === 0) return;
|
||||
|
||||
const apply = () => {
|
||||
applyCellStyleCallbacksToColumns(
|
||||
@@ -1047,6 +1108,8 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
(gv as { refresh?: () => void }).refresh?.();
|
||||
};
|
||||
@@ -1058,7 +1121,7 @@ useEffect(() => {
|
||||
return () => {
|
||||
window.clearTimeout(timerId);
|
||||
};
|
||||
}, [cellStyleCallback, columns, rowData]);
|
||||
}, [cellStyleCallback, columns, rowData, excelExportApplyStyles, fixedColumnNameSet, fixedColumns]);
|
||||
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const activeTabId = useTabStore((state) => state.activeTabId);
|
||||
@@ -1156,6 +1219,10 @@ useEffect(() => {
|
||||
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
|
||||
? prepareGridColumnsForExcelExport(gridView, columns)
|
||||
: undefined;
|
||||
const savedFixedColCount =
|
||||
!excelExportApplyStyles && fixedColumns
|
||||
? releaseFixedOptionsForExcelExport(gridView, fixedColumns)
|
||||
: undefined;
|
||||
|
||||
const exportBaseName = buildExcelExportFileBaseName(
|
||||
menuName,
|
||||
@@ -1169,7 +1236,14 @@ useEffect(() => {
|
||||
fileName: `${exportBaseName}.xlsx`,
|
||||
footer: "hidden",
|
||||
allColumns: !useExcelColumnVisibilityPrep,
|
||||
...(excelExportApplyStyles ? { applyDynamicStyles: true } : {}),
|
||||
done: () => {
|
||||
if (savedFixedColCount !== undefined) {
|
||||
restoreFixedOptionsAfterExcelExport(
|
||||
gridView,
|
||||
savedFixedColCount,
|
||||
);
|
||||
}
|
||||
if (excelExportVisibilitySnapshot) {
|
||||
restoreGridColumnsAfterExcelExport(
|
||||
gridView,
|
||||
@@ -1187,7 +1261,7 @@ useEffect(() => {
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
alert("내보내기가 완료되었습니다.");
|
||||
// alert("내보내기가 완료되었습니다.");
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1515,8 +1589,17 @@ useEffect(() => {
|
||||
gv.setColumnProperty?.(
|
||||
column.name,
|
||||
"styleName",
|
||||
column.styleName ?? "",
|
||||
resolveColumnStyleName(column, fixedColumnNameSet),
|
||||
);
|
||||
if (fixedColumnNameSet.has(column.name) && !excelExportApplyStyles) {
|
||||
gv.setColumnProperty?.(
|
||||
column.name,
|
||||
"exportStyleName",
|
||||
stripFixedStylesForExport(
|
||||
resolveColumnStyleName(column, fixedColumnNameSet),
|
||||
) || "",
|
||||
);
|
||||
}
|
||||
applyColumnListEditorProperties(gv, column);
|
||||
});
|
||||
if (columns.some((c) => isDropDownEditorColumn(c))) {
|
||||
@@ -1705,6 +1788,8 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
gridRef.current.gridView.refresh?.();
|
||||
} catch (e) {
|
||||
@@ -1794,6 +1879,8 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
|
||||
// setColumns 호출 후 footer 배열의 valueCallback을 다시 설정
|
||||
@@ -1847,6 +1934,8 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
liveGridView.refresh?.();
|
||||
}, 50);
|
||||
@@ -1930,6 +2019,8 @@ useEffect(() => {
|
||||
columns,
|
||||
columnLayout,
|
||||
fixedColumns,
|
||||
fixedColumnNameSet,
|
||||
excelExportApplyStyles,
|
||||
rowData,
|
||||
showHeaderCheckbox,
|
||||
getGridDataRef,
|
||||
@@ -2732,13 +2823,18 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
gv.refresh();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
{columns.map((column) => {
|
||||
const baseStyleName = column.styleName;
|
||||
const baseStyleName = resolveColumnStyleName(
|
||||
column,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
const numberFormat =
|
||||
column.dataFormat && column.dataFormat.trim() !== ""
|
||||
? column.dataFormat
|
||||
|
||||
@@ -73,6 +73,21 @@ function buildSearchFormValues(
|
||||
};
|
||||
}
|
||||
|
||||
if (props.defaultRegionAll) {
|
||||
return {
|
||||
sido: "ALL",
|
||||
gugun: "ALL",
|
||||
year: d?.year || defaultYear.toString(),
|
||||
order: d?.order || "",
|
||||
fctName: d?.fctName || "",
|
||||
bsnmCd: "",
|
||||
isDetail: false,
|
||||
isSummary: d?.isSummary || "",
|
||||
clsfCd: initialClsfCd,
|
||||
capacityGbn: d?.capacityGbn || "ALL",
|
||||
};
|
||||
}
|
||||
|
||||
const first = sidoList?.data?.sidoList?.[0];
|
||||
const useFirstSido = (props.sido ?? defaultSearchFormProps.sido) && first;
|
||||
|
||||
@@ -132,6 +147,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
|
||||
// 최초 진입만 시도 API가 늦게 오는 경우
|
||||
useEffect(() => {
|
||||
if (props.defaultRegionAll) return;
|
||||
if (props.defaultValues?.sido) return;
|
||||
const first = sidoList?.data?.sidoList?.[0];
|
||||
if (!first || !mergedProps.sido) return;
|
||||
@@ -145,7 +161,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
bsnmCd: first.bsnmCd,
|
||||
},
|
||||
);
|
||||
}, [sidoList, props.defaultValues?.sido, mergedProps.sido]);
|
||||
}, [sidoList, props.defaultValues?.sido, props.defaultRegionAll, mergedProps.sido]);
|
||||
|
||||
const sggKey: [string, string, string, string] | null =
|
||||
values.sido && values.sido !== "ALL"
|
||||
@@ -177,6 +193,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
// 시도 설정 후 시군구 목록이 오면 첫 번째 항목 자동 선택
|
||||
useEffect(() => {
|
||||
if (!mergedProps.gugun) return;
|
||||
if (props.defaultRegionAll) return;
|
||||
if (props.defaultValues?.gugun) return;
|
||||
if (!values.sido || values.sido === "ALL") return;
|
||||
|
||||
@@ -196,6 +213,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
values.sido,
|
||||
mergedProps.gugun,
|
||||
props.defaultValues?.gugun,
|
||||
props.defaultRegionAll,
|
||||
]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -210,11 +228,13 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
|
||||
const handleSidoChange = (value: string) => {
|
||||
const selectedBsnm =
|
||||
sidoList?.data?.sidoList?.find((v) => v.sido === value)?.bsnmCd || "";
|
||||
value === "ALL"
|
||||
? ""
|
||||
: sidoList?.data?.sidoList?.find((v) => v.sido === value)?.bsnmCd || "";
|
||||
setValues((prev) => ({
|
||||
...prev,
|
||||
sido: value,
|
||||
gugun: "",
|
||||
gugun: props.defaultRegionAll || value === "ALL" ? "ALL" : "",
|
||||
bsnmCd: selectedBsnm,
|
||||
}));
|
||||
};
|
||||
@@ -403,7 +423,8 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
)}
|
||||
{mergedProps.isSummary && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label cursor-pointer">
|
||||
<Label className="text-label cursor-pointer" title="시·군·구 및 읍·면·동 변경에 따라 인구 및 관로 등 공표된 통계수치와 일부 상이할 수 있으니 유의 바랍니다.">
|
||||
<i className="diveicon di-exclamation-circle mr-1" />
|
||||
요약표 포함
|
||||
</Label>
|
||||
<Select
|
||||
|
||||
@@ -17,33 +17,55 @@ import GridTopActionButton from "./GridTopActionButton";
|
||||
function mergeCellStyleCallbackResult(
|
||||
baseStyleName: string | undefined,
|
||||
extra: CellStyleCallbackResult | undefined,
|
||||
stripFixedForExport = false,
|
||||
): CellStyleCallbackResult {
|
||||
let styleName: string | undefined;
|
||||
let extraObj: Extract<CellStyleCallbackResult, object> | undefined;
|
||||
|
||||
if (!extra) {
|
||||
return baseStyleName ? { styleName: baseStyleName } : {};
|
||||
styleName = baseStyleName || undefined;
|
||||
} else if (typeof extra === "string") {
|
||||
styleName = [extra, baseStyleName].filter(Boolean).join(" ") || undefined;
|
||||
} else {
|
||||
extraObj = extra;
|
||||
styleName =
|
||||
[extra.styleName, baseStyleName].filter(Boolean).join(" ") ||
|
||||
undefined;
|
||||
}
|
||||
if (typeof extra === "string") {
|
||||
const styleName = [extra, baseStyleName].filter(Boolean).join(" ");
|
||||
return styleName ? { styleName } : {};
|
||||
}
|
||||
const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" ");
|
||||
return {
|
||||
...extra,
|
||||
|
||||
if (!styleName && !extraObj) return {};
|
||||
|
||||
const result: CellStyleCallbackResult = {
|
||||
...extraObj,
|
||||
...(styleName ? { styleName } : {}),
|
||||
};
|
||||
|
||||
if (typeof result === "object" && stripFixedForExport && styleName) {
|
||||
const stripped = stripFixedStylesForExport(styleName);
|
||||
if (stripped) result.exportStyleName = stripped;
|
||||
else delete result.exportStyleName;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
import SplitDataGrid from "./SplitDataGrid";
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
|
||||
import {
|
||||
buildExcelExportFileBaseName,
|
||||
buildFixedColumnNameSet,
|
||||
getExcelImportColumnFieldNames,
|
||||
getNonEditableColumnFieldNames,
|
||||
hasAdmdstCdColumn,
|
||||
isExcelImportAdmdstCdInvalid,
|
||||
mergeExcelImportWithExistingRows,
|
||||
prepareGridColumnsForExcelExport,
|
||||
releaseFixedOptionsForExcelExport,
|
||||
restoreFixedOptionsAfterExcelExport,
|
||||
restoreGridColumnsAfterExcelExport,
|
||||
resolveColumnStyleName,
|
||||
shouldPrepareColumnsForExcelExport,
|
||||
stripFixedStylesForExport,
|
||||
} from "@/components/util/gridExcelExport"
|
||||
import { useTabStore } from "@/store/tab-store"
|
||||
|
||||
@@ -313,17 +335,24 @@ const TabDataGrid = ({
|
||||
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(undefined);
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
|
||||
const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], attempt = 0) => {
|
||||
if (!cellStyleCallbackRef.current) return;
|
||||
const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], columnLayout: ColumnLayoutItem[] | undefined, fixedCols: number | undefined, attempt = 0) => {
|
||||
const el = gridRefs.current.get(tabKey);
|
||||
const gv = el?.gridView;
|
||||
if (!gv) return;
|
||||
|
||||
const fixedColumnNameSet = buildFixedColumnNameSet(columns, columnLayout, fixedCols);
|
||||
const hasUserCellStyleCallback = Boolean(cellStyleCallbackRef.current);
|
||||
if (fixedColumnNameSet.size === 0 && !hasUserCellStyleCallback) return;
|
||||
|
||||
let attachedCount = 0;
|
||||
for (const column of columns) {
|
||||
const isFixedColumn = fixedColumnNameSet.has(column.name);
|
||||
if (!isFixedColumn && !hasUserCellStyleCallback) continue;
|
||||
|
||||
const gridCol = safeColumnByName(gv, column.name);
|
||||
if (!gridCol) continue;
|
||||
const baseStyleName = column.styleName;
|
||||
const baseStyleName = resolveColumnStyleName(column, fixedColumnNameSet);
|
||||
const stripFixed = isFixedColumn;
|
||||
gridCol.styleCallback = (grid, dataCell) => {
|
||||
const dc = dataCell as {
|
||||
itemIndex?: number;
|
||||
@@ -335,22 +364,24 @@ const TabDataGrid = ({
|
||||
const fieldName = dc?.dataField ?? dc?.fieldName ?? column.fieldName;
|
||||
const data = rowDataMapRef.current.get(tabKey) || [];
|
||||
if (itemIndex < 0 || itemIndex >= data.length) {
|
||||
return mergeCellStyleCallbackResult(baseStyleName, undefined);
|
||||
return mergeCellStyleCallbackResult(baseStyleName, undefined, stripFixed);
|
||||
}
|
||||
const row = data[itemIndex] as Record<string, string | number>;
|
||||
const extra = cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
});
|
||||
return mergeCellStyleCallbackResult(baseStyleName, extra);
|
||||
const extra = hasUserCellStyleCallback
|
||||
? cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
})
|
||||
: undefined;
|
||||
return mergeCellStyleCallbackResult(baseStyleName, extra, stripFixed);
|
||||
};
|
||||
attachedCount += 1;
|
||||
}
|
||||
|
||||
if (attachedCount === 0 && attempt < 20) {
|
||||
setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, attempt + 1), 100);
|
||||
setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, columnLayout, fixedCols, attempt + 1), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -459,6 +490,12 @@ const TabDataGrid = ({
|
||||
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
|
||||
? prepareGridColumnsForExcelExport(gridView, columns)
|
||||
: undefined;
|
||||
const savedFixedColCount = columnInfoItem.fixedColumns
|
||||
? releaseFixedOptionsForExcelExport(
|
||||
gridView,
|
||||
columnInfoItem.fixedColumns,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const exportBaseName = buildExcelExportFileBaseName(
|
||||
menuName,
|
||||
@@ -473,6 +510,12 @@ const TabDataGrid = ({
|
||||
footer: 'hidden',
|
||||
allColumns: !useExcelColumnVisibilityPrep,
|
||||
done: () => {
|
||||
if (savedFixedColCount !== undefined) {
|
||||
restoreFixedOptionsAfterExcelExport(
|
||||
gridView,
|
||||
savedFixedColCount,
|
||||
);
|
||||
}
|
||||
if (excelExportVisibilitySnapshot) {
|
||||
restoreGridColumnsAfterExcelExport(gridView, excelExportVisibilitySnapshot);
|
||||
}
|
||||
@@ -968,6 +1011,11 @@ const TabDataGrid = ({
|
||||
return null;
|
||||
}
|
||||
const { columns, columnLayout } = info;
|
||||
const fixedColumnNameSet = buildFixedColumnNameSet(
|
||||
columns,
|
||||
columnLayout,
|
||||
info.fixedColumns,
|
||||
);
|
||||
const currentRowData = rowDataMap.get(tabKey) || [];
|
||||
|
||||
return (
|
||||
@@ -1192,6 +1240,32 @@ const TabDataGrid = ({
|
||||
});
|
||||
}
|
||||
|
||||
const tabFixedSet = buildFixedColumnNameSet(
|
||||
columns,
|
||||
currentInfo.columnLayout,
|
||||
currentInfo.fixedColumns,
|
||||
);
|
||||
columns.forEach((col) => {
|
||||
const styleName = resolveColumnStyleName(
|
||||
col,
|
||||
tabFixedSet,
|
||||
);
|
||||
gridView.setColumnProperty?.(
|
||||
col.name,
|
||||
"styleName",
|
||||
styleName,
|
||||
);
|
||||
if (tabFixedSet.has(col.name)) {
|
||||
gridView.setColumnProperty?.(
|
||||
col.name,
|
||||
"exportStyleName",
|
||||
stripFixedStylesForExport(
|
||||
styleName,
|
||||
) || "",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 각 탭별 ref 초기화
|
||||
if (!sortDirRefs.current.has(tabKey)) {
|
||||
sortDirRefs.current.set(tabKey, { current: {} });
|
||||
@@ -1247,9 +1321,17 @@ const TabDataGrid = ({
|
||||
const onSortingHandler = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef, findGridElement);
|
||||
(gridView as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = enableSort ? onSortingHandler : () => false;
|
||||
|
||||
if (cellStyleCallbackRef.current) {
|
||||
if (
|
||||
cellStyleCallbackRef.current ||
|
||||
tabFixedSet.size > 0
|
||||
) {
|
||||
setTimeout(() => {
|
||||
attachCellStyleCallbacksForTab(tabKey, columns);
|
||||
attachCellStyleCallbacksForTab(
|
||||
tabKey,
|
||||
columns,
|
||||
currentInfo.columnLayout,
|
||||
currentInfo.fixedColumns,
|
||||
);
|
||||
}, 100);
|
||||
}
|
||||
}, 150);
|
||||
@@ -1262,7 +1344,16 @@ const TabDataGrid = ({
|
||||
layout={columnLayout}
|
||||
rows={currentRowData}
|
||||
>
|
||||
{columns.map((column) => (
|
||||
{columns.map((column) => {
|
||||
const resolvedStyleName =
|
||||
resolveColumnStyleName(
|
||||
column,
|
||||
fixedColumnNameSet,
|
||||
) ||
|
||||
(column.editable === false
|
||||
? "rg-cell-readonly"
|
||||
: undefined);
|
||||
return (
|
||||
<RGDataColumn
|
||||
key={column.name}
|
||||
name={column.name}
|
||||
@@ -1271,11 +1362,9 @@ const TabDataGrid = ({
|
||||
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.00' })}
|
||||
header={column.header}
|
||||
editable={column.editable}
|
||||
{...(column.styleName != null
|
||||
? { styleName: column.styleName }
|
||||
: column.editable === false
|
||||
? { styleName: 'rg-cell-readonly' as const }
|
||||
: {})}
|
||||
{...(resolvedStyleName != null
|
||||
? { styleName: resolvedStyleName }
|
||||
: {})}
|
||||
{...(column.visible === false && { visible: false })}
|
||||
footer={column.footer && !Array.isArray(column.footer) ? {
|
||||
text: (column.footer as { text?: string }).text,
|
||||
@@ -1285,7 +1374,8 @@ const TabDataGrid = ({
|
||||
mergeRule={column.mergeCells ? { criteria: column.mergeGroupField ? `values['${column.mergeGroupField}'] + value` : 'value' } : undefined}
|
||||
sortable={enableSort && !column.sortdisable}
|
||||
/>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</RealGridReact>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1022,7 +1022,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="card !py-0">
|
||||
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
@@ -1033,18 +1033,18 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
</>
|
||||
)}
|
||||
{statusInfo?.laeSbmsnSttsCd === "2" && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showArticleToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="card !py-0">
|
||||
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
||||
{showNotApplicable && (
|
||||
<NotApplicableCheckButton
|
||||
@@ -1052,10 +1052,10 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
setIsCheck={setIsNotApplicable}
|
||||
/>
|
||||
)}
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</div>
|
||||
</div>
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10 [data-theme='black']:bg-transparent">
|
||||
@@ -1064,14 +1064,14 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DataGrid
|
||||
<DataGrid
|
||||
key={canEditGrid ? "enterable" : "readonly"}
|
||||
getGridRef={gridRef as RefObject<RealGridReact | null>}
|
||||
columnInfo={columnInfo}
|
||||
rowStartNum={ExcretaTreatFactInpColumn.rowStartNum}
|
||||
fixedColumns={ExcretaTreatFactInpColumn.fixedColumns}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
getGridDataRef={gridGetRowsRef}
|
||||
showHeaderCheckbox
|
||||
statusInfo={statusInfo}
|
||||
|
||||
@@ -77,10 +77,10 @@ import { getUserInfo } from "../../../../../../packages/common/components/src/ap
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
const SewageExcretaRelatedInpTabList = [
|
||||
PublicInstitutionSewerIndustryInpColumn,
|
||||
PublicInstitutionExcretaIndustryInpColumn,
|
||||
ExcretaRelatedIndustryInpColumn,
|
||||
const SewageExcretaRelatedInpTabList = [
|
||||
PublicInstitutionSewerIndustryInpColumn,
|
||||
PublicInstitutionExcretaIndustryInpColumn,
|
||||
ExcretaRelatedIndustryInpColumn,
|
||||
PrivateSewageTreatRelatedIndustryInpColumn,
|
||||
] as const;
|
||||
|
||||
@@ -1967,8 +1967,8 @@ const SewageExcretaRelatedInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -1980,19 +1980,19 @@ const SewageExcretaRelatedInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void lastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showSecondaryToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showNotApplicableForActiveTab && (
|
||||
<NotApplicableCheckButton
|
||||
isCheck={isNotApplicableForActiveTab}
|
||||
@@ -2002,11 +2002,11 @@ const SewageExcretaRelatedInp = () => {
|
||||
{showArticleEditButtons && (
|
||||
<>
|
||||
<AddArticleButton addArticleFunc={() => void addArticleFunc()} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -2126,7 +2126,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
false,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -181,10 +181,10 @@ function mapListToGridRows<T extends { verificationResult?: SewageExcretaSlurgeV
|
||||
});
|
||||
}
|
||||
|
||||
const SewageExcretaSlurgeInpTabList = [
|
||||
SlurgeProcessingFacStatusInpColumn,
|
||||
DigestorOperatingStatusInpColumn,
|
||||
SewageSlurgeProcessingFacStatusInpColumn,
|
||||
const SewageExcretaSlurgeInpTabList = [
|
||||
SlurgeProcessingFacStatusInpColumn,
|
||||
DigestorOperatingStatusInpColumn,
|
||||
SewageSlurgeProcessingFacStatusInpColumn,
|
||||
ExcretaSlurgeProcessingStatusInpColumn,
|
||||
] as const;
|
||||
|
||||
@@ -2071,8 +2071,8 @@ const SewageExcretaSlurgeInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -2084,19 +2084,19 @@ const SewageExcretaSlurgeInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void lastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showSecondaryToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showNotApplicableForActiveTab && (
|
||||
<NotApplicableCheckButton
|
||||
isCheck={isNotApplicableForActiveTab}
|
||||
@@ -2105,12 +2105,12 @@ const SewageExcretaSlurgeInp = () => {
|
||||
)}
|
||||
{showArticleEditButtons && (
|
||||
<>
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
|
||||
@@ -217,6 +217,18 @@ const getManholeLastDataYear = () => {
|
||||
return Math.max(2014, lastDataYear);
|
||||
};
|
||||
|
||||
/** 2-3 관종별: verificationResult.prlgWhol → 총계 계(pipeByPrstWhol) */
|
||||
function remapPipeTypePrlgWholVerification(
|
||||
result?: SeweragePipeInputVerificationResult | null,
|
||||
): SeweragePipeInputVerificationResult | null | undefined {
|
||||
if (!result) return result;
|
||||
const fromAlias = result.prlgWhol;
|
||||
if (!Array.isArray(fromAlias) || fromAlias.length === 0) return result;
|
||||
const existing = result.pipeByPrstWhol;
|
||||
if (Array.isArray(existing) && existing.length > 0) return result;
|
||||
return { ...result, pipeByPrstWhol: fromAlias };
|
||||
}
|
||||
|
||||
/** 관종별 신 응답(yearlyStatList) -> 그리드 y{year}* 필드로 정규화 */
|
||||
function normalizePipeTypeStatusRow(
|
||||
row: SeweragePipeInputPipeTypeStatusItem,
|
||||
@@ -227,6 +239,7 @@ function normalizePipeTypeStatusRow(
|
||||
yearlyStatList,
|
||||
lyngPrlgWhol,
|
||||
dmltnPrlgWhol,
|
||||
verificationResult,
|
||||
...rest
|
||||
} = row;
|
||||
const yearFields: Record<string, number> = {};
|
||||
@@ -254,6 +267,7 @@ function normalizePipeTypeStatusRow(
|
||||
lyngPrlg: Number(row.lyngPrlg ?? lyngPrlgWhol ?? 0),
|
||||
dmltnPrlg: Number(row.dmltnPrlg ?? dmltnPrlgWhol ?? 0),
|
||||
...yearFields,
|
||||
verificationResult: remapPipeTypePrlgWholVerification(verificationResult) ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -787,7 +801,11 @@ const SewagePipeInp = () => {
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
patchFooterVerificationByTab(prev, tabKey, {
|
||||
verificationResult:
|
||||
remapPipeTypePrlgWholVerification(commonInfo.verificationResult) ??
|
||||
null,
|
||||
}),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
|
||||
@@ -1183,6 +1183,7 @@ const SewagePipeMaintainInp = () => {
|
||||
toGridColumnInfo(info)
|
||||
}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
fixedColumns={info.fixedColumns}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
|
||||
@@ -1258,8 +1258,8 @@ const SewerageFinanceInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -1271,7 +1271,7 @@ const SewerageFinanceInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void lastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
|
||||
@@ -1143,8 +1143,8 @@ const SewerageServiceLevelInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -1156,19 +1156,19 @@ const SewerageServiceLevelInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showSecondaryToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showNotApplicableForResidentTab && (
|
||||
<NotApplicableCheckButton
|
||||
isCheck={isNotApplicable}
|
||||
|
||||
@@ -88,11 +88,11 @@ import { getUserInfo } from "../../../../../../packages/common/components/src/ap
|
||||
|
||||
import SubmitDailog from "./SubmitDailog";
|
||||
|
||||
const WaterReuseInpTabList = [
|
||||
PublicSewageTreatWaterReuseInpColumn,
|
||||
RecycledWaterStatusInpColumn,
|
||||
RainFallUtilFactStatusInpColumn,
|
||||
TreatWaterReuseDesignConstStatusInpColumn,
|
||||
const WaterReuseInpTabList = [
|
||||
PublicSewageTreatWaterReuseInpColumn,
|
||||
RecycledWaterStatusInpColumn,
|
||||
RainFallUtilFactStatusInpColumn,
|
||||
TreatWaterReuseDesignConstStatusInpColumn,
|
||||
TreatWaterReuseBusinessApprStatusInpColumn,
|
||||
] as const;
|
||||
|
||||
@@ -2089,8 +2089,8 @@ const WaterReuseInp = () => {
|
||||
onValuesChange={setSearchValues}
|
||||
/>
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
@@ -2102,19 +2102,19 @@ const WaterReuseInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
||||
)}
|
||||
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showSecondaryToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
{showNotApplicableForActiveTab && (
|
||||
<NotApplicableCheckButton
|
||||
isCheck={isNotApplicableForActiveTab}
|
||||
@@ -2128,12 +2128,12 @@ const WaterReuseInp = () => {
|
||||
)}
|
||||
{isArticleEditTab && showEnterableToolbarButtons && (
|
||||
<>
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
<AddArticleButton addArticleFunc={addArticleFunc} />
|
||||
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
|
||||
@@ -31,7 +31,7 @@ import type {
|
||||
ColumnLayoutItem,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
|
||||
import { SewagePipeColumnInfoListRvw, getST00020003RvwConfig, getST00020005RvwConfig } from "@/components/util/column/review/SewagePipeRvwColumn";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
@@ -148,15 +148,25 @@ const SewagePipeRvw = () => {
|
||||
[tabStatsStatusInfoMap, activeTab],
|
||||
);
|
||||
const columnInfo = useMemo<ColumnInfoList[]>(
|
||||
() =>
|
||||
SewagePipeColumnInfoListRvw.map((tab) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col) => cloneColumnConfig(col)),
|
||||
columnLayout: (tab.columnLayout ?? []).map((item) =>
|
||||
cloneColumnLayoutItem(item),
|
||||
),
|
||||
})),
|
||||
[],
|
||||
() => {
|
||||
const statsYr = lastSearchValues?.year ?? new Date().getFullYear();
|
||||
const st00020003Config = getST00020003RvwConfig(statsYr);
|
||||
const st00020005Config = getST00020005RvwConfig(statsYr);
|
||||
return SewagePipeColumnInfoListRvw.map((tab) => {
|
||||
const source =
|
||||
tab.tabKey === "ST00020003" ? st00020003Config
|
||||
: tab.tabKey === "ST00020005" ? st00020005Config
|
||||
: tab;
|
||||
return {
|
||||
...source,
|
||||
columns: (source.columns ?? []).map((col) => cloneColumnConfig(col as Parameters<typeof cloneColumnConfig>[0])),
|
||||
columnLayout: (source.columnLayout ?? []).map((item) =>
|
||||
cloneColumnLayoutItem(item as ColumnLayoutItem),
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
[lastSearchValues?.year],
|
||||
);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
|
||||
@@ -84,7 +84,7 @@ const ExcretaTreatmentFacilityTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -105,6 +105,7 @@ const ExcretaTreatmentFacilityTot = () => {
|
||||
setRowData={setRowData} // 필수 프로퍼티 에러 해결
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={config.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -132,7 +132,7 @@ const PrivateSewageFacilityTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{/* 하수관로 규칙과 100% 싱크 맞춘 중앙 정렬 소형 팝업 로딩 바 고정 */}
|
||||
{isLoading && (
|
||||
@@ -184,6 +184,7 @@ const PrivateSewageFacilityTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -52,7 +52,7 @@ const PublicSewageFacilityTot = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm isDetail={true} capacityGbn={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} capacityGbn={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" defaultRegionAll />
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
@@ -65,6 +65,7 @@ const PublicSewageFacilityTot = () => {
|
||||
columnInfo={PublicSewageFacilityColumn}
|
||||
rowStartNum={2}
|
||||
isExcelImport={false}
|
||||
excelExportApplyStyles
|
||||
fixedColumns={2}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
|
||||
@@ -84,7 +84,7 @@ const PumpingStationTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -105,6 +105,7 @@ const PumpingStationTot = () => {
|
||||
setRowData={setRowData}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={config.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -102,7 +102,7 @@ const SewageExcretaRelatedTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -140,6 +140,7 @@ const SewageExcretaRelatedTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -146,7 +146,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -188,6 +188,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -133,7 +133,7 @@ const SewagePipeMaintainTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
@@ -186,6 +186,7 @@ const SewagePipeMaintainTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -8,7 +8,7 @@ import SearchForm from "@/components/module/SearchForm";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListTot, getST00020003Config } from "@/components/util/column/total/SewagePipeTotColumn";
|
||||
import { SewagePipeColumnInfoListTot, getST00020003Config, getST00020005Config } from "@/components/util/column/total/SewagePipeTotColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import type { SewagePipeTotalRequest } from "@/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface";
|
||||
import CellColorInfo from "./CellColorInfo";
|
||||
@@ -178,7 +178,7 @@ const SewagePipeTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
@@ -204,11 +204,12 @@ const SewagePipeTot = () => {
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
|
||||
// ★ [동적 처리] ST00020003인 경우 함수를 통해 컬럼 정보를 실시간으로 생성
|
||||
// ★ [동적 처리] ST00020003·ST00020005는 함수를 통해 컬럼 정보를 실시간으로 생성
|
||||
const currentYear = lastSearchedValuesRef.current?.year || new Date().getFullYear();
|
||||
const finalTabConfig = tabKey === "ST00020003"
|
||||
? getST00020003Config(currentYear)
|
||||
: info;
|
||||
const finalTabConfig =
|
||||
tabKey === "ST00020003" ? getST00020003Config(currentYear) :
|
||||
tabKey === "ST00020005" ? getST00020005Config(currentYear) :
|
||||
info;
|
||||
|
||||
if (!finalTabConfig.columns || !finalTabConfig.columnLayout) {
|
||||
return null;
|
||||
@@ -239,6 +240,7 @@ const SewagePipeTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={finalTabConfig.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -118,7 +118,7 @@ const SewageReservoirPumpTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -168,6 +168,7 @@ const SewageReservoirPumpTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -199,6 +199,7 @@ const SewerageAggregateSummary = () => {
|
||||
<SearchForm
|
||||
isSummary={true}
|
||||
sidoSelectText="전체"
|
||||
defaultRegionAll
|
||||
searchButtonText="내려받기"
|
||||
onSubmit={(values) => void handleDownload(values)}
|
||||
isReset={false}
|
||||
|
||||
@@ -144,7 +144,7 @@ const SewerageFinanceTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -195,6 +195,7 @@ const SewerageFinanceTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -51,7 +51,7 @@ const SeweragePenetrationRateTot = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" defaultRegionAll />
|
||||
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
@@ -62,6 +62,7 @@ const SeweragePenetrationRateTot = () => {
|
||||
columnInfo={SeweragePentrationRateTotColumn}
|
||||
rowStartNum={2}
|
||||
isExcelImport={false}
|
||||
excelExportApplyStyles
|
||||
fixedColumns={1}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
|
||||
@@ -91,7 +91,7 @@ const SewerageServiceLevelTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
@@ -129,6 +129,7 @@ const SewerageServiceLevelTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
@@ -157,7 +157,7 @@ const WaterReuseTot = () => {
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
|
||||
|
||||
{/* 정중앙 5rem 크기 고정 격리형 로딩 애니메이션 */}
|
||||
{isLoading && (
|
||||
@@ -207,6 +207,7 @@ const WaterReuseTot = () => {
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport={true}
|
||||
excelExportApplyStyles
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
|
||||
+30
-7
@@ -16,6 +16,12 @@ type OperatorItem = {
|
||||
opertKornNm: string;
|
||||
};
|
||||
|
||||
// opertSeCd 별 operators 목록을 전역 캐싱해서, 수식 블록이 여러 개여도 중복 요청이 발생하지 않게 한다.
|
||||
const operatorsCache: Map<
|
||||
string,
|
||||
OperatorItem[] | Promise<OperatorItem[]>
|
||||
> = new Map();
|
||||
|
||||
interface OperatorsComponentsProps {
|
||||
cmprOpert?: string | null;
|
||||
onValueChange?: (value: string) => void;
|
||||
@@ -37,13 +43,30 @@ const OperatorsComponents = ({
|
||||
}, [cmprOpert]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOperators = async () => {
|
||||
const response = await generalverificationApiService.getVrfcOperators(opertSeCd);
|
||||
if (response.status === 'success' && response.data) {
|
||||
setOperators(response.data);
|
||||
}
|
||||
};
|
||||
void fetchOperators();
|
||||
const cached = operatorsCache.get(opertSeCd);
|
||||
if (Array.isArray(cached)) {
|
||||
setOperators(cached);
|
||||
return;
|
||||
}
|
||||
|
||||
const promise =
|
||||
cached ??
|
||||
(async (): Promise<OperatorItem[]> => {
|
||||
const response =
|
||||
await generalverificationApiService.getVrfcOperators(
|
||||
opertSeCd,
|
||||
);
|
||||
if (response.status === "success" && response.data) {
|
||||
return response.data as OperatorItem[];
|
||||
}
|
||||
return [];
|
||||
})();
|
||||
|
||||
operatorsCache.set(opertSeCd, promise);
|
||||
void promise.then((items) => {
|
||||
operatorsCache.set(opertSeCd, items);
|
||||
setOperators(items);
|
||||
});
|
||||
}, [opertSeCd]);
|
||||
|
||||
// onValueChange가 있으면 부모가 제어(controlled), 없으면 내부 state로 표시
|
||||
|
||||
@@ -354,6 +354,8 @@ export type CellStyleCallbackResult =
|
||||
| string
|
||||
| {
|
||||
styleName?: string;
|
||||
/** RealGrid 엑셀 export 전용 스타일 (applyDynamicStyles 시 사용) */
|
||||
exportStyleName?: string;
|
||||
editable?: boolean;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
@@ -410,6 +412,8 @@ export type DataGridProps = {
|
||||
excelImportSewerBzmnCd?: string;
|
||||
/** 엑셀보내기 파일명에 조회 시도·시군구·연도 접미사 추가 */
|
||||
excelExportSearchContext?: ExcelExportSearchContext;
|
||||
/** true면 엑셀 export 시 cellStyleCallback 색상·고정 컬럼 배경을 모두 반영 (집계 total 화면용) */
|
||||
excelExportApplyStyles?: boolean;
|
||||
isExcelExport?: boolean;
|
||||
onRowsChange?: (rows: Record<string, string | number>[]) => void;
|
||||
fixedColumns?: number; // 고정할 컬럼 개수 (좌측부터)
|
||||
|
||||
@@ -34,10 +34,12 @@ export type SearchFormProps = {
|
||||
defaultValues?: SearchFormValues;
|
||||
searchButtonText?: string;
|
||||
isReset?: boolean;
|
||||
/** true면 시도·시군구 기본값을 계정 첫 항목이 아니라 「전체」로 둠 (집계표 등) */
|
||||
defaultRegionAll?: boolean;
|
||||
};
|
||||
|
||||
export const defaultSearchFormProps: Required<
|
||||
Omit<SearchFormProps, "onSubmit" | "onReset" | "onValuesChange" | "defaultValues" | "sidoSelectText" | "searchButtonText">
|
||||
Omit<SearchFormProps, "onSubmit" | "onReset" | "onValuesChange" | "defaultValues" | "sidoSelectText" | "searchButtonText" | "defaultRegionAll">
|
||||
> = {
|
||||
sido: true,
|
||||
gugun: true,
|
||||
|
||||
@@ -8,11 +8,11 @@ import { ValueType } from "realgrid";
|
||||
export const ExcretaTreatFactInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 기본 정보
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' }, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '소재지' } },
|
||||
{ name: 'telNo', fieldName: 'telNo', header: { text: '전화번호' } },
|
||||
|
||||
|
||||
+3
-3
@@ -8,11 +8,11 @@ import { footerDecimal1, footerDecimal3,footerInt } from "@/components/util/grid
|
||||
export const PublicSewageFactColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 시설명
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '시설명(500m³/일 이상/미만)' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '시설명(500m³/일 이상/미만)' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 소재지
|
||||
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '소재지' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
|
||||
@@ -35,12 +35,12 @@ function dropdownColumn(
|
||||
export const PumpingStationInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 기본 정보
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' } },
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' }, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' }, styleName: 'rg-cell-fixed' },
|
||||
dropdownColumn(
|
||||
{ name: 'pumpSeNm', fieldName: 'pumpSeNm', header: { text: '구분(합류식 분류식)' } },
|
||||
PUMPING_STATION_PUMP_SE_VALUES,
|
||||
|
||||
@@ -7,9 +7,9 @@ import { ValueType } from "realgrid";
|
||||
export const SeweragePenetrationRateInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 수계 및 권역
|
||||
{ name: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const PrivateSewageDesigStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 개인하수도관리지역공고현황
|
||||
{ name: 'indvSwagePbancRgnCnt', fieldName: 'indvSwagePbancRgnCnt', header: { text: '공고지역(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const SepticTankStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 총 시설 수 — (B+C)/2 + (E+F)/2 = A + D
|
||||
{
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const SewageTreatFactStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 총계
|
||||
{
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const ExcretaRelatedIndustryInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 업체수(개소)
|
||||
{ name: 'bzentyCnt', fieldName: 'bzentyCnt', header: { text: '업체수(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const PrivateSewageTreatRelatedIndustryInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 개인하수처리시설 설계·시공업
|
||||
{ name: 'designMdsrCnt', fieldName: 'designMdsrCnt', header: { text: '업소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const PublicInstitutionExcretaIndustryInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 직제
|
||||
{ name: 'ognzNm', fieldName: 'ognzNm', header: { text: '직 제' } },
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const PublicInstitutionSewerIndustryInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 직제
|
||||
{ name: 'ognzNm', fieldName: 'ognzNm', header: { text: '직 제' } },
|
||||
|
||||
+3
-3
@@ -49,11 +49,11 @@ function dropdownColumn(
|
||||
export const DigestorOperatingStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 하수처리장명
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false },
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 소화조 운영현황
|
||||
dropdownColumn(
|
||||
|
||||
+3
-3
@@ -5,11 +5,11 @@ import { ValueType } from "realgrid";
|
||||
export const ExcretaSlurgeProcessingStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 분뇨처리장명
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '분뇨 처리장명' }, editable: false },
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '분뇨 처리장명' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 발생량
|
||||
{
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const SewageSlurgeProcessingFacStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 찌꺼기처리시설명
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '찌꺼기처리시설명' } },
|
||||
|
||||
+3
-3
@@ -5,11 +5,11 @@ import { ValueType } from "realgrid";
|
||||
export const SlurgeProcessingFacStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 하수처리장명
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 찌꺼기발생량(톤/년, 탈수기준)
|
||||
{
|
||||
|
||||
@@ -7,12 +7,12 @@ import { ValueType } from "realgrid";
|
||||
export const FactInstallStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역 (ctpvNm은 commonInfo에서 주입)
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 하수처리장 정보
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, footer: { text: '합계' }, editable: false },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리시설용량(m³/일)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 처리구역면적(km²)
|
||||
|
||||
@@ -136,21 +136,21 @@ export const ManholeStatusInpColumn = {
|
||||
header: { text: "시도" },
|
||||
footer: { text: "합계" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "sggNm",
|
||||
fieldName: "sggNm",
|
||||
header: { text: "시군구" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "admdstNm",
|
||||
fieldName: "admdstNm",
|
||||
header: { text: "행정구역명" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "emphsMngSeNm",
|
||||
|
||||
@@ -12,27 +12,28 @@ export const PipeDiameterStatusInpColumn = {
|
||||
header: { text: "시도" },
|
||||
footer: { text: "합계" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "sggNm",
|
||||
fieldName: "sggNm",
|
||||
header: { text: "시군구" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "pipeSeNm",
|
||||
fieldName: "pipeSeNm",
|
||||
header: { text: "관종" },
|
||||
editable: false,
|
||||
styleName: 'rg-cell-fixed',
|
||||
},
|
||||
{
|
||||
name: "admdstNm",
|
||||
fieldName: "admdstNm",
|
||||
header: { text: "행정구역명" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "pdmtByTsmWhol",
|
||||
|
||||
@@ -90,14 +90,14 @@ export const PipeTypeStatusInpColumn = {
|
||||
header: { text: "시도" },
|
||||
footer: { text: "합계" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "sggNm",
|
||||
fieldName: "sggNm",
|
||||
header: { text: "시군구" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "pipeSeNm",
|
||||
@@ -105,13 +105,14 @@ export const PipeTypeStatusInpColumn = {
|
||||
header: { text: "관종" },
|
||||
footer: { text: "합계" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "admdstNm",
|
||||
fieldName: "admdstNm",
|
||||
header: { text: "행정구역명" },
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly",
|
||||
styleName: "rg-cell-fixed",
|
||||
},
|
||||
{
|
||||
name: "pipeByPrstWhol",
|
||||
|
||||
@@ -6,9 +6,9 @@ import { ValueType } from "realgrid";
|
||||
export const RegionalFactStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 기본 정보
|
||||
{ name: 'goalYr', fieldName: 'goalYr', header: { text: '하수정비기본계획목표년도' } },
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import { footerDecimal1, footerDecimal2, footerInt } from "@/components/util/gri
|
||||
export const PipeDredgingStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구명' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구명' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 하수관로 총 시설연장
|
||||
{ name: 'wholFcltPrlg', fieldName: 'wholFcltPrlg', header: { text: '하수관로총시설연장(m)' }, editable: false, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00', styleName: 'rg-cell-readonly' },
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const RepairStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구명' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구명' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 개·보수관로 > 전체보수(m)
|
||||
{
|
||||
|
||||
+3
-3
@@ -31,10 +31,10 @@ function dropdownColumn(
|
||||
// 하수도 통계 관리 > 04.하수저류시설 및 빗물펌프장 > tab 2: 빗물펌프장
|
||||
export const RainfallPumpStationInpColumn = {
|
||||
columns: [
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' }, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '위치' } },
|
||||
|
||||
dropdownColumn(
|
||||
|
||||
+3
-3
@@ -50,11 +50,11 @@ function oxYnDropdownColumn(
|
||||
export const SewageReservoirInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 기본 정보
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' }, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '위치' } },
|
||||
{
|
||||
name: 'oprtngStrtYmd',
|
||||
|
||||
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const CauserBurdenInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 부과액
|
||||
{ name: 'levyEvntcsCnt', fieldName: 'levyEvntcsCnt', header: { text: '건수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const SewerageChargeInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 요금
|
||||
{ name: 'anlLevyQnt', fieldName: 'anlLevyQnt', header: { text: '연간조정량(m³)(A)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const SewerageFinanceSettleInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 재정별 사업비
|
||||
{
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
|
||||
export const SewerageUsageFeeInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 총계 > 부과액
|
||||
{
|
||||
|
||||
@@ -15,8 +15,8 @@ const COMPLAINT_TYPE_COUNT_FIELDS = [
|
||||
export const ComplaintInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 하수처리인구(명)
|
||||
{ name: 'swtrmtPpltnCnt', fieldName: 'swtrmtPpltnCnt', header: { text: '하수처리인구(명)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
|
||||
|
||||
+4
-4
@@ -16,12 +16,12 @@ const RESIDENT_FRIENDLY_FACILITY_AREA_FIELDS = [
|
||||
export const ResidentFriendlyFactInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 시설 기본 정보
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'fcltCpct', fieldName: 'fcltCpct', header: { text: '시설용량(m³/일)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'fcltCpct', fieldName: 'fcltCpct', header: { text: '시설용량(m³/일)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 처리장면적(㎡)
|
||||
{ name: 'tmplntArea', fieldName: 'tmplntArea', header: { text: '처리장면적(㎡)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
|
||||
|
||||
+2
-2
@@ -54,8 +54,8 @@ const PUBLIC_SEWAGE_REUSE_USE_QNT_SUM_FIELDS = [
|
||||
export const PublicSewageTreatWaterReuseInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 하수처리장
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '시설명' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
|
||||
+2
-2
@@ -56,8 +56,8 @@ function dropdownColumn(
|
||||
export const RainFallUtilFactStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 시설명
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
|
||||
|
||||
@@ -63,8 +63,8 @@ function dropdownColumn(
|
||||
export const RecycledWaterStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 건축물명/개발사업명
|
||||
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '건축물명/개발사업명' } },
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
export const TreatWaterReuseBusinessApprStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 상호(명칭)
|
||||
{ name: 'bplcNm', fieldName: 'bplcNm', header: { text: '상호(명칭)' } },
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
export const TreatWaterReuseDesignConstStatusInpColumn = {
|
||||
columns: [
|
||||
// 행정구역
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
|
||||
|
||||
// 상호(명칭)
|
||||
{ name: 'bplcNm', fieldName: 'bplcNm', header: { text: '상호(명칭)' } },
|
||||
|
||||
@@ -315,129 +315,15 @@ const rawList = [
|
||||
]
|
||||
},
|
||||
|
||||
// ST00020003 관종별 현황은 연도 범위가 동적이므로 rawList에 포함하지 않고
|
||||
// getST00020003RvwConfig() 로 런타임에 생성합니다.
|
||||
{
|
||||
tabKey: 'ST00020003',
|
||||
tabKey: 'ST00020003_PLACEHOLDER',
|
||||
tabLabel: '관종별 현황',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{
|
||||
name: 'ctpvNm',
|
||||
fieldName: 'ctpvNm',
|
||||
header: { text: '시도' },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: [
|
||||
{
|
||||
valueCallback: (grid: GridView) => {
|
||||
const divisionIndex = 0;
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
for (let i = 0; i < dataSource.getRowCount(); i++) {
|
||||
const val = dataSource.getValue(i, 'displayYear');
|
||||
if (val) uniqueValues.add(String(val));
|
||||
}
|
||||
const divisionValues = Array.from(uniqueValues);
|
||||
return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계';
|
||||
}
|
||||
},
|
||||
{
|
||||
valueCallback: (grid: GridView) => {
|
||||
const divisionIndex = 1;
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
for (let i = 0; i < dataSource.getRowCount(); i++) {
|
||||
const val = dataSource.getValue(i, 'displayYear');
|
||||
if (val) uniqueValues.add(String(val));
|
||||
}
|
||||
const divisionValues = Array.from(uniqueValues);
|
||||
return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계';
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true },
|
||||
{ name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true },
|
||||
{
|
||||
name: 'displayYear',
|
||||
fieldName: 'displayYear',
|
||||
header: { text: '구분' },
|
||||
sortdisable: true,
|
||||
footer: [
|
||||
{
|
||||
valueCallback: (grid: GridView) => {
|
||||
const divisionIndex = 0;
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
for (let i = 0; i < dataSource.getRowCount(); i++) {
|
||||
const val = dataSource.getValue(i, 'displayYear');
|
||||
if (val) uniqueValues.add(String(val));
|
||||
}
|
||||
return Array.from(uniqueValues)[divisionIndex] ? Array.from(uniqueValues)[divisionIndex] + '합계' : '';
|
||||
}
|
||||
},
|
||||
{
|
||||
valueCallback: (grid: GridView) => {
|
||||
const divisionIndex = 1;
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
for (let i = 0; i < dataSource.getRowCount(); i++) {
|
||||
const val = dataSource.getValue(i, 'displayYear');
|
||||
if (val) uniqueValues.add(String(val));
|
||||
}
|
||||
return Array.from(uniqueValues)[divisionIndex] ? Array.from(uniqueValues)[divisionIndex] + '합계' : '';
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{ name: 'pipeByPrstWhol', fieldName: 'pipeByPrstWhol', header: { text: '계' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'lyngPrlg', fieldName: 'lyngPrlg', header: { text: '매설연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: 'dmltnPrlg', fieldName: 'dmltnPrlg', header: { text: '철거연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
|
||||
|
||||
...Array.from({ length: totalCount }, (_, i) => {
|
||||
const dtoNum = i + 1;
|
||||
|
||||
const wholField = `yy${dtoNum}PrlgWhol`;
|
||||
const lyngField = `yy${dtoNum}LyngPrlg`;
|
||||
const dmltnField = `yy${dtoNum}DmltnPrlg`;
|
||||
|
||||
return [
|
||||
{ name: wholField, fieldName: wholField, header: { text: '계' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: lyngField, fieldName: lyngField, header: { text: '매설연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
|
||||
{ name: dmltnField, fieldName: dmltnField, header: { text: '철거연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }
|
||||
];
|
||||
}).flat(),
|
||||
|
||||
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },
|
||||
], columnLayout: [
|
||||
{
|
||||
name: "areaGroup",
|
||||
header: { text: "행정구역" },
|
||||
items: [
|
||||
{ column: "ctpvNm", footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] },
|
||||
"sggNm",
|
||||
"swrgPipeNm",
|
||||
],
|
||||
},
|
||||
"displayYear",
|
||||
{ name: "summaryGroup", header: { text: "총계" }, items: ["pipeByPrstWhol", "lyngPrlg", "dmltnPrlg"] },
|
||||
|
||||
...Array.from({ length: totalCount }, (_, i) => {
|
||||
// const dtoNum = totalCount - i;
|
||||
const dtoNum = i + 1;
|
||||
const yearLabel = i === 0 ? "1996년이전" : `${startYear + (i - 1)}년`;
|
||||
|
||||
return {
|
||||
name: `group_${yearLabel}`,
|
||||
header: { text: yearLabel },
|
||||
items: [`yy${dtoNum}PrlgWhol`, `yy${dtoNum}LyngPrlg`, `yy${dtoNum}DmltnPrlg`],
|
||||
};
|
||||
}),
|
||||
"rmrk",
|
||||
],
|
||||
columns: [],
|
||||
columnLayout: [],
|
||||
},
|
||||
|
||||
{
|
||||
@@ -553,8 +439,10 @@ const rawList = [
|
||||
]
|
||||
},
|
||||
|
||||
// ST00020005 맨홀 현황은 연도 범위가 동적이므로 rawList에 포함하지 않고
|
||||
// getST00020005RvwConfig() 로 런타임에 생성합니다.
|
||||
{
|
||||
tabKey: "ST00020005",
|
||||
tabKey: "ST00020005_PLACEHOLDER",
|
||||
tabLabel: "맨홀 현황",
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 3,
|
||||
@@ -858,11 +746,177 @@ const rawList = [
|
||||
},
|
||||
];
|
||||
|
||||
export const SewagePipeColumnInfoListRvw = rawList.map(tab => ({
|
||||
...tab,
|
||||
columns: tab.columns.map(col => ({
|
||||
...col,
|
||||
editable: false,
|
||||
styleName: "rg-cell-readonly"
|
||||
}))
|
||||
}));
|
||||
/** ST00020003 관종별 현황 — 검토 화면용 동적 컬럼 생성
|
||||
* 필드명: prlg_whol_{year}, lyng_prlg_{year}, dmltn_prlg_{year}
|
||||
* (total의 getST00020003Config와 동일 필드, oldPipeRt 제외)
|
||||
*/
|
||||
export const getST00020003RvwConfig = (statsYr: string | number) => {
|
||||
const rvwStartYear = 1995;
|
||||
const rvwEndYear = Number(statsYr) || dayjs().year();
|
||||
|
||||
const yearRange = Array.from(
|
||||
{ length: rvwEndYear - rvwStartYear + 1 },
|
||||
(_, i) => rvwStartYear + i,
|
||||
);
|
||||
|
||||
return {
|
||||
tabKey: 'ST00020003',
|
||||
tabLabel: '관종별 현황',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
|
||||
{ name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true },
|
||||
{ name: 'pipeByPrstWhol', fieldName: 'pipeByPrstWhol', header: { text: '계' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'lyngPrlg', fieldName: 'lyngPrlg', header: { text: '매설연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'dmltnPrlg', fieldName: 'dmltnPrlg', header: { text: '철거연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
...yearRange.flatMap((yr) => [
|
||||
{ name: `prlg_whol_${yr}`, fieldName: `prlg_whol_${yr}`, header: { text: '계' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: `lyng_prlg_${yr}`, fieldName: `lyng_prlg_${yr}`, header: { text: '매설연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: `dmltn_prlg_${yr}`, fieldName: `dmltn_prlg_${yr}`, header: { text: '철거연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
]),
|
||||
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
],
|
||||
columnLayout: [
|
||||
{
|
||||
name: 'areaGroup',
|
||||
header: { text: '행정구역' },
|
||||
items: ['ctpvNm', 'sggNm', 'admdstNm', 'swrgPipeNm'],
|
||||
},
|
||||
{ name: 'summaryGroup', header: { text: '총계' }, items: ['pipeByPrstWhol', 'lyngPrlg', 'dmltnPrlg'] },
|
||||
...yearRange.map((yr) => ({
|
||||
name: `group_${yr}`,
|
||||
header: { text: yr === rvwStartYear ? '1996년이전' : `${yr}년` },
|
||||
items: [`prlg_whol_${yr}`, `lyng_prlg_${yr}`, `dmltn_prlg_${yr}`],
|
||||
})),
|
||||
'rmrk',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
/** ST00020005 맨홀 현황 — 검토 화면용 동적 컬럼 생성
|
||||
* 필드명: wholMnhlCnt{year}, step1~5MnhlCnt{year}
|
||||
* (total의 getST00020005Config와 동일 필드)
|
||||
*/
|
||||
export const getST00020005RvwConfig = (statsYr: string | number) => {
|
||||
const firstYear = 2014;
|
||||
const lastYear = Number(statsYr) || dayjs().year();
|
||||
const yearRange = Array.from({ length: lastYear - firstYear + 1 }, (_, i) => firstYear + i);
|
||||
|
||||
const makeFooterValueCallback = (divisionIndex: number) => ({
|
||||
valueCallback: (grid: GridView) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const uniqueValues = new Set<string>();
|
||||
for (let i = 0; i < dataSource.getRowCount(); i++) {
|
||||
const val = dataSource.getValue(i, 'displayYear');
|
||||
if (val) uniqueValues.add(String(val));
|
||||
}
|
||||
const divisionValues = Array.from(uniqueValues);
|
||||
return divisionValues.length > divisionIndex
|
||||
? divisionValues[divisionIndex] + '합계'
|
||||
: '합계';
|
||||
},
|
||||
});
|
||||
|
||||
const mnhlYearCols = (year: number) => [
|
||||
{ name: `wholMnhlCnt${year}`, fieldName: `wholMnhlCnt${year}`, header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: `step1MnhlCnt${year}`, fieldName: `step1MnhlCnt${year}`, header: { text: '주철' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: `step2MnhlCnt${year}`, fieldName: `step2MnhlCnt${year}`, header: { text: '주철(조화)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: `step3MnhlCnt${year}`, fieldName: `step3MnhlCnt${year}`, header: { text: '콘크리트' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: `step4MnhlCnt${year}`, fieldName: `step4MnhlCnt${year}`, header: { text: '기타' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: `step5MnhlCnt${year}`, fieldName: `step5MnhlCnt${year}`, header: { text: '추락방지시설\n설치맨홀(개)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
];
|
||||
|
||||
const mnhlYearLayout = (year: number) => ({
|
||||
name: `yr${year}Group`,
|
||||
header: { text: year === firstYear ? '2015년 이전' : `${year}년` },
|
||||
items: [
|
||||
{
|
||||
name: `yr${year}MtrlGroup`,
|
||||
header: { text: '맨홀뚜껑 재질(개)' },
|
||||
items: [`wholMnhlCnt${year}`, `step1MnhlCnt${year}`, `step2MnhlCnt${year}`, `step3MnhlCnt${year}`, `step4MnhlCnt${year}`],
|
||||
},
|
||||
`step5MnhlCnt${year}`,
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
tabKey: 'ST00020005',
|
||||
tabLabel: '맨홀 현황',
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 3,
|
||||
columns: [
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true, footer: [makeFooterValueCallback(0), makeFooterValueCallback(1)] },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
|
||||
{ name: 'emphsMngSeNm', fieldName: 'emphsMngSeNm', header: { text: '집중강우\n중점관리\n구역구분' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true },
|
||||
{ name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, editable: false, styleName: 'rg-cell-readonly', sortdisable: true, footer: [makeFooterValueCallback(0), makeFooterValueCallback(1)] },
|
||||
{ name: 'totalWholMnhlCnt', fieldName: 'totalWholMnhlCnt', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'totalStep1MnhlCnt', fieldName: 'totalStep1MnhlCnt', header: { text: '주철' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'totalStep2MnhlCnt', fieldName: 'totalStep2MnhlCnt', header: { text: '주철(조화)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'totalStep3MnhlCnt', fieldName: 'totalStep3MnhlCnt', header: { text: '콘크리트' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'totalStep4MnhlCnt', fieldName: 'totalStep4MnhlCnt', header: { text: '기타' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'totalStep5MnhlCnt', fieldName: 'totalStep5MnhlCnt', header: { text: '추락방지시설\n설치맨홀(개)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
|
||||
...yearRange.flatMap(mnhlYearCols),
|
||||
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
],
|
||||
columnLayout: [
|
||||
{
|
||||
name: 'addrGroup',
|
||||
header: { text: '행정구역' },
|
||||
items: [
|
||||
{ column: 'ctpvNm', footerUserSpans: [{ colspan: 5 }, { colspan: 5 }] },
|
||||
'sggNm',
|
||||
'admdstNm',
|
||||
],
|
||||
},
|
||||
'emphsMngSeNm',
|
||||
'displayYear',
|
||||
{
|
||||
name: 'totGroup',
|
||||
header: { text: '총계' },
|
||||
items: [
|
||||
{ name: 'totMtrlGroup', header: { text: '맨홀뚜껑 재질(개)' }, items: ['totalWholMnhlCnt', 'totalStep1MnhlCnt', 'totalStep2MnhlCnt', 'totalStep3MnhlCnt', 'totalStep4MnhlCnt'] },
|
||||
'totalStep5MnhlCnt',
|
||||
],
|
||||
},
|
||||
...yearRange.map(mnhlYearLayout),
|
||||
'rmrk',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const SewagePipeColumnInfoListRvw = rawList
|
||||
.filter((tab) => tab.tabKey !== 'ST00020003_PLACEHOLDER' && tab.tabKey !== 'ST00020005_PLACEHOLDER')
|
||||
.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map((col) => ({
|
||||
...col,
|
||||
editable: false,
|
||||
styleName: 'rg-cell-readonly',
|
||||
})),
|
||||
}));
|
||||
|
||||
const st00020003RvwPlaceholder = {
|
||||
tabKey: 'ST00020003',
|
||||
tabLabel: '관종별 현황',
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [] as ReturnType<typeof getST00020003RvwConfig>['columns'],
|
||||
columnLayout: [] as ReturnType<typeof getST00020003RvwConfig>['columnLayout'],
|
||||
};
|
||||
|
||||
const st00020005RvwPlaceholder = {
|
||||
tabKey: 'ST00020005',
|
||||
tabLabel: '맨홀 현황',
|
||||
rowStartNum: 3,
|
||||
fixedColumns: 3,
|
||||
columns: [] as ReturnType<typeof getST00020005RvwConfig>['columns'],
|
||||
columnLayout: [] as ReturnType<typeof getST00020005RvwConfig>['columnLayout'],
|
||||
};
|
||||
|
||||
// ST00020003: index 2, ST00020005: 끝에 추가
|
||||
SewagePipeColumnInfoListRvw.splice(2, 0, st00020003RvwPlaceholder);
|
||||
SewagePipeColumnInfoListRvw.push(st00020005RvwPlaceholder);
|
||||
@@ -700,8 +700,10 @@ const rawSewagePipeTotList = [
|
||||
],
|
||||
},
|
||||
|
||||
// ST00020005 맨홀 현황은 연도 범위가 동적이므로 rawSewagePipeTotList에 포함하지 않고
|
||||
// getST00020005Config() 로 런타임에 생성합니다.
|
||||
{
|
||||
tabKey: "ST00020005",
|
||||
tabKey: "ST00020005_PLACEHOLDER",
|
||||
tabLabel: "맨홀 현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
@@ -985,10 +987,80 @@ export const getST00020003Config = (statsYr: string | number) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const SewagePipeColumnInfoListTot: ColumnInfoList[] = rawSewagePipeTotList.map((tab) => ({
|
||||
...tab,
|
||||
columns: tab.columns.map((col) => ({ ...col, editable: false as const })),
|
||||
}));
|
||||
/** ST00020005 — 맨홀 현황 동적 컬럼 생성
|
||||
* 응답 필드: wholMnhlCnt{year}, step1~5MnhlCnt{year}
|
||||
* firstYear: 데이터 시작 연도(기본 2014), lastYear: 조회 연도(statsYr)
|
||||
*/
|
||||
export const getST00020005Config = (statsYr: string | number) => {
|
||||
const firstYear = 2014;
|
||||
const lastYear = Number(statsYr) || new Date().getFullYear();
|
||||
|
||||
const mnhlYearCols = (year: number) => [
|
||||
{ name: `wholMnhlCnt${year}`, fieldName: `wholMnhlCnt${year}`, header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: `step1MnhlCnt${year}`, fieldName: `step1MnhlCnt${year}`, header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: `step2MnhlCnt${year}`, fieldName: `step2MnhlCnt${year}`, header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: `step3MnhlCnt${year}`, fieldName: `step3MnhlCnt${year}`, header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: `step4MnhlCnt${year}`, fieldName: `step4MnhlCnt${year}`, header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: `step5MnhlCnt${year}`, fieldName: `step5MnhlCnt${year}`, header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
];
|
||||
|
||||
const mnhlYearLayout = (year: number) => ({
|
||||
name: `yr${year}Group`,
|
||||
header: { text: year === firstYear ? `${firstYear}년 이전` : `${year}년` },
|
||||
items: [
|
||||
{
|
||||
name: `yr${year}MtrlGroup`,
|
||||
header: { text: "맨홀뚜껑 재질(개)" },
|
||||
items: [
|
||||
`wholMnhlCnt${year}`,
|
||||
`step1MnhlCnt${year}`,
|
||||
`step2MnhlCnt${year}`,
|
||||
`step3MnhlCnt${year}`,
|
||||
`step4MnhlCnt${year}`,
|
||||
],
|
||||
},
|
||||
`step5MnhlCnt${year}`,
|
||||
],
|
||||
});
|
||||
|
||||
const yearRange = Array.from({ length: lastYear - firstYear + 1 }, (_, i) => firstYear + i);
|
||||
|
||||
return {
|
||||
tabKey: "ST00020005",
|
||||
tabLabel: "맨홀 현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
|
||||
{ name: "emphsMngSeNm", fieldName: "emphsMngSeNm", header: { text: "집중강우\n중점관리\n구역구분" }, editable: false },
|
||||
{ name: "totWholMnhlCnt", fieldName: "totWholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: "totStep1MnhlCnt", fieldName: "totStep1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: "totStep2MnhlCnt", fieldName: "totStep2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: "totStep3MnhlCnt", fieldName: "totStep3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: "totStep4MnhlCnt", fieldName: "totStep4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
{ name: "totStep5MnhlCnt", fieldName: "totStep5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
|
||||
...yearRange.flatMap(mnhlYearCols),
|
||||
],
|
||||
columnLayout: [
|
||||
{ name: "addrGroup", header: { text: "행정구역" }, items: ["ctpvNm", "sggNm"] },
|
||||
"emphsMngSeNm",
|
||||
{
|
||||
name: "totGroup",
|
||||
header: { text: "연도별총계" },
|
||||
items: [
|
||||
{ name: "totMtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["totWholMnhlCnt", "totStep1MnhlCnt", "totStep2MnhlCnt", "totStep3MnhlCnt", "totStep4MnhlCnt"] },
|
||||
"totStep5MnhlCnt",
|
||||
],
|
||||
},
|
||||
...yearRange.map(mnhlYearLayout),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const SewagePipeColumnInfoListTot: ColumnInfoList[] = rawSewagePipeTotList
|
||||
.filter((tab) => tab.tabKey !== "ST00020005_PLACEHOLDER")
|
||||
.map((tab) => ({ ...tab, columns: tab.columns.map((col) => ({ ...col, editable: false as const })) }));
|
||||
|
||||
const st00020003PlaceholderTab: ColumnInfoList = {
|
||||
tabKey: "ST00020003",
|
||||
@@ -999,4 +1071,14 @@ const st00020003PlaceholderTab: ColumnInfoList = {
|
||||
columnLayout: [],
|
||||
};
|
||||
|
||||
SewagePipeColumnInfoListTot.splice(2, 0, st00020003PlaceholderTab);
|
||||
const st00020005PlaceholderTab: ColumnInfoList = {
|
||||
tabKey: "ST00020005",
|
||||
tabLabel: "맨홀 현황",
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 2,
|
||||
columns: [],
|
||||
columnLayout: [],
|
||||
};
|
||||
|
||||
SewagePipeColumnInfoListTot.splice(2, 0, st00020003PlaceholderTab);
|
||||
SewagePipeColumnInfoListTot.push(st00020005PlaceholderTab);
|
||||
@@ -199,6 +199,108 @@ export function shouldPrepareColumnsForExcelExport(columns: ColumnConfig[]): boo
|
||||
return columns.some((c) => c.excludeFromExcelExport || c.visible === false);
|
||||
}
|
||||
|
||||
/** 고정(frozen) 컬럼 표시용 — rg-cell-readonly와 동일한 화면 스타일 */
|
||||
export const FIXED_CELL_STYLE_NAME = "rg-cell-fixed";
|
||||
|
||||
const FIXED_EXPORT_STRIP_CLASSES = new Set([
|
||||
FIXED_CELL_STYLE_NAME,
|
||||
"rg-cell-readonly",
|
||||
]);
|
||||
|
||||
/** 고정 컬럼 styleName에 rg-cell-fixed 추가 */
|
||||
export function appendFixedCellStyle(styleName: string | undefined): string {
|
||||
const parts = (styleName ?? "").split(/\s+/).filter(Boolean);
|
||||
if (!parts.includes(FIXED_CELL_STYLE_NAME)) {
|
||||
parts.push(FIXED_CELL_STYLE_NAME);
|
||||
}
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
/** 엑셀 export 시 고정/readonly 배경·글자색 클래스 제거 (검증 등 다른 클래스는 유지) */
|
||||
export function stripFixedStylesForExport(styleName: string | undefined): string {
|
||||
if (!styleName) return "";
|
||||
return styleName
|
||||
.split(/\s+/)
|
||||
.filter((cls) => cls && !FIXED_EXPORT_STRIP_CLASSES.has(cls))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function resolveColumnStyleName(
|
||||
column: { name: string; styleName?: string },
|
||||
fixedColumnNameSet: Set<string>,
|
||||
): string {
|
||||
const base = column.styleName ?? "";
|
||||
return fixedColumnNameSet.has(column.name)
|
||||
? appendFixedCellStyle(base)
|
||||
: base;
|
||||
}
|
||||
|
||||
/** 화면 고정(colCount)과 동일한 앞쪽 visible 리프 컬럼명 */
|
||||
export function getFixedLeafColumnNames(
|
||||
columns: ColumnConfig[],
|
||||
columnLayout: ColumnLayoutItem[] | undefined,
|
||||
fixedColumns: number | undefined,
|
||||
): string[] {
|
||||
if (!fixedColumns || fixedColumns <= 0) return [];
|
||||
|
||||
const excludedNames = new Set(
|
||||
columns.filter((c) => c.excludeFromExcelExport).map((c) => c.name),
|
||||
);
|
||||
const hiddenNames = new Set(
|
||||
columns.filter((c) => c.visible === false).map((c) => c.name),
|
||||
);
|
||||
const knownNames = new Set(columns.map((c) => c.name));
|
||||
const layoutNames =
|
||||
columnLayout && columnLayout.length > 0
|
||||
? flattenColumnLayoutNames(columnLayout)
|
||||
: columns.map((c) => c.name);
|
||||
|
||||
const frozen: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const name of layoutNames) {
|
||||
if (seen.has(name) || !knownNames.has(name)) continue;
|
||||
if (hiddenNames.has(name) || excludedNames.has(name)) continue;
|
||||
seen.add(name);
|
||||
frozen.push(name);
|
||||
if (frozen.length >= fixedColumns) break;
|
||||
}
|
||||
return frozen;
|
||||
}
|
||||
|
||||
export function buildFixedColumnNameSet(
|
||||
columns: ColumnConfig[],
|
||||
columnLayout: ColumnLayoutItem[] | undefined,
|
||||
fixedColumns: number | undefined,
|
||||
): Set<string> {
|
||||
return new Set(getFixedLeafColumnNames(columns, columnLayout, fixedColumns));
|
||||
}
|
||||
|
||||
type GridViewWithFixedOptions = GridView & {
|
||||
setFixedOptions?: (options: { colCount?: number }) => void;
|
||||
getFixedOptions?: () => { colCount?: number };
|
||||
};
|
||||
|
||||
/** 엑셀 export 직전: RealGrid 고정영역 기본 회색이 export에 섞이지 않도록 해제 */
|
||||
export function releaseFixedOptionsForExcelExport(
|
||||
gridView: GridView,
|
||||
fixedColumns: number | undefined,
|
||||
): number | undefined {
|
||||
if (!fixedColumns || fixedColumns <= 0) return undefined;
|
||||
const gv = gridView as GridViewWithFixedOptions;
|
||||
const colCount = gv.getFixedOptions?.()?.colCount ?? fixedColumns;
|
||||
gv.setFixedOptions?.({ colCount: 0 });
|
||||
return colCount;
|
||||
}
|
||||
|
||||
export function restoreFixedOptionsAfterExcelExport(
|
||||
gridView: GridView,
|
||||
colCount: number | undefined,
|
||||
): void {
|
||||
if (colCount && colCount > 0) {
|
||||
(gridView as GridViewWithFixedOptions).setFixedOptions?.({ colCount });
|
||||
}
|
||||
}
|
||||
|
||||
const INVALID_EXCEL_FILE_NAME_CHARS = /[\\/:*?"<>|]/g;
|
||||
|
||||
function sanitizeExcelFileNamePart(value: string): string {
|
||||
|
||||
@@ -258,91 +258,17 @@ export interface SewagePipeReviewManholeItem {
|
||||
emphsMngSeCd: string;
|
||||
emphsMngSeNm?: string;
|
||||
|
||||
yy14WholMnhlCnt: number;
|
||||
yy14Step1MnhlCnt: number;
|
||||
yy14Step2MnhlCnt: number;
|
||||
yy14Step3MnhlCnt: number;
|
||||
yy14Step4MnhlCnt: number;
|
||||
yy14Step5MnhlCnt: number;
|
||||
|
||||
yy15WholMnhlCnt: number;
|
||||
yy15Step1MnhlCnt: number;
|
||||
yy15Step2MnhlCnt: number;
|
||||
yy15Step3MnhlCnt: number;
|
||||
yy15Step4MnhlCnt: number;
|
||||
yy15Step5MnhlCnt: number;
|
||||
|
||||
yy16WholMnhlCnt: number;
|
||||
yy16Step1MnhlCnt: number;
|
||||
yy16Step2MnhlCnt: number;
|
||||
yy16Step3MnhlCnt: number;
|
||||
yy16Step4MnhlCnt: number;
|
||||
yy16Step5MnhlCnt: number;
|
||||
|
||||
yy17WholMnhlCnt: number;
|
||||
yy17Step1MnhlCnt: number;
|
||||
yy17Step2MnhlCnt: number;
|
||||
yy17Step3MnhlCnt: number;
|
||||
yy17Step4MnhlCnt: number;
|
||||
yy17Step5MnhlCnt: number;
|
||||
|
||||
yy18WholMnhlCnt: number;
|
||||
yy18Step1MnhlCnt: number;
|
||||
yy18Step2MnhlCnt: number;
|
||||
yy18Step3MnhlCnt: number;
|
||||
yy18Step4MnhlCnt: number;
|
||||
yy18Step5MnhlCnt: number;
|
||||
|
||||
yy19WholMnhlCnt: number;
|
||||
yy19Step1MnhlCnt: number;
|
||||
yy19Step2MnhlCnt: number;
|
||||
yy19Step3MnhlCnt: number;
|
||||
yy19Step4MnhlCnt: number;
|
||||
yy19Step5MnhlCnt: number;
|
||||
|
||||
yy20WholMnhlCnt: number;
|
||||
yy20Step1MnhlCnt: number;
|
||||
yy20Step2MnhlCnt: number;
|
||||
yy20Step3MnhlCnt: number;
|
||||
yy20Step4MnhlCnt: number;
|
||||
yy20Step5MnhlCnt: number;
|
||||
|
||||
yy21WholMnhlCnt: number;
|
||||
yy21Step1MnhlCnt: number;
|
||||
yy21Step2MnhlCnt: number;
|
||||
yy21Step3MnhlCnt: number;
|
||||
yy21Step4MnhlCnt: number;
|
||||
yy21Step5MnhlCnt: number;
|
||||
|
||||
yy22WholMnhlCnt: number;
|
||||
yy22Step1MnhlCnt: number;
|
||||
yy22Step2MnhlCnt: number;
|
||||
yy22Step3MnhlCnt: number;
|
||||
yy22Step4MnhlCnt: number;
|
||||
yy22Step5MnhlCnt: number;
|
||||
|
||||
yy23WholMnhlCnt: number;
|
||||
yy23Step1MnhlCnt: number;
|
||||
yy23Step2MnhlCnt: number;
|
||||
yy23Step3MnhlCnt: number;
|
||||
yy23Step4MnhlCnt: number;
|
||||
yy23Step5MnhlCnt: number;
|
||||
|
||||
yy24WholMnhlCnt: number;
|
||||
yy24Step1MnhlCnt: number;
|
||||
yy24Step2MnhlCnt: number;
|
||||
yy24Step3MnhlCnt: number;
|
||||
yy24Step4MnhlCnt: number;
|
||||
yy24Step5MnhlCnt: number;
|
||||
|
||||
yy25WholMnhlCnt: number;
|
||||
yy25Step1MnhlCnt: number;
|
||||
yy25Step2MnhlCnt: number;
|
||||
yy25Step3MnhlCnt: number;
|
||||
yy25Step4MnhlCnt: number;
|
||||
yy25Step5MnhlCnt: number;
|
||||
totalWholMnhlCnt: number;
|
||||
totalStep1MnhlCnt: number;
|
||||
totalStep2MnhlCnt: number;
|
||||
totalStep3MnhlCnt: number;
|
||||
totalStep4MnhlCnt: number;
|
||||
totalStep5MnhlCnt: number;
|
||||
|
||||
rmrk: string | null;
|
||||
|
||||
/** 연도별 동적 필드 (wholMnhlCnt{year} ~ step5MnhlCnt{year}) */
|
||||
[key: string]: string | number | null | undefined;
|
||||
}
|
||||
|
||||
export interface SewagePipeManholeResponseData {
|
||||
|
||||
+12
-104
@@ -162,18 +162,18 @@ export interface SewagePipePipeDiamStatusResponse {
|
||||
|
||||
/**
|
||||
* 맨홀 현황 응답 아이템 인터페이스
|
||||
* DTO의 yy14(2014년 이전) ~ yy25(2024년) 필드를 모두 명시적으로 선언
|
||||
* 연도별 동적 필드: wholMnhlCnt{year}, step1~5MnhlCnt{year}
|
||||
* ex) wholMnhlCnt2014, step1MnhlCnt2025
|
||||
*/
|
||||
export interface FcltyManholeSummaryItem {
|
||||
/* --- 기본 정보 및 행 제어 --- */
|
||||
ctpvNm: string; // 시도명
|
||||
sggNm: string; // 시군구명
|
||||
admdstNm: string; // 행정구역명
|
||||
admdstCd: string; // 행정구역코드
|
||||
statsYr: string; // 통계연도
|
||||
rowType: string; // 데이터 구분 (OLD, CURR, SUMMARY 등)
|
||||
emphsMngSeCd: string; // 중점관리구역구분코드
|
||||
emphsMngSeNm: string; // 중점관리구역구분명
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstNm: string;
|
||||
admdstCd: string;
|
||||
statsYr: string;
|
||||
rowType: string;
|
||||
emphsMngSeCd: string;
|
||||
emphsMngSeNm: string;
|
||||
|
||||
totWholMnhlCnt: number;
|
||||
totStep1MnhlCnt: number;
|
||||
@@ -181,101 +181,9 @@ export interface FcltyManholeSummaryItem {
|
||||
totStep3MnhlCnt: number;
|
||||
totStep4MnhlCnt: number;
|
||||
totStep5MnhlCnt: number;
|
||||
/* --- yy14 (2014년 이전) --- */
|
||||
yy14WholMnhlCnt: number; // 합계
|
||||
yy14Step1MnhlCnt: number; // 1단계
|
||||
yy14Step2MnhlCnt: number; // 2단계
|
||||
yy14Step3MnhlCnt: number; // 3단계
|
||||
yy14Step4MnhlCnt: number; // 4단계
|
||||
yy14Step5MnhlCnt: number; // 5단계
|
||||
|
||||
/* --- yy15 (2014년) --- */
|
||||
yy15WholMnhlCnt: number;
|
||||
yy15Step1MnhlCnt: number;
|
||||
yy15Step2MnhlCnt: number;
|
||||
yy15Step3MnhlCnt: number;
|
||||
yy15Step4MnhlCnt: number;
|
||||
yy15Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy16 (2015년) --- */
|
||||
yy16WholMnhlCnt: number;
|
||||
yy16Step1MnhlCnt: number;
|
||||
yy16Step2MnhlCnt: number;
|
||||
yy16Step3MnhlCnt: number;
|
||||
yy16Step4MnhlCnt: number;
|
||||
yy16Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy17 (2016년) --- */
|
||||
yy17WholMnhlCnt: number;
|
||||
yy17Step1MnhlCnt: number;
|
||||
yy17Step2MnhlCnt: number;
|
||||
yy17Step3MnhlCnt: number;
|
||||
yy17Step4MnhlCnt: number;
|
||||
yy17Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy18 (2017년) --- */
|
||||
yy18WholMnhlCnt: number;
|
||||
yy18Step1MnhlCnt: number;
|
||||
yy18Step2MnhlCnt: number;
|
||||
yy18Step3MnhlCnt: number;
|
||||
yy18Step4MnhlCnt: number;
|
||||
yy18Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy19 (2018년) --- */
|
||||
yy19WholMnhlCnt: number;
|
||||
yy19Step1MnhlCnt: number;
|
||||
yy19Step2MnhlCnt: number;
|
||||
yy19Step3MnhlCnt: number;
|
||||
yy19Step4MnhlCnt: number;
|
||||
yy19Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy20 (2019년) --- */
|
||||
yy20WholMnhlCnt: number;
|
||||
yy20Step1MnhlCnt: number;
|
||||
yy20Step2MnhlCnt: number;
|
||||
yy20Step3MnhlCnt: number;
|
||||
yy20Step4MnhlCnt: number;
|
||||
yy20Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy21 (2020년) --- */
|
||||
yy21WholMnhlCnt: number;
|
||||
yy21Step1MnhlCnt: number;
|
||||
yy21Step2MnhlCnt: number;
|
||||
yy21Step3MnhlCnt: number;
|
||||
yy21Step4MnhlCnt: number;
|
||||
yy21Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy22 (2021년) --- */
|
||||
yy22WholMnhlCnt: number;
|
||||
yy22Step1MnhlCnt: number;
|
||||
yy22Step2MnhlCnt: number;
|
||||
yy22Step3MnhlCnt: number;
|
||||
yy22Step4MnhlCnt: number;
|
||||
yy22Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy23 (2022년) --- */
|
||||
yy23WholMnhlCnt: number;
|
||||
yy23Step1MnhlCnt: number;
|
||||
yy23Step2MnhlCnt: number;
|
||||
yy23Step3MnhlCnt: number;
|
||||
yy23Step4MnhlCnt: number;
|
||||
yy23Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy24 (2023년) --- */
|
||||
yy24WholMnhlCnt: number;
|
||||
yy24Step1MnhlCnt: number;
|
||||
yy24Step2MnhlCnt: number;
|
||||
yy24Step3MnhlCnt: number;
|
||||
yy24Step4MnhlCnt: number;
|
||||
yy24Step5MnhlCnt: number;
|
||||
|
||||
/* --- yy25 (2024년) --- */
|
||||
yy25WholMnhlCnt: number;
|
||||
yy25Step1MnhlCnt: number;
|
||||
yy25Step2MnhlCnt: number;
|
||||
yy25Step3MnhlCnt: number;
|
||||
yy25Step4MnhlCnt: number;
|
||||
yy25Step5MnhlCnt: number;
|
||||
/** 연도별 동적 필드 (wholMnhlCnt{year} ~ step5MnhlCnt{year}) */
|
||||
[key: string]: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user