From ca83ac22aa9e70a7329b98cc849be4dd3893eaa7 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Mon, 16 Feb 2026 15:03:38 +0900 Subject: [PATCH 001/150] =?UTF-8?q?feat:=2002.=ED=95=98=EC=88=98=EA=B4=80?= =?UTF-8?q?=EB=A1=9C=20tab3=EA=B4=80=EC=A2=85=EB=B3=84=20=ED=98=84?= =?UTF-8?q?=ED=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/review/SewagePipeRvw.tsx | 3 + .../util/column/review/SewagePipeRvwColumn.ts | 166 +++++++++++++----- .../api/statistics-management-service.ts | 27 ++- ...statistics-sewage-pipe-review-interface.ts | 47 +++++ 4 files changed, 199 insertions(+), 44 deletions(-) diff --git a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx index 34fe00910..f8129ad85 100644 --- a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx @@ -94,9 +94,12 @@ const SewagePipeRvw = () => { if (tabKey === 'b') { response = await statisticsManagementApi.getSewagePipeFacilityList(searchParamRef.current); + }else if(tabKey === 'c'){ + response = await statisticsManagementApi.getSewagePipeTypeList(searchParamRef.current); } if (response?.status === 'success' && response.data) { + setRowDataMap(prev => { const next = new Map(prev); next.set(tabKey, toGridRows(response.data as object[])); diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts index 7bcb3a41f..a6e3d79b4 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -56,7 +56,7 @@ const rawList = [ ] }, { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, editable: false }, + { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, mergeCells: true,editable: false }, { name: 'displayYear', fieldName: 'displayYear', @@ -235,7 +235,7 @@ const rawList = [ ] }, { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'bscFctNm', fieldName: 'bscFctNm', header: { text: '하수처리장명' }, editable: false }, + { name: 'bscFctNm', fieldName: 'bscFctNm', header: { text: '하수처리장명' },mergeCells: true, editable: false }, { name: 'displayYear', fieldName: 'displayYear', @@ -351,47 +351,133 @@ const rawList = [ 'rmk' ] }, - { - tabKey: 'c', - tabLabel: '관종별 현황', - rowStartNum: 2, - fixedColumns: 3, - columns: [ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'SWRG_PIPE_NM', fieldName: '관종', header: { text: '관종' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, + { + tabKey: 'c', + tabLabel: '관종별 현황', + rowStartNum: 2, + fixedColumns: 3, + columns: [ + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true, editable: false }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + } + ] + }, - { name: 'TOTAL_EXTN_SUM', fieldName: '계(총계)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'LAYING_EXTN_SUM', fieldName: '매설연장(m)_총계', header: { text: '매설연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'REMVL_EXTN_SUM', fieldName: '철거연장(m)_총계', header: { text: '철거연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 총계 필드 */ + { name: 'totalExtnSum', fieldName: 'totalExtnSum', header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'layingExtnSum', fieldName: 'layingExtnSum', header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'remvlExtnSum', fieldName: 'remvlExtnSum', header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - ...Array.from({ length: 30 }, (_, i) => { - const yNum = (30 - i).toString().padStart(2, '0'); - const yrOffset = 29 - i; - return [ - { name: `TOTAL_EXTN_${yNum}Y`, fieldName: `계(-${yrOffset}년)`, header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: `LAYING_EXTN_${yNum}Y`, fieldName: `매설연장(m)_(-${yrOffset}년)`, header: { text: '매설연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: `REMVL_EXTN_${yNum}Y`, fieldName: `철거연장(m)_(-${yrOffset}년)`, header: { text: '철거연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' } - ]; - }).flat(), + /* 30개년 동적 컬럼 생성 (SewerPipeTypeItem 필드 매핑) */ + ...Array.from({ length: 30 }, (_, i) => { + const yNum = (30 - i).toString().padStart(2, '0'); + const yrOffset = 29 - i; + return [ + { name: `totalExtn${yNum}y`, fieldName: `totalExtn${yNum}y`, header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: `layingExtn${yNum}y`, fieldName: `layingExtn${yNum}y`, header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: `remvlExtn${yNum}y`, fieldName: `remvlExtn${yNum}y`, header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' } + ]; + }).flat(), - { name: 'LAYING_EXTN', fieldName: `매설연장(m)_당해년도`, header: { text: '매설연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, - ], - columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'SWRG_PIPE_NM', 'YR_GBN', - { name: 'summaryGroup', header: { text: '총계' }, items: ['TOTAL_EXTN_SUM', 'LAYING_EXTN_SUM', 'REMVL_EXTN_SUM'] }, - ...Array.from({ length: 30 }, (_, i) => { - const yNum = (30 - i).toString().padStart(2, '0'); - const yrOffset = 29 - i; - return { name: `yr${yNum}Group`, header: { text: `-${yrOffset}년` }, items: [`TOTAL_EXTN_${yNum}Y`, `LAYING_EXTN_${yNum}Y`, `REMVL_EXTN_${yNum}Y`] }; - }), - { name: 'currentYrGroup', header: { text: `${baseYear}년` }, items: ['LAYING_EXTN'] }, - 'RMK' - ] - }, + { name: 'layingExtn', fieldName: 'layingExtn', header: { text: '매설연장(m)\n(당해년도)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'areaGroup', + header: { text: '행정구역' }, + items: [ + // sido부터 swrgPipeNm(관종)까지 푸터 병합 + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', + 'swrgPipeNm', + ] + }, + 'displayYear', + { name: 'summaryGroup', header: { text: '총계' }, items: ['totalExtnSum', 'layingExtnSum', 'remvlExtnSum'] }, + + ...Array.from({ length: 30 }, (_, i) => { + const yNum = (30 - i).toString().padStart(2, '0'); + const yrOffset = 29 - i; + return { + name: `yr${yNum}Group`, + header: { text: `-${yrOffset}년` }, + items: [`totalExtn${yNum}y`, `layingExtn${yNum}y`, `remvlExtn${yNum}y`] + }; + }), + + { name: 'currentYrGroup', header: { text: `${baseYear}년` }, items: ['layingExtn'] }, + 'rmk' + ] +}, { tabKey: 'd', tabLabel: '관경별 현황', diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index 89718aaf0..5fc81ab34 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -5,17 +5,17 @@ import { ENDPOINT } from "@/shared/config"; import { type StatsInputStatusReviewApiResponse } from "../model/statistics-input-review-interface"; -import { - type SewagePipeReviewRegionalApiResponse -} from "../model/statistics-sewage-pipe-review-interface"; import { type SewerageSupplyRateReviewRequestParams, type SewerageSupplyRateReviewApiResponse, } from "../model/statistics-sewerage-penetration-review-interface"; import { - type SewagePipeFacilityApiResponse + type SewagePipeReviewRegionalApiResponse , + type SewagePipeFacilityApiResponse, + type SewerPipeTypeApiResponse, } from "../model/statistics-sewage-pipe-review-interface"; + export class StatisticsManagementApiService { /** * 00. 통계 입력 현황 목록 조회 @@ -93,6 +93,25 @@ export class StatisticsManagementApiService { ); return response.data; } + + /** + * 02. 하수관로 관종별 시설현황 조회 (Tab2) + * @param params (sido, gugun, yr, bsnmCd) + */ + public async getSewagePipeTypeList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/type-status`, + { params } + ); + return response.data; + } } // 통합 인스턴스 수출 diff --git a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts index 0d82096ad..ea7500cf5 100644 --- a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts @@ -149,4 +149,51 @@ export interface SewagePipeFacilityService { sido?: string; // 시도 gugun?: string; // 구군 }): Promise; +} + +/** + * 02. 하수관로 관종별 시설현황 (PipeType) + * + */ +export interface SewerPipeTypeItem { + // --- SewerPipeTab3Response 기본 필드 --- + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + swrgPipeNm: string; // 관종명 + displayYear: string; // 구분 표시용 (예: 2024년확정) + yr: string; // 연도 + rmk: string; // 비고 + yrGbn?: string; // 구분 (매설/철거 등 행 구분용 - 그리드 설정 기준) + + // --- BasePipeExtnResponse 상속 필드 --- + layingExtn: number; // 매설연장(m) - 당해년도 + + // 총계(Sum) 필드 (그리드 설정에서 사용하는 합계용 필드) + totalExtnSum?: number; + layingExtnSum?: number; + remvlExtnSum?: number; + + // 01y ~ 30y 동적 필드 처리 (Index Signature) + // 예: totalExtn01y, layingExtn05y, remvlExtn30y 등 + [key: `totalExtn${string}y`]: number | undefined; + [key: `layingExtn${string}y`]: number | undefined; + [key: `remvlExtn${string}y`]: number | undefined; +} + + +export interface SewerPipeTypeResponseData { + pipeTypeStatusList: SewerPipeTypeItem[]; +} + +export type SewerPipeTypeApiResponse = ApiResponse; + +export interface SewagePipeTypeService { + + getSewagePipeTypeList(params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업장 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + }): Promise; } \ No newline at end of file From 37e3b6ebdb832d03f7d8feb50c42316f2de168e1 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Mon, 16 Feb 2026 16:33:25 +0900 Subject: [PATCH 002/150] =?UTF-8?q?feat:=2002.=ED=95=98=EC=88=98=EA=B4=80?= =?UTF-8?q?=EB=A1=9C=20tab4=EA=B4=80=EA=B2=BD=EB=B3=84=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/review/SewagePipeRvw.tsx | 2 + .../util/column/review/SewagePipeRvwColumn.ts | 155 ++++++++++++++---- .../api/statistics-management-service.ts | 22 ++- ...statistics-sewage-pipe-review-interface.ts | 46 +++++- 4 files changed, 193 insertions(+), 32 deletions(-) diff --git a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx index f8129ad85..ac7033e53 100644 --- a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx @@ -96,6 +96,8 @@ const SewagePipeRvw = () => { response = await statisticsManagementApi.getSewagePipeFacilityList(searchParamRef.current); }else if(tabKey === 'c'){ response = await statisticsManagementApi.getSewagePipeTypeList(searchParamRef.current); + }else if(tabKey === 'd'){ + response = await statisticsManagementApi.getSewagePipeDiamList(searchParamRef.current); } if (response?.status === 'success' && response.data) { diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts index a6e3d79b4..b57a54d44 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -436,7 +436,6 @@ const rawList = [ { name: 'layingExtnSum', fieldName: 'layingExtnSum', header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, { name: 'remvlExtnSum', fieldName: 'remvlExtnSum', header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 30개년 동적 컬럼 생성 (SewerPipeTypeItem 필드 매핑) */ ...Array.from({ length: 30 }, (_, i) => { const yNum = (30 - i).toString().padStart(2, '0'); const yrOffset = 29 - i; @@ -455,7 +454,7 @@ const rawList = [ name: 'areaGroup', header: { text: '행정구역' }, items: [ - // sido부터 swrgPipeNm(관종)까지 푸터 병합 + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, 'gugun', 'swrgPipeNm', @@ -478,33 +477,131 @@ const rawList = [ 'rmk' ] }, - { - tabKey: 'd', - tabLabel: '관경별 현황', - rowStartNum: 2, - fixedColumns: 3, - columns: [ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '구군' } }, - { name: 'SWRG_PIPE_NM', fieldName: '관종', header: { text: '관종' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - { name: 'TOTAL_EXTN_M', fieldName: '총계(m)', header: { text: '총계(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'UNDER_200', fieldName: '200mm 미만', header: { text: '200mm 미만' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OVER_200_UNDEV_300', fieldName: '200mm이상~300mm미만', header: { text: '200mm이상~\n300mm미만' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OVER_300_UNDEV_500', fieldName: '300mm이상~500mm미만', header: { text: '300mm이상~\n500mm미만' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OVER_500_UNDEV_800', fieldName: '500mm이상~800mm미만', header: { text: '500mm이상~\n800mm미만' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OVER_800', fieldName: '800mm 이상', header: { text: '800mm 이상' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'BOX', fieldName: '사각형(m)', header: { text: '사각형(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'ETC', fieldName: '기타(측구/개거 등)', header: { text: '기타\n(측구/개거 등)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, - ], - columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'SWRG_PIPE_NM', 'YR_GBN', 'TOTAL_EXTN_M', - { name: 'circleGroup', header: { text: '원형(m)' }, items: ['UNDER_200', 'OVER_200_UNDEV_300', 'OVER_300_UNDEV_500', 'OVER_500_UNDEV_800', 'OVER_800'] }, - 'BOX', 'ETC', 'RMK', - ] - } + { + tabKey: 'd', + tabLabel: '관경별 현황', + rowStartNum: 2, + fixedColumns: 3, + columns: [ + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true, editable: false }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + } + ] + }, + + { name: 'totalExtnM', fieldName: 'totalExtnM', header: { text: '총계(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'under200', fieldName: 'under200', header: { text: '200mm 미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over200Undev300', fieldName: 'over200Undev300', header: { text: '200mm이상~\n300mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over300Undev500', fieldName: 'over300Undev500', header: { text: '300mm이상~\n500mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over500Undev500', fieldName: 'over500Undev500', header: { text: '500mm이상~\n800mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over800', fieldName: 'over800', header: { text: '800mm 이상' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'box', fieldName: 'box', header: { text: '사각형(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'etc', fieldName: 'etc', header: { text: '기타\n(측구/개거 등)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', + 'swrgPipeNm', + ] + }, + 'displayYear', + 'totalExtnM', + { + name: 'circleGroup', + header: { text: '원형(m)' }, + items: ['under200', 'over200Undev300', 'over300Undev500', 'over500Undev500', 'over800'] + }, + 'box', + 'etc', + 'rmk' + ] +} ]; export const SewagePipeColumnInfoListRvw = rawList.map(tab => ({ diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index 5fc81ab34..4413a8e1a 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -13,6 +13,7 @@ import { type SewagePipeReviewRegionalApiResponse , type SewagePipeFacilityApiResponse, type SewerPipeTypeApiResponse, + type SewerPipeDiamApiResponse } from "../model/statistics-sewage-pipe-review-interface"; @@ -95,7 +96,7 @@ export class StatisticsManagementApiService { } /** - * 02. 하수관로 관종별 시설현황 조회 (Tab2) + * 02. 하수관로 관종별 시설현황 조회 (Tab3) * @param params (sido, gugun, yr, bsnmCd) */ public async getSewagePipeTypeList( @@ -112,6 +113,25 @@ export class StatisticsManagementApiService { ); return response.data; } + + /** + * 02. 하수관로 관경별 시설현황 조회 (Tab 4) + * @param params (sido, gugun, yr, bsnmCd) + */ + public async getSewagePipeDiamList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/diam-status`, + { params } + ); + return response.data; + } } // 통합 인스턴스 수출 diff --git a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts index ea7500cf5..6abb98a80 100644 --- a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts @@ -82,7 +82,10 @@ export interface SewagePipeReviewService { }): Promise; } - +/** + * 02. 하수관로 시설별 시설현황 (Tab 2) + * + */ export interface SewagePipeReviewFacilityItem { // 1. 행정구역 및 기본 정보 sido: string; // 시도 @@ -152,7 +155,7 @@ export interface SewagePipeFacilityService { } /** - * 02. 하수관로 관종별 시설현황 (PipeType) + * 02. 하수관로 관종별 시설현황 (Tab 3) * */ export interface SewerPipeTypeItem { @@ -196,4 +199,43 @@ export interface SewagePipeTypeService { sido?: string; // 시도 gugun?: string; // 구군 }): Promise; +} + +/** + * 02. 하수관로 관경별 시설현황 아이템 (Tab 4) + */ +export interface SewerPipeDiamItem { + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + swrgPipeNm: string; // 관종명 (또는 관경 관련 명칭) + displayYear: string; // 구분 (예: 2023년 확정 / 2024년) + + // 관경별 수치 데이터 + under200: number; // 200미만 + over200Undev300: number; // 200~300 + over300Undev500: number; // 300~500 + over500Undev500: number; // 500~800 (DTO 명칭 기준) + over800: number; // 800이상 + box: number; // BOX + etc: number; // 기타 + rmk: string; // 비고 +} + + +export interface SewerPipeDiamResponseData { + pipeDiamStatusList: SewerPipeDiamItem[]; +} + + +export type SewerPipeDiamApiResponse = ApiResponse; + + +export interface SewagePipeDiamService { + getSewagePipeDiamList(params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업장 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + }): Promise; } \ No newline at end of file From 4b883a30c534f4639eda495c2446ff5a64f5e427 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Mon, 16 Feb 2026 17:47:28 +0900 Subject: [PATCH 003/150] =?UTF-8?q?feat:=2003.=ED=95=98=EC=88=98=EA=B4=80?= =?UTF-8?q?=EB=A1=9C=20=EC=9C=A0=EC=A7=80=EA=B4=80=EB=A6=AC=20tab1=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/SewagePipeMaintainRvw.tsx | 158 ++++++++++++++--- .../review/SewagePipeMaintainRvwColumn.ts | 162 ++++++++++++++---- .../api/statistics-management-service.ts | 22 +++ ...istics-sewage-pipe-mtc-review-interface.ts | 46 +++++ 4 files changed, 328 insertions(+), 60 deletions(-) create mode 100644 apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts diff --git a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx index 03549083e..8d66c5e0f 100644 --- a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx @@ -1,51 +1,159 @@ -import { useState } from "react"; - -import { TabDataGrid } from "@rsone/components"; +import { useState, useMemo, useRef } from "react"; +import TabDataGrid from "@/components/module/TabDataGrid"; import SearchForm from "@/components/module/SearchForm"; import SaveButton from "@/components/button/SaveButton"; import ApproveButton from "@/components/button/ApproveButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReturnButton from "@/components/button/ReturnButton"; +import { LoadingWithAnimation } from "@common/components"; +import { useAlertStore } from "@/shared/lib"; +import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn"; +import type { ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { toGridRows } from "@/components/util/grid/gridFunction"; -// 하수도 통계 관리 03. 하수관로 유지 관리 +// 하수도 통계 검토 03. 하수관로 유지 관리 const SewagePipeMaintainRvw = () => { + const { setMessage: alert } = useAlertStore((state) => state); const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); + const [isLoading, setIsLoading] = useState(false); + const [activeTab, setActiveTab] = useState('a'); - const approveFunc = () => { - console.log('approveFunc'); - // 승인 로직 + const searchParamRef = useRef(null); + + const columnInfo = useMemo(() => { + return SewagePipeMaintainColumnInfoList.map((tab: ColumnInfoList) => ({ + ...tab, + columns: (tab.columns ?? []).map((col: ColumnConfig) => ({ + ...col, + editable: false as const, + styleName: col.styleName ?? ("rg-cell-readonly" as const), + })), + })); + }, []); + + const handleReset = () => { + setRowDataMap(new Map()); + setActiveTab('a'); + searchParamRef.current = null; }; - const saveFunc = () => { - console.log('submit'); - // 저장 로직 + const handleSearch = async (values: SearchFormValues) => { + if (!values.sido || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + + setActiveTab('a'); + setRowDataMap(new Map()); + + try { + setIsLoading(true); + + const searchParam = { + sido: values.sido, + gugun: values.gugun === 'ALL' ? '' : values.gugun, + bsnmCd: values.bsnmCd, + yr: values.year, + }; + + searchParamRef.current = searchParam; + const response = await statisticsManagementApi.getSewerPipeMtcList(searchParam); + + if (response?.status === 'success' && response.data) { + const newRowDataMap = new Map[]>(); + + // API 응답이 배열을 직접 반환하는 경우와 객체로 감싸진 경우 모두 처리 + const listData = Array.isArray(response.data) + ? response.data + : (response.data.mtcStatusList || []); + + newRowDataMap.set('a', toGridRows(listData as object[])); + setRowDataMap(newRowDataMap); + } else { + alert(response?.message || "데이터가 없습니다."); + } + } catch (error) { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } }; - const reviewOpinionFunc = () => { - console.log('reviewOpinionFunc'); - // 검토의견 작성 - }; + const handleActiveTabChange = async (tabKey: string) => { + setActiveTab(tabKey); - const returnFunc = () => { - console.log('returnFunc'); - // 반려 + const existingAData = rowDataMap.get('a'); + const activeTabData = rowDataMap.get(tabKey); + + const hasAData = Array.isArray(existingAData) && existingAData.length > 0; + const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0; + + if (hasAData && !hasActiveTabData && searchParamRef.current) { + try { + setIsLoading(true); + + let response; + + if (tabKey === 'a') { + response = await statisticsManagementApi.getSewerPipeMtcList(searchParamRef.current); + } + + if (response?.status === 'success' && response.data) { + setRowDataMap(prev => { + const next = new Map(prev); + + // API 응답이 배열을 직접 반환하는 경우와 객체로 감싸진 경우 모두 처리 + const listData = Array.isArray(response.data) + ? response.data + : (response.data.mtcStatusList || []); + + next.set(tabKey, toGridRows(listData as object[])); + return next; + }); + } else { + alert(response?.message || "데이터가 없습니다."); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + } }; return ( - <> - +
+ +
- - - - + { }} /> + { }} /> + { }} /> + { }} />
- - + + {isLoading && ( +
+
+ +
+
+ )} + + +
); }; diff --git a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts index 28aa25ce5..b1e7b9f89 100644 --- a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts @@ -1,64 +1,156 @@ import { ValueType } from "realgrid"; +import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; +import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; // 하수도 통계 관리 03. 하수관로 유지 관리 const rawMaintainList = [ - { + { tabKey: 'a', tabLabel: '개보수', rowStartNum: 4, fixedColumns: 2, columns: [ - /* 1. 행정 및 기본 정보 */ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, + /* 1. 행정구역 (시도) */ + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + + /* 2. 구분 (displayYear) */ + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : ''; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : ''; + } + } + ] + }, - /* 2. 전체보수 (단위: m) */ - { name: 'RPAIR_PIPE_SM', fieldName: '계(전체보수)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_CMBN_PIPE', fieldName: '합류식(전체보수)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_RAINWTR_PIPE', fieldName: '우수(전체보수)', header: { text: '우수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_PLTWTR_PIPE', fieldName: '오수(전체보수)', header: { text: '오수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 3. 전체보수 (m) - 소수점 1자리 합계 */ + { name: 'rpairPipeSm', fieldName: 'rpairPipeSm', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rpairCmbnPipe', fieldName: 'rpairCmbnPipe', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rpairRainwtrPipe', fieldName: 'rpairRainwtrPipe', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rpairPltwtrPipe', fieldName: 'rpairPltwtrPipe', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 3. 부분보수 (단위: 개소) */ - { name: 'RPAIR_PIPE_SM2', fieldName: '계(부분보수)', header: { text: '계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_CMBN_PIPE2', fieldName: '합류식(부분보수)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_RAINWTR_PIPE2', fieldName: '우수(부분보수)', header: { text: '우수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RPAIR_PLTWTR_PIPE2', fieldName: '오수(부분보수)', header: { text: '오수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 4. 부분보수 (개소) - 정수 합계 */ + { name: 'rpairPipeSm2', fieldName: 'rpairPipeSm2', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rpairCmbnPipe2', fieldName: 'rpairCmbnPipe2', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rpairRainwtrPipe2', fieldName: 'rpairRainwtrPipe2', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rpairPltwtrPipe2', fieldName: 'rpairPltwtrPipe2', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 4. 부속시설 (개소) */ - { name: 'MNHLE_SITE', fieldName: '합류식 맨홀', header: { text: '합류식\n맨홀' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PLTWTR_MNHLE_SITE', fieldName: '오수맨홀', header: { text: '오수맨홀' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'RAINWTR_MNHLE_SITE', fieldName: '우수맨홀', header: { text: '우수맨홀' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PLTWTR_RCVR_SITE', fieldName: '오수받이(개소)', header: { text: '오수받이(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'RAINWTR_RCVR_SITE', fieldName: '빗물받이(개소)', header: { text: '빗물받이(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SOILHOLE_SITE', fieldName: '토실(개소)', header: { text: '토실(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SOILSPLL_SITE', fieldName: '토구(개소)', header: { text: '토구(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + /* 5. 부속시설 (개소) - 정수 합계 */ + { name: 'mnhleSite', fieldName: 'mnhleSite', header: { text: '합류식\n맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrMnhleSite', fieldName: 'pltwtrMnhleSite', header: { text: '오수맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrMnhleSite', fieldName: 'rainwtrMnhleSite', header: { text: '우수맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrRcvrSite', fieldName: 'pltwtrRcvrSite', header: { text: '오수받이' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrRcvrSite', fieldName: 'rainwtrRcvrSite', header: { text: '빗물받이' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'soilspllSite', fieldName: 'soilspllSite', header: { text: '토실' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'soilholeSite', fieldName: 'soilholeSite', header: { text: '토구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 5. 조사량 및 비고 */ - { name: 'CCTV', fieldName: '수밀검사·CCTV조사량(m)', header: { text: '수밀검사·\nCCTV조사량\n(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + /* 6. 조사량 및 비고 */ + { name: 'cctv', fieldName: 'cctv', header: { text: '수밀검사·\nCCTV조사량\n(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'YR_GBN', + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ + { column: 'sido', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, + 'gugun' + ] + }, + 'displayYear', { name: 'mtcPipeGroup', header: { text: '개·보수관로' }, items: [ { name: 'totalRepairGroup', header: { text: '전체보수(m)' }, - items: ['RPAIR_PIPE_SM', 'RPAIR_CMBN_PIPE', { name: 'sepTotalGroup', header: { text: '분류식' }, items: ['RPAIR_RAINWTR_PIPE', 'RPAIR_PLTWTR_PIPE'] }] + items: ['rpairPipeSm', 'rpairCmbnPipe', { name: 'sepTotalGroup', header: { text: '분류식' }, items: ['rpairRainwtrPipe', 'rpairPltwtrPipe'] }] }, { name: 'partRepairGroup', header: { text: '부분보수(개소)' }, - items: ['RPAIR_PIPE_SM2', 'RPAIR_CMBN_PIPE2', { name: 'sepPartGroup', header: { text: '분류식' }, items: ['RPAIR_RAINWTR_PIPE2', 'RPAIR_PLTWTR_PIPE2'] }] + items: ['rpairPipeSm2', 'rpairCmbnPipe2', { name: 'sepPartGroup', header: { text: '분류식' }, items: ['rpairRainwtrPipe2', 'rpairPltwtrPipe2'] }] } ] }, - 'MNHLE_SITE', - { name: 'sepMnhleGroup', header: { text: '분류식' }, items: ['PLTWTR_MNHLE_SITE', 'RAINWTR_MNHLE_SITE'] }, - 'PLTWTR_RCVR_SITE', 'RAINWTR_RCVR_SITE', - { name: 'soilGroup', header: { text: '토실·토구(개소)' }, items: ['SOILHOLE_SITE', 'SOILSPLL_SITE'] }, - 'CCTV', 'RMK' + 'mnhleSite', + { name: 'sepMnhleGroup', header: { text: '분류식' }, items: ['pltwtrMnhleSite', 'rainwtrMnhleSite'] }, + 'pltwtrRcvrSite', 'rainwtrRcvrSite', + { name: 'soilGroup', header: { text: '토실·토구(개소)' }, items: ['soilspllSite', 'soilholeSite'] }, + 'cctv', 'rmk' ] }, { diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index 4413a8e1a..30ff9fb98 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -15,6 +15,9 @@ import { type SewerPipeTypeApiResponse, type SewerPipeDiamApiResponse } from "../model/statistics-sewage-pipe-review-interface"; +import { + type SewerPipeMtcApiResponse +} from "../model/statistics-sewage-pipe-mtc-review-interface"; export class StatisticsManagementApiService { @@ -132,6 +135,25 @@ export class StatisticsManagementApiService { ); return response.data; } + + /** + * 03. 하수관로 유지관리 개보수 현황 조회 + * @param params (yr, bsnmCd, sido, gugun) + */ +public async getSewerPipeMtcList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } +): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe-mtc/repair`, + { params } + ); + return response.data; +} } // 통합 인스턴스 수출 diff --git a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts new file mode 100644 index 000000000..5bab50b28 --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts @@ -0,0 +1,46 @@ +import type { ApiResponse } from "./statistics-interface"; + +/** + * 03. 하수관로 유지관리 개보수 아이템 + */ +export interface SewerPipeMtcItem { + // 1. 행정구역 및 기본정보 + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + adstDong?: string; // 행정동 + yr: string; // 실제 연도 + displayYear: string; // 화면 표시용 (예: 2023년확정) + rowType: 'OLD' | 'CURR'; // OLD / CURR 구분 + + // 2. 개보수 관로 - 전체보수(m) + rpairPipeSm: number; // 계 + rpairCmbnPipe: number; // 합류식 + rpairRainwtrPipe: number; // 분류식-우수 + rpairPltwtrPipe: number; // 분류식-오수 + + // 3. 개보수 관로 - 부분보수(개소) + rpairPipeSm2: number; // 계 + rpairCmbnPipe2: number; // 합류식 + rpairRainwtrPipe2: number; // 분류식-우수 + rpairPltwtrPipe2: number; // 분류식-오수 + + // 4. 부속 시설물 (개소/m) + mnhleSite: number; // 합류식 맨홀 + pltwtrMnhleSite: number; // 오수 맨홀 (분류식) + rainwtrMnhleSite: number; // 우수 맨홀 (분류식) + pltwtrRcvrSite: number; // 오수받이 + rainwtrRcvrSite: number; // 빗물받이 + soilspllSite: number; // 토실 + soilholeSite: number; // 토구 + cctv: number; // CCTV 조사량 (m) + + // 5. 기타 및 상태값 + rmk: string; // 비고 +} + +export interface SewerPipeMtcResponseData { + mtcStatusList: SewerPipeMtcItem[]; +} + +export type SewerPipeMtcApiResponse = ApiResponse; \ No newline at end of file From be21ea3e8a087c1cd700353cb1e87ed64a2b7637 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Mon, 16 Feb 2026 17:58:41 +0900 Subject: [PATCH 004/150] =?UTF-8?q?feat:=2003.=ED=95=98=EC=88=98=EA=B4=80?= =?UTF-8?q?=EB=A1=9C=20=EC=9C=A0=EC=A7=80=EA=B4=80=EB=A6=AC=20tab2?= =?UTF-8?q?=EA=B4=80=EB=A1=9C=EC=A4=80=EC=84=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/SewagePipeMaintainRvw.tsx | 2 + .../review/SewagePipeMaintainRvwColumn.ts | 187 ++++++++++++------ .../api/statistics-management-service.ts | 22 ++- ...istics-sewage-pipe-mtc-review-interface.ts | 55 +++++- 4 files changed, 204 insertions(+), 62 deletions(-) diff --git a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx index 8d66c5e0f..f2da6cce4 100644 --- a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx @@ -99,6 +99,8 @@ const SewagePipeMaintainRvw = () => { if (tabKey === 'a') { response = await statisticsManagementApi.getSewerPipeMtcList(searchParamRef.current); + }else if (tabKey === 'b') { + response = await statisticsManagementApi.getSewerPipeDrdgList(searchParamRef.current); } if (response?.status === 'success' && response.data) { diff --git a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts index b1e7b9f89..ab32f39a5 100644 --- a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts @@ -153,71 +153,138 @@ const rawMaintainList = [ 'cctv', 'rmk' ] }, - { - tabKey: 'b', - tabLabel: '관로준설', - rowStartNum: 4, - fixedColumns: 2, - columns: [ - /* 1. 행정 및 기본 정보 */ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - - /* 2. 시설 기본 정보 */ - { name: 'PIPL_FCLTY_EXTN', fieldName: '하수관로 총시설연장(m)', header: { text: '하수관로\n총시설연장\n(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { + tabKey: 'b', + tabLabel: '관로준설', + rowStartNum: 4, + fixedColumns: 2, + columns: [ + /* 1. 행정 및 기본 정보 */ + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const uniqueValues = new Set(); + for (let i = 0; i < grid.getDataSource().getRowCount(); i++) { + const val = grid.getDataSource().getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > 0 ? divisionValues[0] + ' 합계' : ''; + } + }, + { + valueCallback: (grid: GridView) => { + const uniqueValues = new Set(); + for (let i = 0; i < grid.getDataSource().getRowCount(); i++) { + const val = grid.getDataSource().getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > 1 ? divisionValues[1] + ' 합계' : ''; + } + } + ] + }, + + /* 2. 시설 기본 정보 */ + { name: 'piplFcltyExtn', fieldName: 'piplFcltyExtn', header: { text: '하수관로\n총시설연장\n(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 3. 준설연장 및 개소수 */ - { name: 'DRDG_PIPE_SM', fieldName: '합계(준설)', header: { text: '합계' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DRDG_CMBN_PIPE', fieldName: '합류식(준설)', header: { text: '합류식' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DRDG_RAINWTR_PIPE', fieldName: '우수(준설)', header: { text: '우수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DRDG_PLTWTR_PIPE', fieldName: '오수(준설)', header: { text: '오수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'MNHLE_SITE_DRDG', fieldName: '맨홀(개소)', header: { text: '맨홀\n(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PLTWTR_RCVR_SITE_DRDG', fieldName: '오·우수받이(개소)', header: { text: '오·우수받이\n(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SOILSPLL_SOILHOLE_SITE', fieldName: '토실·토구(개소)', header: { text: '토실·토구\n(개소)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + /* 3. 준설연장 및 개소수 */ + { name: 'drdgPipeSm', fieldName: 'drdgPipeSm', header: { text: '합계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgCmbnPipe', fieldName: 'drdgCmbnPipe', header: { text: '합류식' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgRainwtrPipe', fieldName: 'drdgRainwtrPipe', header: { text: '우수' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgPltwtrPipe', fieldName: 'drdgPltwtrPipe', header: { text: '오수' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'mnhleSite', fieldName: 'mnhleSite', header: { text: '맨홀\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrRcvrSite', fieldName: 'pltwtrRcvrSite', header: { text: '오·우수받이\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'soilspllSoilholeSite', fieldName: 'soilspllSoilholeSite', header: { text: '토실·토구\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 4. 준설 계획 및 율 */ - { name: 'DRDG_TRGET_EXTN', fieldName: '준설 계획 연장(m)', header: { text: '준설 계획\n연장(m)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DRDG_TRGET_RT', fieldName: '계획대비 준설율(%)', header: { text: '계획대비\n준설율\n(%)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PIPL_FCLTY_RT', fieldName: '준설계획 연장대비 준설율(%)', header: { text: '준설계획\n연장대비\n준설율(%)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 4. 준설 계획 및 율 */ + { name: 'drdgTrgetExtn', fieldName: 'drdgTrgetExtn', header: { text: '준설 계획\n연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgTrgetRt', fieldName: 'drdgTrgetRt', header: { text: '계획대비\n준설율\n(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'piplFcltyRt', fieldName: 'piplFcltyRt', header: { text: '준설계획\n연장대비\n준설율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 5. 준설실적 */ - { name: 'DRDG_ACMSLT_AMOUNT', fieldName: '금액(백만원)', header: { text: '금액\n(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'DRDG_ACMSLT_QY', fieldName: '준설량(㎥)', header: { text: '준설량\n(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + /* 5. 준설실적 */ + { name: 'drdgAcmsltAmount', fieldName: 'drdgAcmsltAmount', header: { text: '금액\n(백만원)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drdgAcmsltQy', fieldName: 'drdgAcmsltQy', header: { text: '준설량\n(㎥)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 6. 준설토사처리 */ - { name: 'DRDG_ACMSLT_UNTPC', fieldName: '준설토 처리비(백만원)', header: { text: '준설토\n처리비\n(백만원)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'DRDG_ETND_SM', fieldName: '처리량 계(톤)', header: { text: '처리량 계\n(톤)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RCLM', fieldName: '매립', header: { text: '매립' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'OCEAN_DPSL', fieldName: '해양투기', header: { text: '해양투기' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RUSE', fieldName: '재활용', header: { text: '재활용' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'ETC', fieldName: '기타(준설)', header: { text: '기타\n(소각, 고형화,\n시멘트원료 등)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 6. 준설토사처리 */ + { name: 'drdgAcmsltUntpc', fieldName: 'drdgAcmsltUntpc', header: { text: '준설토\n처리비\n(백만원)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drdgEtndSm', fieldName: 'drdgEtndSm', header: { text: '처리량 계\n(톤)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rclm', fieldName: 'rclm', header: { text: '매립' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'oceanDpsl', fieldName: 'oceanDpsl', header: { text: '해양투기' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'ruse', fieldName: 'ruse', header: { text: '재활용' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'etc', fieldName: 'etc', header: { text: '기타' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 7. 기타 및 의견 */ - { name: 'RMK_DRDG', fieldName: '비고(준설)', header: { text: '비고' } }, - ], - columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'YR_GBN', 'PIPL_FCLTY_EXTN', - { - name: 'drdgGroup', header: { text: '준설연장 및 개소수' }, - items: [ - { - name: 'pipeLenGroup', header: { text: '관로(m)' }, - items: ['DRDG_PIPE_SM', 'DRDG_CMBN_PIPE', { name: 'sepRepairGroup', header: { text: '분류식' }, items: ['DRDG_RAINWTR_PIPE', 'DRDG_PLTWTR_PIPE'] }] - }, - 'MNHLE_SITE_DRDG', 'PLTWTR_RCVR_SITE_DRDG', 'SOILSPLL_SOILHOLE_SITE' - ] - }, - 'DRDG_TRGET_EXTN', 'DRDG_TRGET_RT', 'PIPL_FCLTY_RT', - { name: 'acmsltGroup', header: { text: '준설실적' }, items: ['DRDG_ACMSLT_AMOUNT', 'DRDG_ACMSLT_QY'] }, - { - name: 'processGroup', header: { text: '준설토사처리' }, - items: ['DRDG_ACMSLT_UNTPC', 'DRDG_ETND_SM', 'RCLM', 'OCEAN_DPSL', 'RUSE', 'ETC'] - }, - 'RMK_DRDG' - ] - } + /* 7. 기타 및 의견 */ + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ + { name: 'addrGroup', header: { text: '행정구역' }, items: [{ column: 'sido', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, 'gugun'] }, + 'displayYear', 'piplFcltyExtn', + { + name: 'drdgGroup', header: { text: '준설연장 및 개소수' }, + items: [ + { + name: 'pipeLenGroup', header: { text: '관로(m)' }, + items: ['drdgPipeSm', 'drdgCmbnPipe', { name: 'sepRepairGroup', header: { text: '분류식' }, items: ['drdgRainwtrPipe', 'drdgPltwtrPipe'] }] + }, + 'mnhleSite', 'pltwtrRcvrSite', 'soilspllSoilholeSite' + ] + }, + 'drdgTrgetExtn', 'drdgTrgetRt', 'piplFcltyRt', + { name: 'acmsltGroup', header: { text: '준설실적' }, items: ['drdgAcmsltAmount', 'drdgAcmsltQy'] }, + { + name: 'processGroup', header: { text: '준설토사처리' }, + items: ['drdgAcmsltUntpc', 'drdgEtndSm', 'rclm', 'oceanDpsl', 'ruse', 'etc'] + }, + 'rmk' + ] +} ]; export const SewagePipeMaintainColumnInfoList = rawMaintainList.map(tab => ({ diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index 30ff9fb98..4702636a8 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -16,7 +16,8 @@ import { type SewerPipeDiamApiResponse } from "../model/statistics-sewage-pipe-review-interface"; import { - type SewerPipeMtcApiResponse + type SewerPipeMtcApiResponse, + type SewerPipeDrdgApiResponse } from "../model/statistics-sewage-pipe-mtc-review-interface"; @@ -154,6 +155,25 @@ public async getSewerPipeMtcList( ); return response.data; } + +/** + * 03. 하수관로 유지관리 관로준설 현황 조회 (Tab B) + * @param params (yr, bsnmCd, sido, gugun) + */ +public async getSewerPipeDrdgList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } +): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe-mtc/dredging`, + { params } + ); + return response.data; +} } // 통합 인스턴스 수출 diff --git a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts index 5bab50b28..eb47c2de7 100644 --- a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface.ts @@ -43,4 +43,57 @@ export interface SewerPipeMtcResponseData { mtcStatusList: SewerPipeMtcItem[]; } -export type SewerPipeMtcApiResponse = ApiResponse; \ No newline at end of file +export type SewerPipeMtcApiResponse = ApiResponse; + +/** + * 03. 하수관로 유지관리 관로준설 아이템 (Tab 2) + */ +export interface SewerPipeDrdgItem { + // 1. 기본 정보 + sido: string; // 시도 + gugun: string; // 시군구 + yr: string; // 연도 + displayYear: string; // 화면 표시용 (예: 2024년, 2023년확정) + rowType: 'OLD' | 'CURR'; // OLD / CURR 구분 + bsnmCd: string; // 사업소코드 + + // 2. 시설 현황 + piplFcltyExtn: number; // 관로연장 (m) + + // 3. 준설 실적 - 관로 (m) + drdgPipeSm: number; // 준설계 + drdgCmbnPipe: number; // 합류식 + drdgPltwtrPipe: number; // 분류식-오수 + drdgRainwtrPipe: number; // 분류식-우수 + + // 4. 준설 실적 - 부속시설 (개소) + mnhleSite: number; // 맨홀 + pltwtrRcvrSite: number; // 오수받이 + soilspllSoilholeSite: number; // 토실/토구 + + // 5. 준설 효율 및 성과 + drdgTrgetExtn: number; // 준설대상 연장 (m) + drdgTrgetRt: number; // 준설대상 대비 준설률 (%) + piplFcltyRt: number; // 시설연장 대비 준설률 (%) + drdgAcmsltQy: number; // 준설실적 - 양 (m3) + drdgAcmsltAmount: number; // 준설실적 - 금액 (천원) + drdgAcmsltUntpc: number; // 준설실적 - 단가 (원/m3) + + // 6. 준설토 처리 (톤) + drdgEtndSm: number; // 처리량 계 + rclm: number; // 매립 + oceanDpsl: number; // 해양투기 + ruse: number; // 재활용 + etc: number; // 기타 + + // 7. 기타 및 상태값 + rmk: string; // 비고 +} + + +export interface SewerPipeDrdgResponseData { + drdgStatusList: SewerPipeDrdgItem[]; +} + + +export type SewerPipeDrdgApiResponse = ApiResponse; \ No newline at end of file From 842ec36c322736295a7efb89f709fa1a65a1cb09 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Thu, 19 Feb 2026 15:41:19 +0900 Subject: [PATCH 005/150] =?UTF-8?q?feat:=20=ED=95=98=EC=88=98=20=EC=A0=80?= =?UTF-8?q?=EB=A5=98=EC=8B=9C=EC=84=A4=20=EB=B0=8F=20=EB=B9=97=EB=AC=BC=20?= =?UTF-8?q?=ED=8E=8C=ED=94=84=EC=9E=A5=20=EC=A1=B0=ED=9A=8C=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/SewageReservoirPumpRvw.tsx | 153 +++++- .../review/SewageReservoirPumpRvwColumn.ts | 448 ++++++++++++------ .../api/statistics-management-service.ts | 43 ++ .../sewage-reservoir-pump-review-interface.ts | 171 +++++++ 4 files changed, 640 insertions(+), 175 deletions(-) create mode 100644 apps/st/src/entities/statistics/model/sewage-reservoir-pump-review-interface.ts diff --git a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx index a2b5e8de7..3b7645a4e 100644 --- a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx +++ b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx @@ -1,52 +1,153 @@ -import { useState } from "react"; - -import { TabDataGrid } from "@rsone/components"; +import { useState, useMemo, useRef } from "react"; +import TabDataGrid from "@/components/module/TabDataGrid"; import SearchForm from "@/components/module/SearchForm"; import SaveButton from "@/components/button/SaveButton"; import ApproveButton from "@/components/button/ApproveButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReturnButton from "@/components/button/ReturnButton"; +import { LoadingWithAnimation } from "@common/components"; +import { useAlertStore } from "@/shared/lib"; +import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn"; +import type { ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { toGridRows } from "@/components/util/grid/gridFunction"; -// 하수도 통계 관리 04. 하수저류 시설 및 빗물 펌프장 +/** + * 하수도 통계 검토 04. 하수저류 시설 및 빗물 펌프장 + * Tab A: 하수저류시설 + * Tab B: 빗물펌프장 + */ const SewageReservoirPumpRvw = () => { + const { setMessage: alert } = useAlertStore((state) => state); const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); + const [isLoading, setIsLoading] = useState(false); + const [activeTab, setActiveTab] = useState('a'); - const approveFunc = () => { - console.log('approveFunc'); - // 승인 로직 + const searchParamRef = useRef(null); + + const columnInfo = useMemo(() => { + return SewageReservoirPumpInfoListRvw.map((tab: ColumnInfoList) => ({ + ...tab, + columns: (tab.columns ?? []).map((col: ColumnConfig) => ({ + ...col, + editable: false as const, + styleName: col.styleName ?? ("rg-cell-readonly" as const), + })), + })); + }, []); + + const handleReset = () => { + setRowDataMap(new Map()); + setActiveTab('a'); + searchParamRef.current = null; }; - const saveFunc = () => { - console.log('submit'); - // 저장 로직 + // 조회 버튼 클릭 시: Tab A(하수저류시설) 데이터 로드 + const handleSearch = async (values: SearchFormValues) => { + if (!values.sido || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + + setActiveTab('a'); + setRowDataMap(new Map()); + + try { + setIsLoading(true); + + const searchParam = { + sido: values.sido, + gugun: values.gugun === 'ALL' ? '' : values.gugun, + bsnmCd: values.bsnmCd, + yr: values.year, + }; + + searchParamRef.current = searchParam; + + //하수저류시설 (Tab A) + const response = await statisticsManagementApi.getSewerReservoirList(searchParam); + + if (response?.status === 'success' && response.data) { + const newRowDataMap = new Map[]>(); + newRowDataMap.set('a', toGridRows(response.data as object[])); + setRowDataMap(newRowDataMap); + } else { + alert(response?.message || "데이터가 없습니다."); + } + } catch (error) { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } }; - const reviewOpinionFunc = () => { - console.log('reviewOpinionFunc'); - // 검토의견 작성 - }; + // 탭 전환 시: 데이터가 없는 경우 지연 로딩 (Tab B: 빗물펌프장) + const handleActiveTabChange = async (tabKey: string) => { + setActiveTab(tabKey); - const returnFunc = () => { - console.log('returnFunc'); - // 반려 + const activeTabData = rowDataMap.get(tabKey); + const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0; + + // 메인 데이터(Tab A)가 있고, 현재 탭 데이터가 없을 때만 API 호출 + if (searchParamRef.current && !hasActiveTabData) { + try { + setIsLoading(true); + let response; + + if (tabKey === 'b') { + response = await statisticsManagementApi.getPumpStationList(searchParamRef.current); + } + + if (response?.status === 'success' && response.data) { + setRowDataMap(prev => { + const next = new Map(prev); + next.set(tabKey, toGridRows(response.data as object[])); + return next; + }); + } else if (response) { + alert(response?.message || "데이터가 없습니다."); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + } }; return ( - <> - +
+ +
- - - - + { /* 검토의견 로직 */ }} /> + { /* 승인 로직 */ }} /> + { /* 반려 로직 */ }} /> + { /* 저장 로직 */ }} />
- - + + {isLoading && ( +
+
+ +
+
+ )} + + +
); }; -export default SewageReservoirPumpRvw; +export default SewageReservoirPumpRvw; \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts index 43aa1c62d..b5caf5604 100644 --- a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts @@ -1,186 +1,336 @@ import { ValueType } from "realgrid"; +import { + footerIntArrayByDivision, + footerDecimal1ArrayByDivision, + footerDecimal3ArrayByDivision +} from "@/components/util/grid/footerSummary"; +import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; + // 하수도 통계 관리 04. 하수저류 시설 및 빗물 펌프장 const rawReservoirList = [ { tabKey: 'a', - tabLabel: '하수저류시설', + tabLabel: '하수저류시설현황', rowStartNum: 3, fixedColumns: 2, columns: [ /* 1. 기본 정보 */ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'SWGE_STRGE_NM', fieldName: '시설명', header: { text: '시설명' } }, - { name: 'ADDR', fieldName: '위치', header: { text: '위치' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - - /* 2. 시설 제원 */ - { name: 'RN_DT', fieldName: '가동개시 년월일', header: { text: '가동개시\n년월일' } }, - { name: 'PL_DSGN_FRQNC', fieldName: '계획설계빈도', header: { text: '계획설계빈도' } }, - { name: 'INS_PER1', fieldName: '침수예방', header: { text: '침수예방' } }, - { name: 'INS_PER2', fieldName: '수질오염저감', header: { text: '수질오염저감' } }, - { name: 'INS_PER3', fieldName: '하수 재이용 용도', header: { text: '하수\n재이용\n용도' } }, - { name: 'FAC_RES', fieldName: '시설구조', header: { text: '시설구조(일반지하식,지하터널식,기타)' } }, - { name: 'DGR_AR', fieldName: '집수구역면적(㎢)', header: { text: '집수구역면적(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'SWGE_SGBS_CPCTY', fieldName: '시설용량(㎥)', header: { text: '시설용량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - /* 3. 펌프시설 */ - { name: 'TOT_CPCTY', fieldName: '총양수량(㎥/min)', header: { text: '총양수량\n(㎥/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CALBR', fieldName: '구경(mm)_1', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY', fieldName: '대당 양수량_1', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO', fieldName: '펌프대수_1', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO', fieldName: '예비펌프대수_1', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_CALBR2', fieldName: '구경(mm)_2', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY2', fieldName: '대당 양수량_2', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO2', fieldName: '펌프대수_2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO2', fieldName: '예비펌프대수_2', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_CALBR3', fieldName: '구경(mm)_3', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY3', fieldName: '대당 양수량_3', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO3', fieldName: '펌프대수_3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO3', fieldName: '예비펌프대수_3', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - /* 4. 부대시설 및 방식 */ - { name: 'FRE_FNNC', fieldName: '전처리시설(유/무)', header: { text: '전처리시설(유/무)' } }, - { name: 'DORZEQP_FNNC', fieldName: '탈취설비(유/무)', header: { text: '탈취설비(유/무)' } }, - { name: 'SELF_ETRQ_FNNC', fieldName: '자가발전(유/무)', header: { text: '자가발전(유/무)' } }, - { name: 'DRA_MTH', fieldName: '배수방식(자연배수,강제배수)', header: { text: '배수방식(자연배수,강제배수)' } }, - { name: 'ELEC_MTH', fieldName: '저류방식(직렬,병렬)', header: { text: '저류방식(직렬,병렬)' } }, - { name: 'ELEC_PRO_MAT', fieldName: '저류수 처리방법', header: { text: '저류수\n처리방법' } }, - { name: 'DIS_WAT', fieldName: '방류수역(또는 연계처리장명)', header: { text: '방류수역(또는 연계처리장명)' } }, - - /* 5. 최종처분량 및 방법 */ - { name: 'SDMT_TRE_CPCTY', fieldName: '침사(량)', header: { text: '침사' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'IPRT_TRE_CPCTY', fieldName: '협잡물(량)', header: { text: '협잡물' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SDMT_TRE_MTH', fieldName: '침사(방법)', header: { text: '침사' } }, - { name: 'IPRT_TRE_MTH', fieldName: '협잡물(방법)', header: { text: '협잡물' } }, - - /* 6. 운영실적 */ - { name: 'YRSTP_RN_DYN', fieldName: '가동일수', header: { text: '가동일수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'DSPLCMNT_AMNT', fieldName: '총배수량(㎥/년)', header: { text: '총배수량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, - ], - columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'SWGE_STRGE_NM', 'ADDR', 'YR_GBN', 'RN_DT', 'PL_DSGN_FRQNC', { - name: 'purposeGroup', - header: { text: '설치목적(O,X로 표기, 중복체크 가능)' }, - items: ['INS_PER1', 'INS_PER2', 'INS_PER3'] - }, - 'FAC_RES', 'DGR_AR', 'SWGE_SGBS_CPCTY', - { - name: 'pumpMainGroup', - header: { text: '펌프시설' }, - items: [ - 'TOT_CPCTY', - { name: 'p1Group', header: { text: '펌프시설1' }, items: ['PUMP_CALBR', 'WP_CPCTY', 'PUMP_CO', 'PUMP_PREPAR_CO'] }, - { name: 'p2Group', header: { text: '펌프시설2' }, items: ['PUMP_CALBR2', 'WP_CPCTY2', 'PUMP_CO2', 'PUMP_PREPAR_CO2'] }, - { name: 'p3Group', header: { text: '펌프시설3' }, items: ['PUMP_CALBR3', 'WP_CPCTY3', 'PUMP_CO3', 'PUMP_PREPAR_CO3'] } + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; + return '합계'; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; + return '합계'; + } + } ] }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'swgeStrgeNm', fieldName: 'swgeStrgeNm', header: { text: '시설명' }, mergeCells: true, editable: false }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' }, mergeCells: true, editable: false }, { - name: 'subFacGroup', - header: { text: '부대시설' }, - items: ['FRE_FNNC', 'DORZEQP_FNNC', 'SELF_ETRQ_FNNC'] + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + } + ] }, - 'DRA_MTH', 'ELEC_MTH', 'ELEC_PRO_MAT', 'DIS_WAT', + + /* 2. 시설 제원 */ + { name: 'rnDt', fieldName: 'rnDt', header: { text: '가동개시 년월일' } }, + { name: 'plDsgnFrqnc', fieldName: 'plDsgnFrqnc', header: { text: '계획설계빈도' } }, + { name: 'insPer1', fieldName: 'insPer1', header: { text: '침수예방' } }, + { name: 'insPer2', fieldName: 'insPer2', header: { text: '수질오염저감' } }, + { name: 'insPer3', fieldName: 'insPer3', header: { text: '하수 재이용 용도' } }, + { name: 'facRes', fieldName: 'facRes', header: { text: '시설구조' } }, + { name: 'dgrAr', fieldName: 'dgrAr', header: { text: '집수구역면적(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000', footer: footerDecimal3ArrayByDivision }, + { name: 'swgeSgbsCpcty', fieldName: 'swgeSgbsCpcty', header: { text: '시설용량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + + /* 3. 펌프시설 */ + { name: 'totCpcty', fieldName: 'totCpcty', header: { text: '총양수량(㎥/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCalbr', fieldName: 'pumpCalbr', header: { text: '구경(mm)' } }, + { name: 'wpCpcty', fieldName: 'wpCpcty', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo', fieldName: 'pumpCo', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo', fieldName: 'pumpPreparCo', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpCalbr2', fieldName: 'pumpCalbr2', header: { text: '구경(mm)' } }, + { name: 'wpCpcty2', fieldName: 'wpCpcty2', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo2', fieldName: 'pumpCo2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo2', fieldName: 'pumpPreparCo2', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpCalbr3', fieldName: 'pumpCalbr3', header: { text: '구경(mm)' } }, + { name: 'wpCpcty3', fieldName: 'wpCpcty3', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo3', fieldName: 'pumpCo3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo3', fieldName: 'pumpPreparCo3', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + + /* 4. 부대시설 및 방식 */ + { name: 'freFnnc', fieldName: 'freFnnc', header: { text: '전처리시설(유/무)' } }, + { name: 'dorzeqpFnnc', fieldName: 'dorzeqpFnnc', header: { text: '탈취설비(유/무)' } }, + { name: 'selfEtrqFnnc', fieldName: 'selfEtrqFnnc', header: { text: '자가발전(유/무)' } }, + { name: 'draMth', fieldName: 'draMth', header: { text: '배수방식' } }, + { name: 'elecMth', fieldName: 'elecMth', header: { text: '저류방식' } }, + { name: 'elecProMat', fieldName: 'elecProMat', header: { text: '저류수 처리방법' } }, + { name: 'disWat', fieldName: 'disWat', header: { text: '방류수역' } }, + + /* 5. 최종처분량 및 방법 */ + { name: 'sdmtTreCpcty', fieldName: 'sdmtTreCpcty', header: { text: '침사' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'iprtTreCpcty', fieldName: 'iprtTreCpcty', header: { text: '협잡물' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'sdmtTreMth', fieldName: 'sdmtTreMth', header: { text: '침사' } }, + { name: 'iprtTreMth', fieldName: 'iprtTreMth', header: { text: '협잡물' } }, + + /* 6. 운영실적 */ + { name: 'yrstpRnDyn', fieldName: 'yrstpRnDyn', header: { text: '가동일수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'dsplcmntAmnt', fieldName: 'dsplcmntAmnt', header: { text: '총배수량(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ { - name: 'disposalAmtGroup', - header: { text: '최종처분량(ton/년)' }, - items: ['SDMT_TRE_CPCTY', 'IPRT_TRE_CPCTY'] + name: 'areaGroup', header: { text: '행정구역' }, items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', 'swgeStrgeNm', 'addr' + ] }, + 'displayYear', 'rnDt', 'plDsgnFrqnc', + { name: 'purposeGroup', header: { text: '설치목적' }, items: ['insPer1', 'insPer2', 'insPer3'] }, + 'facRes', 'dgrAr', 'swgeSgbsCpcty', { - name: 'disposalMthGroup', - header: { text: '최종처분방법' }, - items: ['SDMT_TRE_MTH', 'IPRT_TRE_MTH'] + name: 'pumpMainGroup', header: { text: '펌프시설' }, + items: [ + 'totCpcty', + { name: 'p1Group', header: { text: '펌프시설1' }, items: ['pumpCalbr', 'wpCpcty', 'pumpCo', 'pumpPreparCo'] }, + { name: 'p2Group', header: { text: '펌프시설2' }, items: ['pumpCalbr2', 'wpCpcty2', 'pumpCo2', 'pumpPreparCo2'] }, + { name: 'p3Group', header: { text: '펌프시설3' }, items: ['pumpCalbr3', 'wpCpcty3', 'pumpCo3', 'pumpPreparCo3'] } + ] }, - { - name: 'opGroup', - header: { text: '운영실적' }, - items: ['YRSTP_RN_DYN', 'DSPLCMNT_AMNT'] - }, - 'RMK' + { name: 'subFacGroup', header: { text: '부대시설' }, items: ['freFnnc', 'dorzeqpFnnc', 'selfEtrqFnnc'] }, + 'draMth', 'elecMth', 'elecProMat', 'disWat', + { name: 'disposalAmtGroup', header: { text: '최종처분량(ton/년)' }, items: ['sdmtTreCpcty', 'iprtTreCpcty'] }, + { name: 'disposalMthGroup', header: { text: '최종처분방법' }, items: ['sdmtTreMth', 'iprtTreMth'] }, + { name: 'opGroup', header: { text: '운영실적' }, items: ['yrstpRnDyn', 'dsplcmntAmnt'] }, + 'rmk' ] }, - { +{ tabKey: 'b', tabLabel: '빗물펌프장', rowStartNum: 3, fixedColumns: 2, columns: [ /* 1. 기본 정보 */ - { name: 'SIDO', fieldName: '시도', header: { text: '시도' } }, - { name: 'GUGUN', fieldName: '시군구', header: { text: '시군구' } }, - { name: 'SWGE_STRGE_NM', fieldName: '시설명', header: { text: '시설명' } }, - { name: 'ADDR', fieldName: '위치', header: { text: '위치' } }, - { name: 'YR_GBN', fieldName: '구분', header: { text: '구분' } }, - - /* 2. 시설 제원 */ - { name: 'FCT_KND', fieldName: '시설물종류', header: { text: '시설물종류' } }, - { name: 'RN_DT', fieldName: '가동개시 년월일', header: { text: '가동개시\n년월일' } }, - { name: 'PL_DSGN_FRQNC', fieldName: '계획설계빈도', header: { text: '계획설계빈도' } }, - { name: 'DGR_AR', fieldName: '집수구역면적(㎢)', header: { text: '집수구역면적(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - - /* 3. 유수지 및 흡수정 */ - { name: 'REV_FNNC', fieldName: '유수지유무(유,무)', header: { text: '유수지유무\n(유,무)' } }, - { name: 'REV_CPCTY', fieldName: '저류량(유수지)', header: { text: '저류량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'ABS_CPCTY', fieldName: '저류량(흡수정)', header: { text: '저류량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'SWGE_SGBS_CPCTY', fieldName: '시설용량(㎥)', header: { text: '시설용량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - /* 4. 펌프시설 */ - { name: 'TOT_CPCTY', fieldName: '총양수량(㎥/min)', header: { text: '총양수량\n(㎥/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CALBR', fieldName: '구경(mm)_1', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY', fieldName: '대당 양수량_1', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO', fieldName: '펌프대수_1', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO', fieldName: '예비펌프대수_1', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_CALBR2', fieldName: '구경(mm)_2', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY2', fieldName: '대당 양수량_2', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO2', fieldName: '펌프대수_2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO2', fieldName: '예비펌프대수_2', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_CALBR3', fieldName: '구경(mm)_3', header: { text: '구경(mm)' } }, - { name: 'WP_CPCTY3', fieldName: '대당 양수량_3', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_CO3', fieldName: '펌프대수_3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_PREPAR_CO3', fieldName: '예비펌프대수_3', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - /* 5. 부대시설 및 기타 */ - { name: 'FRE_FNNC', fieldName: '전처리시설', header: { text: '전처리시설' } }, - { name: 'DORZEQP_FNNC', fieldName: '탈취설비', header: { text: '탈취설비' } }, - { name: 'SELF_ETRQ_FNNC', fieldName: '자가발전', header: { text: '자가발전' } }, - { name: 'DIS_WAT', fieldName: '방류수역', header: { text: '방류수역' } }, - - /* 6. 최종처분량 및 방법 */ - { name: 'SDMT_TRE_CPCTY', fieldName: '침사(량)', header: { text: '침사(량)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'IPRT_TRE_CPCTY', fieldName: '협잡물(량)', header: { text: '협잡물(량)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'SDMT_TRE_MTH', fieldName: '침사(방법)', header: { text: '침사(방법)' } }, - { name: 'IPRT_TRE_MTH', fieldName: '협잡물(방법)', header: { text: '협잡물(방법)' } }, - - /* 7. 운영실적 */ - { name: 'YRSTP_RN_DYN', fieldName: '가동일수', header: { text: '가동일수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'DSPLCMNT_AMNT', fieldName: '총배수량(㎥/년)', header: { text: '총배수량\n(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; + return '합계'; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; + return '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'swgeStrgeNm', fieldName: 'swgeStrgeNm', header: { text: '시설명' }, mergeCells: true, editable: false }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' }, mergeCells: true, editable: false }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + }, + { + valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + } + ] + }, + { name: 'fctKnd', fieldName: 'fctKnd', header: { text: '시설물종류' } }, + { name: 'rnDt', fieldName: 'rnDt', header: { text: '가동개시 년월일' } }, + { name: 'plDsgnFrqnc', fieldName: 'plDsgnFrqnc', header: { text: '계획설계빈도' } }, + { name: 'dgrAr', fieldName: 'dgrAr', header: { text: '집수구역면적(㎢)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000', footer: footerDecimal3ArrayByDivision }, + { name: 'revFnnc', fieldName: 'revFnnc', header: { text: '유수지유무\n(유,무)' } }, + { name: 'revCpcty', fieldName: 'revCpcty', header: { text: '저류량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'absCpcty', fieldName: 'absCpcty', header: { text: '저류량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'swgeSgbsCpcty', fieldName: 'swgeSgbsCpcty', header: { text: '시설용량(㎥)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'totCpcty', fieldName: 'totCpcty', header: { text: '총양수량(㎥/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCalbr', fieldName: 'pumpCalbr', header: { text: '구경(mm)' } }, + { name: 'wpCpcty', fieldName: 'wpCpcty', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo', fieldName: 'pumpCo', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo', fieldName: 'pumpPreparCo', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpCalbr2', fieldName: 'pumpCalbr2', header: { text: '구경(mm)' } }, + { name: 'wpCpcty2', fieldName: 'wpCpcty2', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo2', fieldName: 'pumpCo2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo2', fieldName: 'pumpPreparCo2', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpCalbr3', fieldName: 'pumpCalbr3', header: { text: '구경(mm)' } }, + { name: 'wpCpcty3', fieldName: 'wpCpcty3', header: { text: '대당 양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', footer: footerDecimal1ArrayByDivision }, + { name: 'pumpCo3', fieldName: 'pumpCo3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'pumpPreparCo3', fieldName: 'pumpPreparCo3', header: { text: '예비펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'freFnnc', fieldName: 'freFnnc', header: { text: '전처리시설' } }, + { name: 'dorzeqpFnnc', fieldName: 'dorzeqpFnnc', header: { text: '탈취설비' } }, + { name: 'selfEtrqFnnc', fieldName: 'selfEtrqFnnc', header: { text: '자가발전' } }, + { name: 'disWat', fieldName: 'disWat', header: { text: '방류수역' } }, + { name: 'sdmtTreCpcty', fieldName: 'sdmtTreCpcty', header: { text: '침사(량)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'iprtTreCpcty', fieldName: 'iprtTreCpcty', header: { text: '협잡물(량)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'sdmtTreMth', fieldName: 'sdmtTreMth', header: { text: '침사(방법)' } }, + { name: 'iprtTreMth', fieldName: 'iprtTreMth', header: { text: '협잡물(방법)' } }, + { name: 'yrstpRnDyn', fieldName: 'yrstpRnDyn', header: { text: '가동일수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'dsplcmntAmnt', fieldName: 'dsplcmntAmnt', header: { text: '총배수량(㎥/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0', footer: footerIntArrayByDivision }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: ['SIDO', 'GUGUN'] }, - 'SWGE_STRGE_NM', 'ADDR', 'YR_GBN', 'FCT_KND', 'RN_DT', 'PL_DSGN_FRQNC', 'DGR_AR', - { name: 'revGroup', header: { text: '유수지' }, items: ['REV_FNNC', 'REV_CPCTY'] }, - { name: 'absGroup', header: { text: '흡수정' }, items: ['ABS_CPCTY'] }, - 'SWGE_SGBS_CPCTY', + { + name: 'areaGroup', header: { text: '행정구역' }, items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', 'swgeStrgeNm', 'addr' + ] + }, + 'displayYear', 'fctKnd', 'rnDt', 'plDsgnFrqnc', 'dgrAr', + { name: 'revGroup', header: { text: '유수지' }, items: ['revFnnc', 'revCpcty'] }, + { name: 'absGroup', header: { text: '흡수정' }, items: ['absCpcty'] }, + 'swgeSgbsCpcty', { name: 'pumpMainGroup', header: { text: '펌프시설' }, items: [ - 'TOT_CPCTY', - { name: 'p1Group', header: { text: '펌프시설1' }, items: ['PUMP_CALBR', 'WP_CPCTY', 'PUMP_CO', 'PUMP_PREPAR_CO'] }, - { name: 'p2Group', header: { text: '펌프시설2' }, items: ['PUMP_CALBR2', 'WP_CPCTY2', 'PUMP_CO2', 'PUMP_PREPAR_CO2'] }, - { name: 'p3Group', header: { text: '펌프시설3' }, items: ['PUMP_CALBR3', 'WP_CPCTY3', 'PUMP_CO3', 'PUMP_PREPAR_CO3'] } + 'totCpcty', + { name: 'p1Group', header: { text: '펌프시설1' }, items: ['pumpCalbr', 'wpCpcty', 'pumpCo', 'pumpPreparCo'] }, + { name: 'p2Group', header: { text: '펌프시설2' }, items: ['pumpCalbr2', 'wpCpcty2', 'pumpCo2', 'pumpPreparCo2'] }, + { name: 'p3Group', header: { text: '펌프시설3' }, items: ['pumpCalbr3', 'wpCpcty3', 'pumpCo3', 'pumpPreparCo3'] } ] }, - { name: 'subFacGroup', header: { text: '부대시설' }, items: ['FRE_FNNC', 'DORZEQP_FNNC', 'SELF_ETRQ_FNNC'] }, - 'DIS_WAT', - { name: 'disposalAmtGroup', header: { text: '최종처분량(ton/년)' }, items: ['SDMT_TRE_CPCTY', 'IPRT_TRE_CPCTY'] }, - { name: 'disposalMthGroup', header: { text: '최종처분방법' }, items: ['SDMT_TRE_MTH', 'IPRT_TRE_MTH'] }, - { name: 'opGroup', header: { text: '운영실적' }, items: ['YRSTP_RN_DYN', 'DSPLCMNT_AMNT'] }, - 'RMK' + { name: 'subFacGroup', header: { text: '부대시설' }, items: ['freFnnc', 'dorzeqpFnnc', 'selfEtrqFnnc'] }, + 'disWat', + { name: 'disposalAmtGroup', header: { text: '최종처분량(ton/년)' }, items: ['sdmtTreCpcty', 'iprtTreCpcty'] }, + { name: 'disposalMthGroup', header: { text: '최종처분방법' }, items: ['sdmtTreMth', 'iprtTreMth'] }, + { name: 'opGroup', header: { text: '운영실적' }, items: ['yrstpRnDyn', 'dsplcmntAmnt'] }, + 'rmk' ] } ]; diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index 4702636a8..20e3b532f 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -19,6 +19,11 @@ import { type SewerPipeMtcApiResponse, type SewerPipeDrdgApiResponse } from "../model/statistics-sewage-pipe-mtc-review-interface"; +import { + type SewerReservoirRegionalApiResponse, + type PumpingStationRegionalApiResponse +} from "../model/sewage-reservoir-pump-review-interface"; + export class StatisticsManagementApiService { @@ -174,6 +179,44 @@ public async getSewerPipeDrdgList( ); return response.data; } + +/** + * 04. 하수저류시설 tab1. 시설현황 조회 + * @param params (yr, bsnmCd, sido, gugun) + */ +public async getSewerReservoirList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } +): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-reservoir/facilities`, + { params } + ); + return response.data; +} + +/** + * 04. 빗물펌프장 tab2. 조회 + * @param params (yr, bsnmCd, sido, gugun) + */ +public async getPumpStationList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } +): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-reservoir/pumping-stations`, + { params } + ); + return response.data; +} } // 통합 인스턴스 수출 diff --git a/apps/st/src/entities/statistics/model/sewage-reservoir-pump-review-interface.ts b/apps/st/src/entities/statistics/model/sewage-reservoir-pump-review-interface.ts new file mode 100644 index 000000000..4f7b6bf7f --- /dev/null +++ b/apps/st/src/entities/statistics/model/sewage-reservoir-pump-review-interface.ts @@ -0,0 +1,171 @@ + +import type { ApiResponse } from "./statistics-interface"; + +/** + * 04. 하수저류시설 tab1. 시설현황 아이템 인터페이스 (Tab1) + */ +export interface SewerReservoirRegionalItem { +// 1. 기본 정보 및 행 제어 + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + swgeStrgeNm: string; // 시설명 + addr: string; // 위치(주소) + yr: string; // 연도 + rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 + displayYear: string; // 구분 표시용 (2024년, 2023년확정 등) + + // 2. 시설 제원 + rnDt: string; // 가동개시일 (yyyyMMdd) + plDsgnFrqnc: string; // 계획설계빈도 + insPer1: string; // 설치목적: 침수예방 + insPer2: string; // 설치목적: 수질오염저감 + insPer3: string; // 설치목적: 하수재이용 + facRes: string; // 시설구조 + dgrAr: number; // 집수구역면적 + swgeSgbsCpcty: number; // 시설용량 + totCpcty: number; // 총양수량 + + // 3. 펌프시설 상세 (1호기) + pumpCalbr: string; // 구경 + wpCpcty: number; // 대당 양수량 + pumpCo: number; // 펌프대수 + pumpPreparCo: number; // 예비펌프대수 + + // 4. 펌프시설 상세 (2호기) - 추가 + pumpCalbr2: string; // 구경2 + wpCpcty2: number; // 대당 양수량2 + pumpCo2: number; // 펌프대수2 + pumpPreparCo2: number; // 예비펌프대수2 + + // 5. 펌프시설 상세 (3호기) - 추가 + pumpCalbr3: string; // 구경3 + wpCpcty3: number; // 대당 양수량3 + pumpCo3: number; // 펌프대수3 + pumpPreparCo3: number; // 예비펌프대수3 + + // 6. 부대시설 및 방식 - 추가 + freFnnc: string; // 전처리시설유무 + dorzeqpFnnc: string; // 탈취설비유무 + selfEtrqFnnc: string; // 자가발전유무 + draMth: string; // 배수방식 + elecMth: string; // 저류방식 + elecProMat: string; // 저류수 처리방법 + disWat: string; // 방류수역 + + // 7. 최종처분량 및 방법 - 추가 + sdmtTreCpcty: number; // 침사량 + iprtTreCpcty: number; // 협잡물량 + sdmtTreMth: string; // 침사 처분방법 + iprtTreMth: string; // 협잡물 처분방법 + + // 8. 운영 실적 + yrstpRnDyn: number; // 가동일수 + dsplcmntAmnt: number; // 총배수량 + rmk: string; // 비고 + + // 9. 상태 체크용 (그리드 배경색 제어용) - 추가 + lcnt: number; // 신규 여부 (1/0) + ocnt: number; // 기존 여부 (1/0) +} + +/** + * 하수저류시설 응답 데이터 구조 + */ +export interface SewerReservoirResponseData { + reservoirList: SewerReservoirRegionalItem[]; +} + +/** + * 하수저류시설 서비스 인터페이스 + */ +export interface SewerReservoirService { + /** + * 하수저류시설 리스트 조회 (Tab1) + */ + getSewerReservoirList(params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업장 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + }): Promise; +} + +/** + * 04. 빗물펌프장 tab2. 시설현황 아이템 인터페이스 (Tab2) + */ +export interface PumpingStationRegionalItem { + // --- 기본 정보 --- + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + swgeStrgeNm: string; // 시설명 + addr: string; // 위치 + yr: string; // 연도 + rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 + displayYear: string; // 구분 표시용 + + // --- 시설 제원 --- + fctKnd: string; // 시설물종류 + rnDt: string; // 가동개시일 + plDsgnFrqnc: string; // 계획설계빈도 + dgrAr: number; // 집수구역면적 + revFnnc: string; // 유수지유무 + revCpcty: number; // 유수지저류량 + absCpcty: number; // 흡수정저류량 + swgeSgbsCpcty: number; // 시설용량 + totCpcty: number; // 총양수량 + + // --- 펌프시설 상세 (1, 2, 3호기) --- + pumpCalbr: string; wpCpcty: number; pumpCo: number; pumpPreparCo: number; + pumpCalbr2: string; wpCpcty2: number; pumpCo2: number; pumpPreparCo2: number; + pumpCalbr3: string; wpCpcty3: number; pumpCo3: number; pumpPreparCo3: number; + + // --- 부대시설 및 방식 --- + freFnnc: string; // 전처리시설유무 + dorzeqpFnnc: string; // 탈취설비유무 + selfEtrqFnnc: string; // 자가발전유무 + disWat: string; // 방류수역 + + // --- 최종처분량 및 방법 --- + sdmtTreCpcty: number; // 침사 처분량 + iprtTreCpcty: number; // 협잡물 처분량 + sdmtTreMth: string; // 침사 처분방법 + iprtTreMth: string; // 협잡물 처분방법 + + // --- 운영실적 --- + yrstpRnDyn: number; // 가동일수 + dsplcmntAmnt: number; // 총배수량 + rmk: string; // 비고 +} + +/** + * 응답 타입 정의 + */ +export type SewerReservoirRegionalApiResponse = ApiResponse; +export type PumpingStationRegionalApiResponse = ApiResponse; + +/** + * 하수저류시설 및 빗물펌프장 서비스 인터페이스 + */ +export interface SewerReservoirService { + /** + * 하수저류시설 리스트 조회 (Tab1) + */ + getSewerReservoirList(params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + }): Promise; + + /** + * 빗물펌프장 리스트 조회 (Tab2) + */ + getPumpStationList(params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + }): Promise; +} \ No newline at end of file From b6a75f6ee02899b5cd2846b5280c81d03df14f98 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Fri, 20 Feb 2026 14:02:27 +0900 Subject: [PATCH 006/150] =?UTF-8?q?feat:=2005.=ED=8E=8C=ED=94=84=EC=9E=A5?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/review/PumpingStationRvw.tsx | 98 +++++++-- .../column/review/PumpingStationRvwColumn.ts | 206 +++++++++++++----- .../api/statistics-management-service.ts | 24 +- ...istics-pumping-station-review-interface.ts | 86 ++++++++ 4 files changed, 343 insertions(+), 71 deletions(-) create mode 100644 apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts diff --git a/apps/st/src/components/statistics/review/PumpingStationRvw.tsx b/apps/st/src/components/statistics/review/PumpingStationRvw.tsx index 8a9df541a..b1a1d6f9d 100644 --- a/apps/st/src/components/statistics/review/PumpingStationRvw.tsx +++ b/apps/st/src/components/statistics/review/PumpingStationRvw.tsx @@ -1,26 +1,82 @@ -import { useState } from "react"; - -import { DataGrid } from "@rsone/components"; +import { useState, useMemo } from "react"; +import DataGrid from "@/components/module/DataGrid"; import SearchForm from "@/components/module/SearchForm"; import SaveButton from "@/components/button/SaveButton"; import ApproveButton from "@/components/button/ApproveButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReturnButton from "@/components/button/ReturnButton"; +import { LoadingWithAnimation } from "@common/components"; +import { useAlertStore } from "@/shared/lib"; import { PumpingStationInfoList } from "@/components/util/column/review/PumpingStationRvwColumn"; +import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { toGridRows } from "@/components/util/grid/gridFunction"; -// 하수도 통계 관리 05.펌프장 +// 하수도 통계 검토 05.중계펌프장 const PumpingStationRvw = () => { - const [rowData, setRowData] = useState[]>([]); + const { setMessage: alert } = useAlertStore((state) => state); + const [rowData, setRowData] = useState([]); + const [isLoading, setIsLoading] = useState(false); - const approveFunc = () => { console.log('approveFunc'); }; - const saveFunc = () => { console.log('submit'); }; - const reviewOpinionFunc = () => { console.log('reviewOpinionFunc'); }; - const returnFunc = () => { console.log('returnFunc'); }; + const columnInfo = useMemo(() => { + const { columns, columnLayout, fixedColumns, rowStartNum } = PumpingStationInfoList; + return { + columnLayout, + fixedColumns, + rowStartNum, + columns: columns.map((col) => ({ + ...col, + editable: false as const, // 검토 화면이므로 읽기 전용 + styleName: col.styleName ?? ("rg-cell-readonly" as const), + })), + }; + }, []); + + const handleReset = () => { + setRowData([]); + }; + + const handleSearch = async (values: SearchFormValues) => { + if (values.sido === '' || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + + try { + setIsLoading(true); + + const response = await statisticsManagementApi.getMediationPumpStationList({ + sido: values.sido, + gugun: values.gugun === 'ALL' ? '' : values.gugun, + bsnmCd: values.bsnmCd, + yr: values.year, + bscFctNm: values.fctName, // 시설명 검색 + }); + + if (response.status === 'success' && response.data) { + setRowData(toGridRows(response.data)); + } else { + setRowData([]); + alert(response.message || "데이터가 없습니다."); + } + } catch (error) { + console.error("중계펌프장 조회 중 에러 발생:", error); + alert('데이터 조회 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + }; + + const approveFunc = () => console.log('approveFunc'); + const saveFunc = () => console.log('submit'); + const reviewOpinionFunc = () => console.log('reviewOpinionFunc'); + const returnFunc = () => console.log('returnFunc'); return ( - <> - +
+ +
@@ -29,8 +85,24 @@ const PumpingStationRvw = () => {
- - + + {isLoading && ( +
+
+ +
+
+ )} + + +
); }; diff --git a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts index 058eb39e3..7ab5e8e59 100644 --- a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts +++ b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts @@ -1,106 +1,198 @@ import { ValueType } from "realgrid"; +import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; +import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; -// 하수도 통계 관리 05.펌프장 export const rawPumpingStationInfoList = { columns: [ /* 1. 행정구역 및 기본정보 */ - { name: 'sido', fieldName: '시도', header: { text: '시도' } }, - { name: 'sgg', fieldName: '구군', header: { text: '구군' } }, - { name: 'PUMP_NM', fieldName: '펌프장명', header: { text: '펌프장명' } }, - { name: 'GBN', fieldName: '구분', header: { text: '구분' } }, - { name: 'TRT_FCT_NM', fieldName: '처리장명', header: { text: '처리장명' } }, - { name: 'TYPE_GBN', fieldName: '구분(합류식분류식)', header: { text: '구분(합류식분류식)' } }, - { name: 'ADDR', fieldName: '위치', header: { text: '위치' } }, - { name: 'RN_DT', fieldName: '가동년월일 yyyy-mm-dd', header: { text: '가동년월일\nyyyy-mm-dd' } }, - { name: 'DSN_AR', fieldName: '계획배수면적(km2)', header: { text: '계획배수면적\n(km2)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; 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 rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; 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: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'pumpNm', fieldName: 'pumpNm', header: { text: '펌프장명' }, mergeCells: true, editable: false, sortdisable: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + editable: false, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + 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(); + 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: 'bscFctNm', fieldName: 'bscFctNm', header: { text: '처리장명' } }, + { name: 'pumpCls', fieldName: 'pumpCls', header: { text: '구분\n(합류/분류)' } }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' }, styleName: 'rg-cell-left' }, + { name: 'rnDt', fieldName: 'rnDt', header: { text: '가동년월일\nyyyy-mm-dd' } }, + { + name: 'planDrhgAr', fieldName: 'planDrhgAr', header: { text: '계획배수면적\n(km2)' }, + footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' + }, /* 2. 계획배수량 (m3/hr) */ - { name: 'DSN_QTY_A', fieldName: '청천시(A)', header: { text: '청천시(A)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DSN_QTY_B', fieldName: '우천시(B)', header: { text: '우천시(B)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'DSN_QTY_DIFF', fieldName: '우수량(B-A)', header: { text: '우수량(B-A)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'finewtDrngVl', fieldName: 'finewtDrngVl', header: { text: '청천시(A)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtDrngVl', fieldName: 'rainwtDrngVl', header: { text: '우천시(B)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'vlWtr', fieldName: 'vlWtr', header: { text: '우수량(B-A)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, /* 3. 총 양수량 (m3/min) */ - { name: 'TOT_PUMP_QTY_O', fieldName: '오수펌프 총양수량', header: { text: '오수펌프\n총양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'TOT_PUMP_QTY_W', fieldName: '우수펌프 총양수량', header: { text: '우수펌프\n총양수량' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrTotCpcty', fieldName: 'pltwtrTotCpcty', header: { text: '오수펌프\n총양수량' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrTotCpcty', fieldName: 'rainwtrTotCpcty', header: { text: '우수펌프\n총양수량' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, /* 4. 오수펌프 상세 (1, 2, 3) */ - { name: 'PUMP_O1_D', fieldName: '구경(mm)_오수1', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O1_Q', fieldName: '양수량(m3/min)_오수1', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O1_C', fieldName: '펌프대수_오수1', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_O1_S', fieldName: '예비대수_오수1', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpCalbr1', fieldName: 'pltwtrPumpCalbr1', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCpcty1', fieldName: 'pltwtrPumpCpcty1', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCnt1', fieldName: 'pltwtrPumpCnt1', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpPreparCnt1', fieldName: 'pltwtrPumpPreparCnt1', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + + { name: 'pltwtrPumpCalbr2', fieldName: 'pltwtrPumpCalbr2', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCpcty2', fieldName: 'pltwtrPumpCpcty2', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCnt2', fieldName: 'pltwtrPumpCnt2', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpPreparCnt2', fieldName: 'pltwtrPumpPreparCnt2', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_O2_D', fieldName: '구경(mm)_오수2', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O2_Q', fieldName: '양수량(m3/min)_오수2', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O2_C', fieldName: '펌프대수_오수2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_O2_S', fieldName: '예비대수_오수2', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - - { name: 'PUMP_O3_D', fieldName: '구경(mm)_오수3', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O3_Q', fieldName: '양수량(m3/min)_오수3', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_O3_C', fieldName: '펌프대수_오수3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_O3_S', fieldName: '예비대수_오수3', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpCalbr3', fieldName: 'pltwtrPumpCalbr3', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCpcty3', fieldName: 'pltwtrPumpCpcty3', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'pltwtrPumpCnt3', fieldName: 'pltwtrPumpCnt3', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrPumpPreparCnt3', fieldName: 'pltwtrPumpPreparCnt3', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, /* 5. 우수펌프 상세 (1, 2, 3) */ - { name: 'PUMP_W1_D', fieldName: '구경(mm)_우수1', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W1_Q', fieldName: '양수량(m3/min)_우수1', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W1_C', fieldName: '펌프대수_우수1', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W1_S', fieldName: '예비대수_우수1', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpCalbr1', fieldName: 'rainwtrPumpCalbr1', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCpcty1', fieldName: 'rainwtrPumpCpcty1', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCnt1', fieldName: 'rainwtrPumpCnt1', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpPreparCnt1', fieldName: 'rainwtrPumpPreparCnt1', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W2_D', fieldName: '구경(mm)_우수2', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W2_Q', fieldName: '양수량(m3/min)_우수2', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W2_C', fieldName: '펌프대수_우수2', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W2_S', fieldName: '예비대수_우수2', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpCalbr2', fieldName: 'rainwtrPumpCalbr2', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCpcty2', fieldName: 'rainwtrPumpCpcty2', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCnt2', fieldName: 'rainwtrPumpCnt2', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpPreparCnt2', fieldName: 'rainwtrPumpPreparCnt2', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W3_D', fieldName: '구경(mm)_우수3', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W3_Q', fieldName: '양수량(m3/min)_우수3', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'PUMP_W3_C', fieldName: '펌프대수_우수3', header: { text: '펌프대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'PUMP_W3_S', fieldName: '예비대수_우수3', header: { text: '예비대수' }, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpCalbr3', fieldName: 'rainwtrPumpCalbr3', header: { text: '구경(mm)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCpcty3', fieldName: 'rainwtrPumpCpcty3', header: { text: '양수량\n(m3/min)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rainwtrPumpCnt3', fieldName: 'rainwtrPumpCnt3', header: { text: '펌프대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'rainwtrPumpPreparCnt3', fieldName: 'rainwtrPumpPreparCnt3', header: { text: '예비대수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 6. 운영 정보 */ - { name: 'PWR_USAGE', fieldName: '연간전력사용량(kWh/년)', header: { text: '연간전력사용량\n(kWh/년)' }, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'RMK', fieldName: '비고', header: { text: '비고' } }, + /* 6. 기타 및 운영 */ + { name: 'elcpwAmnt', fieldName: 'elcpwAmnt', header: { text: '연간전력사용량\n(kWh/년)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, ], columnLayout: [ - { name: 'groupA', header: { text: '행정구역' }, items: ['sido', 'sgg'] }, - 'PUMP_NM', 'GBN', 'TRT_FCT_NM', 'TYPE_GBN', 'ADDR', 'RN_DT', 'DSN_AR', + { + name: 'groupA', + header: { text: '행정구역' }, + items: [ + { column: 'sido', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, + 'gugun', + 'pumpNm', + ] + }, + 'displayYear', + 'bscFctNm', + 'pumpCls', + 'addr', + 'rnDt', + 'planDrhgAr', { name: 'groupB', header: { text: '계획시간대최대배수량(m3/hr)' }, - items: ['DSN_QTY_A', 'DSN_QTY_B', 'DSN_QTY_DIFF'], + items: ['finewtDrngVl', 'rainwtDrngVl', 'vlWtr'], }, { name: 'groupC', header: { text: '배수능력' }, items: [ - 'TOT_PUMP_QTY_O', + 'pltwtrTotCpcty', { name: 'pumpO_Detail', header: { text: '오수펌프 상세' }, items: [ - { name: 'O1', header: { text: '오수펌프(1)' }, items: ['PUMP_O1_D', 'PUMP_O1_Q', 'PUMP_O1_C', 'PUMP_O1_S'] }, - { name: 'O2', header: { text: '오수펌프(2)' }, items: ['PUMP_O2_D', 'PUMP_O2_Q', 'PUMP_O2_C', 'PUMP_O2_S'] }, - { name: 'O3', header: { text: '오수펌프(3)' }, items: ['PUMP_O3_D', 'PUMP_O3_Q', 'PUMP_O3_C', 'PUMP_O3_S'] } + { name: 'O1', header: { text: '오수펌프(1)' }, items: ['pltwtrPumpCalbr1', 'pltwtrPumpCpcty1', 'pltwtrPumpCnt1', 'pltwtrPumpPreparCnt1'] }, + { name: 'O2', header: { text: '오수펌프(2)' }, items: ['pltwtrPumpCalbr2', 'pltwtrPumpCpcty2', 'pltwtrPumpCnt2', 'pltwtrPumpPreparCnt2'] }, + { name: 'O3', header: { text: '오수펌프(3)' }, items: ['pltwtrPumpCalbr3', 'pltwtrPumpCpcty3', 'pltwtrPumpCnt3', 'pltwtrPumpPreparCnt3'] } ] }, - 'TOT_PUMP_QTY_W', + 'rainwtrTotCpcty', { name: 'pumpW_Detail', header: { text: '우수펌프 상세' }, items: [ - { name: 'W1', header: { text: '우수펌프(1)' }, items: ['PUMP_W1_D', 'PUMP_W1_Q', 'PUMP_W1_C', 'PUMP_W1_S'] }, - { name: 'W2', header: { text: '우수펌프(2)' }, items: ['PUMP_W2_D', 'PUMP_W2_Q', 'PUMP_W2_C', 'PUMP_W2_S'] }, - { name: 'W3', header: { text: '우수펌프(3)' }, items: ['PUMP_W3_D', 'PUMP_W3_Q', 'PUMP_W3_C', 'PUMP_W3_S'] } + { name: 'W1', header: { text: '우수펌프(1)' }, items: ['rainwtrPumpCalbr1', 'rainwtrPumpCpcty1', 'rainwtrPumpCnt1', 'rainwtrPumpPreparCnt1'] }, + { name: 'W2', header: { text: '우수펌프(2)' }, items: ['rainwtrPumpCalbr2', 'rainwtrPumpCpcty2', 'rainwtrPumpCnt2', 'rainwtrPumpPreparCnt2'] }, + { name: 'W3', header: { text: '우수펌프(3)' }, items: ['rainwtrPumpCalbr3', 'rainwtrPumpCpcty3', 'rainwtrPumpCnt3', 'rainwtrPumpPreparCnt3'] } ] } ], }, - 'PWR_USAGE', 'RMK', - ] + 'elcpwAmnt', + 'rmk', + ], + fixedColumns: 2, + rowStartNum: 3 }; export const PumpingStationInfoList = { ...rawPumpingStationInfoList, columns: rawPumpingStationInfoList.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: col.styleName ?? "rg-cell-readonly" })) }; \ No newline at end of file diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index 20e3b532f..9fae80f45 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -23,7 +23,9 @@ import { type SewerReservoirRegionalApiResponse, type PumpingStationRegionalApiResponse } from "../model/sewage-reservoir-pump-review-interface"; - +import { + type MediationPumpStationApiResponse, +} from "../model/statistics-pumping-station-review-interface"; export class StatisticsManagementApiService { @@ -217,6 +219,26 @@ public async getPumpStationList( ); return response.data; } + +/** + * 05. 중계펌프장 통계관리 조회 + * @param params (yr, bsnmCd, sido, gugun, bscFctNm) + */ +public async getMediationPumpStationList( + params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업소 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + bscFctNm?: string; // 펌프장명 (시설명) + } +): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/mediation-pump-stations`, + { params } + ); + return response.data; +} } // 통합 인스턴스 수출 diff --git a/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts new file mode 100644 index 000000000..fbfc04711 --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts @@ -0,0 +1,86 @@ +import type { ApiResponse } from "./statistics-interface"; + +/** + * 중계펌프장 통계관리 아이템 인터페이스 + */ +export interface MediationPumpStationItem { + // 기본 정보 및 행 제어 + sido: string; // 시도 + gugun: string; // 시군구 + adstCd: string; // 행정코드 + pumpNm: string; // 펌프장명 + yr: string; // 연도 + displayYear: string; // 구분 표시용 (2024년확정 등) + rowType: 'OLD' | 'CURR'; // 전년도/당해년도 구분 + + // 주요 제원 + bscFctNm: string; // 처리장명 + pumpCls: string; // 구분 (합류식/분류식) + addr: string; // 위치 + rnDt: string; // 가동년월일 + planDrhgAr: number; // 계획배수면적(㎢) + finewtDrngVl: number; // 계획시간대최대배수량 - 청천시(A) + rainwtDrngVl: number; // 계획시간대최대배수량 - 우천시(B) + vlWtr: number; // 계획시간대최대배수량 - 우수량(B-A) + + // 오수펌프 상세 (PLTWTR) + pltwtrTotCpcty: number; // 오수 총양수량(㎥/min) + pltwtrPumpCalbr1: number; // 오수펌프1 구경(mm) + pltwtrPumpCpcty1: number; // 오수펌프1 양수량(㎥/min) + pltwtrPumpCnt1: number; // 오수펌프1 펌프대수 + pltwtrPumpPreparCnt1: number; // 오수펌프1 예비대수 + + pltwtrPumpCalbr2: number; // 오수펌프2 구경(mm) + pltwtrPumpCpcty2: number; // 오수펌프2 양수량(㎥/min) + pltwtrPumpCnt2: number; // 오수펌프2 펌프대수 + pltwtrPumpPreparCnt2: number; // 오수펌프2 예비대수 + + pltwtrPumpCalbr3: number; // 오수펌프3 구경(mm) + pltwtrPumpCpcty3: number; // 오수펌프3 양수량(㎥/min) + pltwtrPumpCnt3: number; // 오수펌프3 펌프대수 + pltwtrPumpPreparCnt3: number; // 오수펌프3 예비대수 + + // 우수펌프 상세 (RAINWTR) + rainwtrTotCpcty: number; // 우수 총양수량(㎥/min) + rainwtrPumpCalbr1: number; // 우수펌프1 구경(mm) + rainwtrPumpCpcty1: number; // 우수펌프1 양수량(㎥/min) + rainwtrPumpCnt1: number; // 우수펌프1 펌프대수 + rainwtrPumpPreparCnt1: number; // 우수펌프1 예비대수 + + rainwtrPumpCalbr2: number; // 우수펌프2 구경(mm) + rainwtrPumpCpcty2: number; // 우수펌프2 양수량(㎥/min) + rainwtrPumpCnt2: number; // 우수펌프2 펌프대수 + rainwtrPumpPreparCnt2: number; // 우수펌프2 예비대수 + + rainwtrPumpCalbr3: number; // 우수펌프3 구경(mm) + rainwtrPumpCpcty3: number; // 우수펌프3 양수량(㎥/min) + rainwtrPumpCnt3: number; // 우수펌프3 펌프대수 + rainwtrPumpPreparCnt3: number; // 우수펌프3 예비대수 + + // 기타 + elcpwAmnt: number; // 연간전력사용량(kWh/년) + rmk: string; // 비고 + lcnt: string; // 당해 존재 여부 ('1'|'0') + ocnt: string; // 전년 존재 여부 ('1'|'0') +} + + +export interface MediationPumpStationResponseData { + pumpStationList: MediationPumpStationItem[]; +} + + +export type MediationPumpStationApiResponse = ApiResponse & { + message: string; +}; + + +export interface MediationPumpStationService { + getMediationPumpStationList(params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업소 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + bscFctNm?: string; // 펌프장명 + }): Promise; +} \ No newline at end of file From cb29ac6c4d9524f64a2c3b58560510a09c6bbf58 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Fri, 20 Feb 2026 15:22:09 +0900 Subject: [PATCH 007/150] =?UTF-8?q?refactor:=20=EC=BB=AC=EB=9F=BC.ts-=20ed?= =?UTF-8?q?itable:=20false,=20styleName:=20"rg-cell-readonly"=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../column/review/PumpingStationRvwColumn.ts | 10 ++- .../review/SewagePipeMaintainRvwColumn.ts | 13 ++-- .../util/column/review/SewagePipeRvwColumn.ts | 27 +++----- .../review/SewageReservoirPumpRvwColumn.ts | 19 +++--- .../SeweragePenetrationRateRvwColumn.ts | 64 +++++++++---------- .../review/StatsInputStatusRvwColumn.ts | 3 +- 6 files changed, 58 insertions(+), 78 deletions(-) diff --git a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts index 7ab5e8e59..f031fed5e 100644 --- a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts +++ b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts @@ -10,7 +10,6 @@ export const rawPumpingStationInfoList = { fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { @@ -43,13 +42,12 @@ export const rawPumpingStationInfoList = { } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'pumpNm', fieldName: 'pumpNm', header: { text: '펌프장명' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'pumpNm', fieldName: 'pumpNm', header: { text: '펌프장명' }, mergeCells: true, sortdisable: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -82,7 +80,7 @@ export const rawPumpingStationInfoList = { }, { name: 'bscFctNm', fieldName: 'bscFctNm', header: { text: '처리장명' } }, { name: 'pumpCls', fieldName: 'pumpCls', header: { text: '구분\n(합류/분류)' } }, - { name: 'addr', fieldName: 'addr', header: { text: '위치' }, styleName: 'rg-cell-left' }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' } }, { name: 'rnDt', fieldName: 'rnDt', header: { text: '가동년월일\nyyyy-mm-dd' } }, { name: 'planDrhgAr', fieldName: 'planDrhgAr', header: { text: '계획배수면적\n(km2)' }, @@ -193,6 +191,6 @@ export const PumpingStationInfoList = { columns: rawPumpingStationInfoList.columns.map(col => ({ ...col, editable: false, - styleName: col.styleName ?? "rg-cell-readonly" + styleName: "rg-cell-readonly" })) }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts index ab32f39a5..f0ca4e7bc 100644 --- a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts @@ -16,8 +16,7 @@ const rawMaintainList = [ fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, - sortdisable: true, + tdisable: true, footer: [ { valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { @@ -53,14 +52,13 @@ const rawMaintainList = [ } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, /* 2. 구분 (displayYear) */ { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -165,7 +163,6 @@ const rawMaintainList = [ fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { @@ -198,12 +195,11 @@ const rawMaintainList = [ } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -291,6 +287,7 @@ export const SewagePipeMaintainColumnInfoList = rawMaintainList.map(tab => ({ ...tab, columns: tab.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) })); \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts index b57a54d44..4cc605836 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -16,7 +16,6 @@ const rawList = [ fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { @@ -55,13 +54,12 @@ const rawList = [ } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, mergeCells: true,editable: false }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true,sortdisable: true }, + { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, mergeCells: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -197,7 +195,6 @@ const rawList = [ fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { @@ -234,13 +231,12 @@ const rawList = [ } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'bscFctNm', fieldName: 'bscFctNm', header: { text: '하수처리장명' },mergeCells: true, editable: false }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'bscFctNm', fieldName: 'bscFctNm', header: { text: '하수처리장명' },mergeCells: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -362,7 +358,6 @@ const rawList = [ fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { @@ -393,13 +388,12 @@ const rawList = [ } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true, editable: false }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true,sortdisable: true }, + { name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -488,7 +482,6 @@ const rawList = [ fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { @@ -525,13 +518,12 @@ const rawList = [ } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true, editable: false }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -608,6 +600,7 @@ export const SewagePipeColumnInfoListRvw = rawList.map(tab => ({ ...tab, columns: tab.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) })); \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts index b5caf5604..ded536777 100644 --- a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts @@ -21,7 +21,6 @@ const rawReservoirList = [ fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { @@ -60,14 +59,13 @@ const rawReservoirList = [ } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'swgeStrgeNm', fieldName: 'swgeStrgeNm', header: { text: '시설명' }, mergeCells: true, editable: false }, - { name: 'addr', fieldName: 'addr', header: { text: '위치' }, mergeCells: true, editable: false }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'swgeStrgeNm', fieldName: 'swgeStrgeNm', header: { text: '시설명' }, mergeCells: true }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' }, mergeCells: true}, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -189,7 +187,6 @@ const rawReservoirList = [ fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { @@ -228,14 +225,13 @@ const rawReservoirList = [ } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'swgeStrgeNm', fieldName: 'swgeStrgeNm', header: { text: '시설명' }, mergeCells: true, editable: false }, - { name: 'addr', fieldName: 'addr', header: { text: '위치' }, mergeCells: true, editable: false }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'swgeStrgeNm', fieldName: 'swgeStrgeNm', header: { text: '시설명' }, mergeCells: true }, + { name: 'addr', fieldName: 'addr', header: { text: '위치' }, mergeCells: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { @@ -339,6 +335,7 @@ export const SewageReservoirPumpInfoListRvw = rawReservoirList.map(tab => ({ ...tab, columns: tab.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) })); \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts index bf1af2259..fed92fbde 100644 --- a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts @@ -2,88 +2,75 @@ import { ValueType } from "realgrid"; import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; -export const SeweragePenetrationRateInfoList = { + +const rawSeweragePenetrationRateInfoList = { columns: [ { name: 'sido', fieldName: 'sido', header: { text: '시도' }, mergeCells: true, - editable: false, sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 0; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); const uniqueValues = new Set(); for (let i = 0; i < rowCount; i++) { const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); - } + if (divisionValue) uniqueValues.add(String(divisionValue)); } const divisionValues = Array.from(uniqueValues); - if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; - return '합계'; + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; } }, { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 1; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); const uniqueValues = new Set(); for (let i = 0; i < rowCount; i++) { const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); - } + if (divisionValue) uniqueValues.add(String(divisionValue)); } const divisionValues = Array.from(uniqueValues); - if (divisionValues.length > divisionIndex) return divisionValues[divisionIndex] + '합계'; - return '합계'; + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; } } ] }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, editable: false, sortdisable: true }, - { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, mergeCells: true, editable: false, sortdisable: true }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, - editable: false, sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 0; const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); - } + 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, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 1; const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); - } + 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] + '합계' : ''; @@ -97,17 +84,15 @@ export const SeweragePenetrationRateInfoList = { { name: 'lgbsn', fieldName: 'lgbsn', header: { text: '중권역' } }, { name: 'mdmbsn', fieldName: 'mdmbsn', header: { text: '소권역' } }, { name: 'speclCntrplnAr', fieldName: 'speclCntrplnAr', header: { text: '특별대책지역' } }, - { name: 'popltnSm', fieldName: 'popltnSm', header: { text: '총인구' }, footer: footerIntArrayByDivision, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'popltnSm', fieldName: 'popltnSm', header: { text: '총인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'arSm', fieldName: 'arSm', header: { text: '총면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: 'swgeProcZoneInnerSm', fieldName: 'swgeProcZoneInnerSm', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-total' }, + { name: 'swgeProcZoneInnerSm', fieldName: 'swgeProcZoneInnerSm', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - // 공공하수처리인구 { name: 'pblswrgPopltnSm', fieldName: 'pblswrgPopltnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'pblswrgPrmTrt', fieldName: 'pblswrgPrmTrt', header: { text: '1차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'pblswrgScdTrt', fieldName: 'pblswrgScdTrt', header: { text: '2차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'pblswrgTertTrt', fieldName: 'pblswrgTertTrt', header: { text: '3차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - // 폐수처리인구(명) { name: 'wstrPopltnSm', fieldName: 'wstrPopltnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'wstrPrmTrt', fieldName: 'wstrPrmTrt', header: { text: '1차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'wstrScdTrt', fieldName: 'wstrScdTrt', header: { text: '2차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, @@ -183,4 +168,13 @@ export const SeweragePenetrationRateInfoList = { ], fixedColumns: 2, rowStartNum: 3 +}; + +export const SeweragePenetrationRateInfoList = { + ...rawSeweragePenetrationRateInfoList, + columns: rawSeweragePenetrationRateInfoList.columns.map(col => ({ + ...col, + editable: false, + styleName: "rg-cell-readonly" + })) }; \ No newline at end of file diff --git a/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts b/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts index a5408cc7f..9f280338c 100644 --- a/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts +++ b/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts @@ -115,6 +115,7 @@ export const StatsInputStatusInfoList = { ...StatsInputStatusInfoColumnRaw, columns: StatsInputStatusInfoColumnRaw.columns.map(col => ({ ...col, - editable: false + editable: false, + styleName: "rg-cell-readonly" })) }; \ No newline at end of file From 8fa00797c8dd4035999c4de4474eed697b7f5f97 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Fri, 20 Feb 2026 16:10:39 +0900 Subject: [PATCH 008/150] =?UTF-8?q?refactor:=20tab=20key=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=EB=90=9C=20=EC=BD=94=EB=93=9C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/review/SewagePipeMaintainRvw.tsx | 16 +++++++--------- .../statistics/review/SewagePipeRvw.tsx | 16 ++++++++-------- .../statistics/review/SewageReservoirPumpRvw.tsx | 13 +++++-------- .../column/review/SewagePipeMaintainRvwColumn.ts | 4 ++-- .../util/column/review/SewagePipeRvwColumn.ts | 8 ++++---- .../review/SewageReservoirPumpRvwColumn.ts | 4 ++-- 6 files changed, 28 insertions(+), 33 deletions(-) diff --git a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx index f2da6cce4..272ae04f1 100644 --- a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx @@ -19,7 +19,7 @@ const SewagePipeMaintainRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); const [isLoading, setIsLoading] = useState(false); - const [activeTab, setActiveTab] = useState('a'); + const [activeTab, setActiveTab] = useState('ST00030001'); const searchParamRef = useRef(null); @@ -36,7 +36,7 @@ const SewagePipeMaintainRvw = () => { const handleReset = () => { setRowDataMap(new Map()); - setActiveTab('a'); + setActiveTab('ST00030001'); searchParamRef.current = null; }; @@ -46,7 +46,7 @@ const SewagePipeMaintainRvw = () => { return; } - setActiveTab('a'); + setActiveTab('ST00030001'); setRowDataMap(new Map()); try { @@ -65,12 +65,11 @@ const SewagePipeMaintainRvw = () => { if (response?.status === 'success' && response.data) { const newRowDataMap = new Map[]>(); - // API 응답이 배열을 직접 반환하는 경우와 객체로 감싸진 경우 모두 처리 const listData = Array.isArray(response.data) ? response.data : (response.data.mtcStatusList || []); - newRowDataMap.set('a', toGridRows(listData as object[])); + newRowDataMap.set('ST00030001', toGridRows(listData as object[])); setRowDataMap(newRowDataMap); } else { alert(response?.message || "데이터가 없습니다."); @@ -85,7 +84,7 @@ const SewagePipeMaintainRvw = () => { const handleActiveTabChange = async (tabKey: string) => { setActiveTab(tabKey); - const existingAData = rowDataMap.get('a'); + const existingAData = rowDataMap.get('ST00030001'); const activeTabData = rowDataMap.get(tabKey); const hasAData = Array.isArray(existingAData) && existingAData.length > 0; @@ -97,9 +96,9 @@ const SewagePipeMaintainRvw = () => { let response; - if (tabKey === 'a') { + if (tabKey === 'ST00030001') { response = await statisticsManagementApi.getSewerPipeMtcList(searchParamRef.current); - }else if (tabKey === 'b') { + }else if (tabKey === 'ST00030002') { response = await statisticsManagementApi.getSewerPipeDrdgList(searchParamRef.current); } @@ -107,7 +106,6 @@ const SewagePipeMaintainRvw = () => { setRowDataMap(prev => { const next = new Map(prev); - // API 응답이 배열을 직접 반환하는 경우와 객체로 감싸진 경우 모두 처리 const listData = Array.isArray(response.data) ? response.data : (response.data.mtcStatusList || []); diff --git a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx index ac7033e53..94b79dfb0 100644 --- a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx @@ -19,7 +19,7 @@ const SewagePipeRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); const [isLoading, setIsLoading] = useState(false); - const [activeTab, setActiveTab] = useState('a'); + const [activeTab, setActiveTab] = useState('ST00020001'); const searchParamRef = useRef(null); @@ -36,7 +36,7 @@ const SewagePipeRvw = () => { const handleReset = () => { setRowDataMap(new Map()); - setActiveTab('a'); + setActiveTab('ST00020001'); }; const handleSearch = async (values: SearchFormValues) => { @@ -45,7 +45,7 @@ const SewagePipeRvw = () => { return; } - setActiveTab('a'); + setActiveTab('ST00020001'); setRowDataMap(new Map()); @@ -64,7 +64,7 @@ const SewagePipeRvw = () => { if (response?.status === 'success' && response.data) { const newRowDataMap = new Map[]>(); - newRowDataMap.set('a', toGridRows(response.data as object[])); + newRowDataMap.set('ST00020001', toGridRows(response.data as object[])); setRowDataMap(newRowDataMap); } else { alert(response?.message || "데이터가 없습니다."); @@ -79,7 +79,7 @@ const SewagePipeRvw = () => { const handleActiveTabChange = async (tabKey: string) => { setActiveTab(tabKey); - const existingAData = rowDataMap.get('a'); + const existingAData = rowDataMap.get('ST00020001'); const activeTabData = rowDataMap.get(tabKey); const hasAData = Array.isArray(existingAData) && existingAData.length > 0; @@ -92,11 +92,11 @@ const SewagePipeRvw = () => { let response; - if (tabKey === 'b') { + if (tabKey === 'ST00020002') { response = await statisticsManagementApi.getSewagePipeFacilityList(searchParamRef.current); - }else if(tabKey === 'c'){ + }else if(tabKey === 'ST00020003'){ response = await statisticsManagementApi.getSewagePipeTypeList(searchParamRef.current); - }else if(tabKey === 'd'){ + }else if(tabKey === 'ST00020004'){ response = await statisticsManagementApi.getSewagePipeDiamList(searchParamRef.current); } diff --git a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx index 3b7645a4e..346edb0cd 100644 --- a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx +++ b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx @@ -23,7 +23,7 @@ const SewageReservoirPumpRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); const [isLoading, setIsLoading] = useState(false); - const [activeTab, setActiveTab] = useState('a'); + const [activeTab, setActiveTab] = useState('ST00040001'); const searchParamRef = useRef(null); @@ -40,18 +40,17 @@ const SewageReservoirPumpRvw = () => { const handleReset = () => { setRowDataMap(new Map()); - setActiveTab('a'); + setActiveTab('ST00040001'); searchParamRef.current = null; }; - // 조회 버튼 클릭 시: Tab A(하수저류시설) 데이터 로드 const handleSearch = async (values: SearchFormValues) => { if (!values.sido || values.sido === 'ALL') { alert('시도를 선택해주세요.'); return; } - setActiveTab('a'); + setActiveTab('ST00040001'); setRowDataMap(new Map()); try { @@ -71,7 +70,7 @@ const SewageReservoirPumpRvw = () => { if (response?.status === 'success' && response.data) { const newRowDataMap = new Map[]>(); - newRowDataMap.set('a', toGridRows(response.data as object[])); + newRowDataMap.set('ST00040001', toGridRows(response.data as object[])); setRowDataMap(newRowDataMap); } else { alert(response?.message || "데이터가 없습니다."); @@ -83,20 +82,18 @@ const SewageReservoirPumpRvw = () => { } }; - // 탭 전환 시: 데이터가 없는 경우 지연 로딩 (Tab B: 빗물펌프장) const handleActiveTabChange = async (tabKey: string) => { setActiveTab(tabKey); const activeTabData = rowDataMap.get(tabKey); const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0; - // 메인 데이터(Tab A)가 있고, 현재 탭 데이터가 없을 때만 API 호출 if (searchParamRef.current && !hasActiveTabData) { try { setIsLoading(true); let response; - if (tabKey === 'b') { + if (tabKey === 'ST00040002') { response = await statisticsManagementApi.getPumpStationList(searchParamRef.current); } diff --git a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts index f0ca4e7bc..aaf2bed67 100644 --- a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts @@ -5,7 +5,7 @@ import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataG // 하수도 통계 관리 03. 하수관로 유지 관리 const rawMaintainList = [ { - tabKey: 'a', + tabKey: 'ST00030001', tabLabel: '개보수', rowStartNum: 4, fixedColumns: 2, @@ -152,7 +152,7 @@ const rawMaintainList = [ ] }, { - tabKey: 'b', + tabKey: 'ST00030002', tabLabel: '관로준설', rowStartNum: 4, fixedColumns: 2, diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts index 4cc605836..93f52068f 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -6,7 +6,7 @@ import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataG // 하수도 통계 관리 02. 하수관로 const rawList = [ { - tabKey: 'a', + tabKey: 'ST00020001', tabLabel: '지역별 시설현황', rowStartNum: 4, fixedColumns: 2, @@ -185,7 +185,7 @@ const rawList = [ ] }, { - tabKey: 'b', + tabKey: 'ST00020002', tabLabel: '시설별 설치현황', rowStartNum: 4, fixedColumns: 3, @@ -348,7 +348,7 @@ const rawList = [ ] }, { - tabKey: 'c', + tabKey: 'ST00020003', tabLabel: '관종별 현황', rowStartNum: 2, fixedColumns: 3, @@ -472,7 +472,7 @@ const rawList = [ ] }, { - tabKey: 'd', + tabKey: 'ST00020004', tabLabel: '관경별 현황', rowStartNum: 2, fixedColumns: 3, diff --git a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts index ded536777..415d2896f 100644 --- a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts @@ -10,7 +10,7 @@ import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataG // 하수도 통계 관리 04. 하수저류 시설 및 빗물 펌프장 const rawReservoirList = [ { - tabKey: 'a', + tabKey: 'ST00040001', tabLabel: '하수저류시설현황', rowStartNum: 3, fixedColumns: 2, @@ -176,7 +176,7 @@ const rawReservoirList = [ ] }, { - tabKey: 'b', + tabKey: 'ST00040002', tabLabel: '빗물펌프장', rowStartNum: 3, fixedColumns: 2, From 103efe79d3197d5d6a9d251c066fd0c2a91b0274 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Mon, 23 Feb 2026 10:03:37 +0900 Subject: [PATCH 009/150] =?UTF-8?q?st:=20feat:=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EC=83=81=ED=99=A9=20=EC=A0=95=EB=B3=B4=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/css/DataGrid.css | 12 + apps/st/src/components/module/DataGrid.tsx | 227 ++++++------------ .../input/SeweragePenetrationRateInp.tsx | 33 ++- apps/st/src/components/type/DataGrid.type.ts | 2 + 4 files changed, 111 insertions(+), 163 deletions(-) diff --git a/apps/st/src/components/css/DataGrid.css b/apps/st/src/components/css/DataGrid.css index f602400c7..888d9fec1 100644 --- a/apps/st/src/components/css/DataGrid.css +++ b/apps/st/src/components/css/DataGrid.css @@ -21,6 +21,18 @@ @apply !bg-[#e3eaf2]; } + /* 진행상황: 소프트 인라인 요약 (툴바와 자연스럽게 blend) */ + .data-grid-status-bubble { + @apply rounded-lg border border-slate-200/80 bg-slate-50/90 px-4 py-2.5; + box-shadow: none; + } + .data-grid-status-bubble span { + @apply text-base text-slate-700; + } + .data-grid-status-bubble span + span { + @apply ml-4 border-l border-slate-200/70 pl-4; + } + [data-theme="black"] { .rg-grid .rg-header .rg-table .rg-header-group-cell { @apply !bg-[#3C4043] !text-[#fff]; diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index 7d7f925ee..5eebe138e 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -86,110 +86,6 @@ function toNum(v: unknown): number { return parseFloat(String(v).replace(/,/g, "")) || NaN; } -/** 헤더에 정렬 화살표 표시 (CSS 방식) */ -function updateHeaderSortIndicator( - gridView: { - columnByIndex?: ( - i: number, - ) => { fieldName?: string; name?: string } | undefined; - columnByName?: ( - name: string, - ) => { fieldName?: string; name?: string } | undefined; - getColumn?: ( - i: number, - ) => { fieldName?: string; name?: string } | undefined; - }, - columns: { fieldName?: string; name: string }[], - sortDir: Record, -) { - try { - columns.forEach((col) => { - if (!col.fieldName && !col.name) return; - - // 컬럼 찾기 - let gridCol: { fieldName?: string; name?: string } | undefined; - for (let i = 0; i < 100; i++) { - const c = - gridView.columnByIndex?.(i) ?? gridView.getColumn?.(i); - if (!c) break; - if ( - c.fieldName === (col.fieldName || col.name) || - c.name === (col.fieldName || col.name) - ) { - gridCol = c; - break; - } - } - if (!gridCol) { - gridCol = gridView.columnByName?.(col.name); - } - - if (!gridCol) return; - - const fieldName = - gridCol.fieldName || gridCol.name || col.fieldName || col.name; - const direction = sortDir[fieldName] || sortDir[col.name] || "none"; - - // DOM에서 헤더 요소 찾기 - const gridEl1 = document.querySelector( - "[data-grid-id]", - ) as HTMLElement | null; - const gridEl2 = document.querySelector( - ".rg-grid", - ) as HTMLElement | null; - const gridEl = gridEl1 || gridEl2; - if (!gridEl) return; - - // 헤더 셀 찾기 (fieldName 또는 name으로) - const headerCells = gridEl.querySelectorAll( - '.rg-header-cell, [class*="header"]', - ); - let targetCell: HTMLElement | null = null; - - headerCells.forEach((cell) => { - const el = cell as HTMLElement; - const text = el.textContent?.trim(); - const colName = col.name; - const colFieldName = col.fieldName; - if ( - text && - (text.includes(colName) || - (colFieldName && text.includes(colFieldName))) - ) { - targetCell = el; - } - }); - - if (!targetCell) return; - - const headerCell = targetCell as HTMLElement; - - // 기존 화살표 제거 - const existingIndicator = headerCell.querySelector( - ".rg-sort-indicator", - ) as HTMLElement | null; - if (existingIndicator) { - existingIndicator.remove(); - } - - // 화살표 추가 - if (direction === "asc" || direction === "desc") { - const indicator = document.createElement("span"); - indicator.className = "rg-sort-indicator"; - indicator.textContent = direction === "asc" ? "▲" : "▼"; - indicator.style.marginLeft = "4px"; - indicator.style.fontSize = "10px"; - indicator.style.color = "#666"; - indicator.style.display = "inline-block"; - indicator.style.verticalAlign = "middle"; - headerCell.appendChild(indicator); - } - }); - } catch (e) { - console.warn("updateHeaderSortIndicator error:", e); - } -} - /** onSorting: mergeCells: true인 컬럼이 있으면 그룹 내 순서 유지 정렬, 없으면 RealGrid 기본 정렬 */ function createOnSortingHandler( columns: { @@ -416,6 +312,7 @@ const DataGrid = ({ getGridDataRef, cellStyleCallback, onRowClick, + statusInfo, }: DataGridProps) => { const { columns, columnLayout } = columnInfo; const gridRef = useRef(null); @@ -624,10 +521,10 @@ const DataGrid = ({ ) { const cell = worksheet[ - XLSX.utils.encode_cell({ - r: rowIndex, - c: col, - }) + XLSX.utils.encode_cell({ + r: rowIndex, + c: col, + }) ]; if ( cell && @@ -703,7 +600,7 @@ const DataGrid = ({ const col = range.s.c + colIndex; const cell = worksheet[ - XLSX.utils.encode_cell({ r: row, c: col }) + XLSX.utils.encode_cell({ r: row, c: col }) ]; const headerName = headers[colIndex] || `Column${colIndex + 1}`; @@ -999,7 +896,7 @@ const DataGrid = ({ ) { ( column.footer[ - index + index ] as FooterItemWithCallback ).valueCallback = footerItem.valueCallback; @@ -1050,11 +947,11 @@ const DataGrid = ({ getGridDataRef.current = () => { const gv = gridRef.current?.gridView as | { - getDataSource?: () => { - getRowCount(): number; - getValue(r: number, f: string): unknown; - }; - } + getDataSource?: () => { + getRowCount(): number; + getValue(r: number, f: string): unknown; + }; + } | undefined; if (!gv?.getDataSource) return []; const ds = gv.getDataSource(); @@ -1126,6 +1023,22 @@ const DataGrid = ({ onChange={handleFileChange} />
+ {statusInfo && +
+ 진행상황: { + statusInfo.locgStatus === null || statusInfo.locgStatus === '0' ? + '미작성' + : + statusInfo.locgStatus === '1' ? '작성중' : statusInfo.locgStatus === '2' ? '제출' + : + statusInfo.locgStatus === '3' ? '승인' : '반려' + } + + 제출일: {statusInfo.locgDt ? statusInfo.locgDt : '미제출'} + 담당자: {statusInfo.locgCharger ? statusInfo.locgCharger : '-'} + 연락처: {statusInfo.locgCttpc ? statusInfo.locgCttpc : '-'} +
+ } {isExcelImport && ( + + + + + + ); +}; + +export default SubmitDailog; \ No newline at end of file diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index eea0126c9..5635b7f8a 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -212,6 +212,10 @@ export type CellStyleCallbackParams = { export type CellStyleCallback = (params: CellStyleCallbackParams) => string | undefined; +/** 셀 hover 시 툴팁 문구 (반환 시 해당 문구 표시, 검증 오류 메시지 등) */ +export type CellTooltipCallbackParams = { row: Record; fieldName: string }; +export type CellTooltipCallback = (params: CellTooltipCallbackParams) => string | undefined; + export type DataGridProps = { columnInfo: ColumnInfo; rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주) @@ -226,6 +230,8 @@ export type DataGridProps = { >; /** 셀 단위로 styleName을 동적으로 결정 (예: 검증 오류 셀) */ cellStyleCallback?: CellStyleCallback; + /** 셀 hover 시 툴팁 문구 (예: verificationResult의 rsnExpln) */ + cellTooltipCallback?: CellTooltipCallback; /** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */ getGridDataRef?: { current: (() => Record[]) | null; diff --git a/apps/st/src/components/util/column/input/DoList.ts b/apps/st/src/components/util/column/input/DoList.ts new file mode 100644 index 000000000..2b58bd57b --- /dev/null +++ b/apps/st/src/components/util/column/input/DoList.ts @@ -0,0 +1,23 @@ +export const doList = [ + "4100000000", + "4200000000", + "4300000000", + "4400000000", + "4500000000", + "4600000000", + "4700000000", + "4800000000", +]; + +export const needBsnmFrgnrPoplSmSidoList = [ + "3600000000", + "4100000000", + "4200000000", + "4300000000", + "4400000000", + "4500000000", + "4600000000", + "4700000000", + "4800000000", + "5000000000", +]; \ No newline at end of file diff --git a/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts b/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts index 41a9c5700..6cd283565 100644 --- a/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts +++ b/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts @@ -7,13 +7,13 @@ import { ValueType } from "realgrid"; export const SeweragePenetrationRateInpColumn = { columns: [ // 행정구역 - { name: 'sido', fieldName: 'sido', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' }, - { name: 'adstDong', fieldName: 'adstDong', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' }, + { 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: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' }, editable: false, styleName: 'rg-cell-readonly' }, - { name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' } }, + { name: 'trbtrNm', fieldName: 'trbtrNm', header: { text: '지류' } }, // 수계영향권별분류 { name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' }, editable: false, styleName: 'rg-cell-readonly' }, @@ -21,7 +21,7 @@ export const SeweragePenetrationRateInpColumn = { { name: 'mdmbsn', fieldName: 'mdmbsn', header: { text: '소권역' }, editable: false, styleName: 'rg-cell-readonly' }, // 특별대책지역 - { name: 'speclCntrplnAr', fieldName: 'speclCntrplnAr', header: { text: '특별대책지역' } }, + { name: 'spcCtpnRgnCn', fieldName: 'spcCtpnRgnCn', header: { text: '특별대책지역' } }, // 행정참고자료인구 { name: 'totalPopl', fieldName: 'totalPopl', header: { text: '총인구' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, @@ -29,113 +29,113 @@ export const SeweragePenetrationRateInpColumn = { { name: 'frgnrPoplSm', fieldName: 'frgnrPoplSm', header: { text: '외국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, // 행정참고자료면적 - { name: 'area', fieldName: 'area', header: { text: '행정참고자료면적(㎢)' }, footer: footerDecimal3, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000', styleName: 'rg-cell-readonly' }, + { name: 'refArea', fieldName: 'refArea', header: { text: '행정참고자료면적(㎢)' }, footer: footerDecimal3, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000', styleName: 'rg-cell-readonly' }, // 총인구 { - name: 'popltnSm', - fieldName: 'popltnSm', + name: 'wholPpltnCnt', + fieldName: 'wholPpltnCnt', header: { text: '총인구(명)' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'sum' as const }, + computed: { fields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt', 'unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, // 총면적 { - name: 'arSm', - fieldName: 'arSm', + name: 'wholArea', + fieldName: 'wholArea', header: { text: '총면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000', - computed: { fields: ['swgeProcZoneInnerAr', 'swgeProcZoneOuterAr'], type: 'sum' as const }, + computed: { fields: ['staaArea', 'staaExcpArea'], type: 'sum' as const }, styleName: 'rg-cell-total', }, // 하수처리구역 내 > 합계 { - name: 'swgeProcZoneInnerSm', - fieldName: 'swgeProcZoneInnerSm', + name: 'wholSwtrmtPpltnCnt', + fieldName: 'wholSwtrmtPpltnCnt', header: { text: '합계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], type: 'sum' as const }, + computed: { fields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, // 하수처리구역 내 > 공공하수처리인구 { - name: 'pblswrgPopltnSm', - fieldName: 'pblswrgPopltnSm', + name: 'wholPblSwtrmtPpltnCnt', + fieldName: 'wholPblSwtrmtPpltnCnt', header: { text: '소계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'], type: 'sum' as const }, + computed: { fields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, - { name: 'pblswrgPrmTrt', fieldName: 'pblswrgPrmTrt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'pblswrgScdTrt', fieldName: 'pblswrgScdTrt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'pblswrgTertTrt', fieldName: 'pblswrgTertTrt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts1SwtrmtPpltnCnt', fieldName: 'ts1SwtrmtPpltnCnt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts2SwtrmtPpltnCnt', fieldName: 'ts2SwtrmtPpltnCnt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts3SwtrmtPpltnCnt', fieldName: 'ts3SwtrmtPpltnCnt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 하수처리구역 내 > 폐수처리인구 { - name: 'wstrPopltnSm', - fieldName: 'wstrPopltnSm', + name: 'wholWwtPrcsPpltnCnt', + fieldName: 'wholWwtPrcsPpltnCnt', header: { text: '소계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], type: 'sum' as const }, + computed: { fields: ['ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, - { name: 'wstrPrmTrt', fieldName: 'wstrPrmTrt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'wstrScdTrt', fieldName: 'wstrScdTrt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'wstrTertTrt', fieldName: 'wstrTertTrt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts1WwtPrcsPpltnCnt', fieldName: 'ts1WwtPrcsPpltnCnt', header: { text: '물리적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts2WwtPrcsPpltnCnt', fieldName: 'ts2WwtPrcsPpltnCnt', header: { text: '생물학적처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'ts3WwtPrcsPpltnCnt', fieldName: 'ts3WwtPrcsPpltnCnt', header: { text: '고도처리' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 하수처리구역 내 > 미접속인구 - { name: 'notConectPopltn', fieldName: 'notConectPopltn', header: { text: '미접속인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'yetCntnPpltnCnt', fieldName: 'yetCntnPpltnCnt', header: { text: '미접속인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 하수처리구역 내 > 면적 - { name: 'swgeProcZoneInnerAr', fieldName: 'swgeProcZoneInnerAr', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'staaArea', fieldName: 'staaArea', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, // 하수처리구역 외 { - name: 'swgeProcZoneOuterSm', - fieldName: 'swgeProcZoneOuterSm', + name: 'wholStaaExcpPpltnCnt', + fieldName: 'wholStaaExcpPpltnCnt', header: { text: '합계' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'sum' as const }, + computed: { fields: ['unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt'], type: 'sum' as const }, styleName: 'rg-cell-total', }, - { name: 'townUnprocPopltn', fieldName: 'townUnprocPopltn', header: { text: '미처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'townSwttPopltn', fieldName: 'townSwttPopltn', header: { text: '오수처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'townWrrtnPopltn', fieldName: 'townWrrtnPopltn', header: { text: '정화조인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'swgeProcZoneOuterAr', fieldName: 'swgeProcZoneOuterAr', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'unprcsPpltnCnt', fieldName: 'unprcsPpltnCnt', header: { text: '미처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'drtwPrcsPpltnCnt', fieldName: 'drtwPrcsPpltnCnt', header: { text: '오수처리인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'spctnkPpltnCnt', fieldName: 'spctnkPpltnCnt', header: { text: '정화조인구' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'staaExcpArea', fieldName: 'staaExcpArea', header: { text: '면적(㎢)' }, footer: footerDecimal3, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, // 공공하수처리구역 인구보급률 (하수처리구역 내 인구 / 전체 인구 * 100) { - name: 'pblswrgPopltnSuprt', - fieldName: 'pblswrgPopltnSuprt', + name: 'staaPsplyRt', + fieldName: 'staaPsplyRt', header: { text: '공공하수처리구역인구보급률(%)' }, footer: footerAvgDecimal, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', computed: { - numeratorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], - denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], + numeratorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt'], + denominatorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt', 'unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt'], type: 'pblswrgPopltnSuprtRate' as const }, styleName: 'rg-cell-total', @@ -143,16 +143,16 @@ export const SeweragePenetrationRateInpColumn = { // 고도처리 인구보급률 (고도처리 인구 / 전체 인구 * 100) { - name: 'adtrtPopltnSuprt', - fieldName: 'adtrtPopltnSuprt', + name: 'altdPrcsPsplyRt', + fieldName: 'altdPrcsPsplyRt', header: { text: '고도처리인구보급률(%)' }, footer: footerAvgDecimal, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', computed: { - numeratorFields: ['pblswrgTertTrt', 'wstrTertTrt'], - denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], + numeratorFields: ['ts3SwtrmtPpltnCnt', 'ts3WwtPrcsPpltnCnt'], + denominatorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt', 'unprcsPpltnCnt', 'drtwPrcsPpltnCnt', 'spctnkPpltnCnt'], type: 'adtrtPopltnSuprtRate' as const }, styleName: 'rg-cell-total', @@ -160,27 +160,27 @@ export const SeweragePenetrationRateInpColumn = { // 하수도 설치율 { - name: 'swrgInstlrt', - fieldName: 'swrgInstlrt', + name: 'sewerInstlRt', + fieldName: 'sewerInstlRt', header: { text: '하수도설치율(%)' }, footer: footerAvgDecimal, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', computed: { - numeratorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'swgeProcZoneOuterSm'], - denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], - thirdFields: ['piplSupprt'], + numeratorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt', 'wholStaaExcpPpltnCnt'], + denominatorFields: ['ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt', 'yetCntnPpltnCnt'], + thirdFields: ['swagePplPsplyRt'], type: 'swrgInstlrtRate' as const }, styleName: 'rg-cell-total', }, // 비고 - { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - { name: 'piplSupprt', fieldName: 'piplSupprt', header: { text: '하수관로보급률(%)' }, visible: false }, - { name: 'adstCd', fieldName: 'adstCd', header: { text: '행정코드' }, visible: false }, + { name: 'swagePplPsplyRt', fieldName: 'swagePplPsplyRt', header: { text: '하수관로보급률(%)' }, visible: false }, + { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정코드' }, visible: false }, ], columnLayout: [ @@ -189,49 +189,49 @@ export const SeweragePenetrationRateInpColumn = { hideChildHeaders: false, header: { text: '행정구역' }, items: [ - { column: 'sido', footerUserSpans: [{ colspan: 3 }] }, - 'gugun', - 'adstDong', + { column: 'ctpvNm', footerUserSpans: [{ colspan: 3 }] }, + 'sggNm', + 'admdstNm', ], }, 'rvbsn', - 'bstrm', + 'trbtrNm', { name: 'groupB', hideChildHeaders: false, header: { text: '수계영향권별분류' }, items: ['mdbsn', 'lgbsn', 'mdmbsn'], }, - 'speclCntrplnAr', + 'spcCtpnRgnCn', { name: 'groupC', hideChildHeaders: false, header: { text: '행정참고자료인구' }, items: ['totalPopl', 'poplSm', 'frgnrPoplSm'], }, - 'area', - 'popltnSm', - 'arSm', + 'refArea', + 'wholPpltnCnt', + 'wholArea', { name: 'groupD', hideChildHeaders: false, header: { text: '하수처리구역내' }, items: [ - 'swgeProcZoneInnerSm', + 'wholSwtrmtPpltnCnt', { name: 'pblswrgGroup', hideChildHeaders: false, header: { text: '공공하수처리인구(명)' }, - items: ['pblswrgPopltnSm', 'pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'], + items: ['wholPblSwtrmtPpltnCnt', 'ts1SwtrmtPpltnCnt', 'ts2SwtrmtPpltnCnt', 'ts3SwtrmtPpltnCnt'], }, { name: 'wstrGroup', hideChildHeaders: false, header: { text: '폐수처리인구(명)' }, - items: ['wstrPopltnSm', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], + items: ['wholWwtPrcsPpltnCnt', 'ts1WwtPrcsPpltnCnt', 'ts2WwtPrcsPpltnCnt', 'ts3WwtPrcsPpltnCnt'], }, - 'notConectPopltn', - 'swgeProcZoneInnerAr' + 'yetCntnPpltnCnt', + 'staaArea' ], }, { @@ -239,18 +239,18 @@ export const SeweragePenetrationRateInpColumn = { hideChildHeaders: false, header: { text: '하수처리구역외' }, items: [ - 'swgeProcZoneOuterSm', - 'townUnprocPopltn', - 'townSwttPopltn', - 'townWrrtnPopltn', - 'swgeProcZoneOuterAr' + 'wholStaaExcpPpltnCnt', + 'unprcsPpltnCnt', + 'drtwPrcsPpltnCnt', + 'spctnkPpltnCnt', + 'staaExcpArea' ], }, - 'pblswrgPopltnSuprt', - 'adtrtPopltnSuprt', - 'swrgInstlrt', - 'rmk', - 'piplSupprt', - 'adstCd', + 'staaPsplyRt', + 'altdPrcsPsplyRt', + 'sewerInstlRt', + 'rmrk', + 'swagePplPsplyRt', + 'admdstCd', ] }; \ No newline at end of file diff --git a/apps/st/src/entities/statistics/api/statistics-input-service.ts b/apps/st/src/entities/statistics/api/statistics-input-service.ts index f8fdc0850..a45251549 100644 --- a/apps/st/src/entities/statistics/api/statistics-input-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-input-service.ts @@ -38,6 +38,15 @@ export class StatisticsInputAPiService implements StatisticsInputService { return response.data; } + + public async submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise { + const response = await axios.post ( + `${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate/submit`, + params + ); + + return response.data; + } } export const statisticsInputAPi = new StatisticsInputAPiService(); \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-input-interface.ts b/apps/st/src/entities/statistics/model/statistics-input-interface.ts index 3fd0ba0c8..72b519acc 100644 --- a/apps/st/src/entities/statistics/model/statistics-input-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-input-interface.ts @@ -2,120 +2,83 @@ import type { ApiResponse } from "./statistics-interface"; // 하수도 보급률 데이터 항목 export interface SewerageSupplyRateStatusInfo { - locgStatus: string; - locgDt: string; - locgCharger: string; - locgCttpc: string; - prvnStatus: string; - dcsnStatus: string; + locgStatus: string | null; + locgDt: string | null; + locgCharger: string | null; + locgCttpc: string | null; + locgJob: string | null; + prvnStatus: string | null; + dcsnStatus: string | null; wrterOpinionSm: string | null; chckerOpinionSm: string | null; returnOpinionSm: string | null; + locgCnfirmDept: string | null; + locgCnfirmJob: string | null; + locgCnfirmNm: string | null; + locgCnfirmPh: string | null; + locgDept: string | null; } export interface SewerageSupplyRateCommonInfo { - bsnmArea: number; - bsnmCd: string; - bsnmFrgnrPoplSm: number; - bsnmPoplSm: number; - bsnmTotalPopl: number; + sewerBzmnArea: number; + sewerBzmnCd: string; + sewerBzmnFrgnrPoplSm: number; + sewerBzmnPoplSm: number; + sewerBzmnTotalPopl: number; piplFcltyExtn: number; piplPlanExtn: number; - piplSupprt: number; - sido: string; - yr: string; + swagePplPsplyRt: number; + ctpvNm: string; + statsYr: string; } /** 검증 결과 한 건 (필드별 메시지) */ export interface VerificationResultItem { - type: string; - rsn: string; + vrfcCd: string; + rsnExpln: string; } /** 필드명 → 검증 결과 배열. 예: { popltnSm: [{ type, rsn }], wstrPrmTrt: [...] } */ export type SewerageSupplyRateVerificationResult = Record; +/** 하수도 보급률 목록 API 응답 한 행 (실제 응답 필드만) */ export interface SewerageSupplyRateItem { - sido: string; - gugun: string; - adstDong: string; - rgnNm: string; - adstCd: string; - yr: string; - bsnmCd: string; - rvbsn: string; - lgbsn: string; + admdstCd: string; + sggNm: string; + admdstNm: string; + rvbsn: string | null; + trbtrNm: string; mdbsn: string; + lgbsn: string; mdmbsn: string; + spcCtpnRgnCn: string | null; totalPopl: number; poplSm: number; frgnrPoplSm: number; - area: number; - bsnmTotalPopl: number; - bsnmPoplSm: number; - bsnmFrgnrPoplSm: number; - bsnmArea: number; - piplPlanExtn: number; - piplFcltyExtn: number; - piplSupprt: number; - wrssm: string | null; - bstrm: string; - speclCntrplnAr: string | null; - popltnSm: number; - arSm: number; - swgeProcZoneInnerSm: number; - pblswrgPopltnSm: number; - pblswrgPrmTrt: number; - pblswrgScdTrt: number; - pblswrgTertTrt: number; - wstrPopltnSm: number; - wstrPrmTrt: number; - wstrScdTrt: number; - wstrTertTrt: number; - swgeProcZoneInnerAr: number; - swgeProcZoneOuterSm: number; - townUnprocPopltn: number; - townSwttPopltn: number; - townWrrtnPopltn: number; - uptownUnprPopltn: number; - uptownSwttPopltn: number; - uptownWrrtnPopltn: number; - swgeProcZoneOuterAr: number; - pblswrgPopltnSuprt: number; - adtrtPopltnSuprt: number; - swrgInstlrt: number; - wrterOpinion: string | null; - chckerOpinion: string | null; - notConectPopltn: number; - rmk: string; - chkWrssm: string; - chkBstrm: string; - chkSpeclCntrplnAr: string; - chkPopltnSm: string; - chkArSm: string; - chkSwgeProcZoneInnerSm: string; - chkPblswrgPopltnSm: string; - chkPblswrgPrmTrt: string; - chkPblswrgScdTrt: string; - chkPblswrgTertTrt: string; - chkWstrPopltnSm: string; - chkWstrPrmTrt: string; - chkWstrScdTrt: string; - chkWstrTertTrt: string; - chkSwgeProcZoneInnerAr: string; - chkSwgeProcZoneOuterSm: string; - chkTownUnprocPopltn: string; - chkTownSwttPopltn: string; - chkTownWrrtnPopltn: string; - chkUptownUnprPopltn: string; - chkUptownSwttPopltn: string; - chkUptownWrrtnPopltn: string; - chkSwgeProcZoneOuterAr: string; - chkPblswrgPopltnSuprt: string; - chkAdtrtPopltnSuprt: string; - chkSwrgInstlrt: string; - chkNotConectPopltn: string; - verificationResult?: SewerageSupplyRateVerificationResult; + refArea: number; + wholPpltnCnt: number; + wholArea: number; + wholSwtrmtPpltnCnt: number; + wholPblSwtrmtPpltnCnt: number; + ts1SwtrmtPpltnCnt: number; + ts2SwtrmtPpltnCnt: number; + ts3SwtrmtPpltnCnt: number; + wholWwtPrcsPpltnCnt: number; + ts1WwtPrcsPpltnCnt: number; + ts2WwtPrcsPpltnCnt: number; + ts3WwtPrcsPpltnCnt: number; + yetCntnPpltnCnt: number; + staaArea: number; + wholStaaExcpPpltnCnt: number; + unprcsPpltnCnt: number; + drtwPrcsPpltnCnt: number; + spctnkPpltnCnt: number; + staaExcpArea: number; + staaPsplyRt: number; + altdPrcsPsplyRt: number; + sewerInstlRt: number; + rmrk: string | null; + verificationResult?: SewerageSupplyRateVerificationResult | null; } export interface SewerageSupplyRateResponseData { @@ -129,55 +92,63 @@ export type SewerageSupplyRateApiResponse = ApiResponse; checkInputPeriod(params: string): Promise; saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise; + submitSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise; } \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-input-submit-info-schema.ts b/apps/st/src/entities/statistics/model/statistics-input-submit-info-schema.ts new file mode 100644 index 000000000..fb3380b6b --- /dev/null +++ b/apps/st/src/entities/statistics/model/statistics-input-submit-info-schema.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; + +export const statisticsInputSubmitInfoSchema = z.object({ + // 작성자 명, 작성자 직제, 작성자 부서, 작성자 연락처, 승인자명, 승인자 직제, 승인자 부서, 승인자 연락처 (전부 필수) + locgCharger: z.string().min(1, "작성자 명을 입력하세요"), + locgJob: z.string().min(1, "작성자 직제를 입력하세요"), + locgDept: z.string().min(1, "작성자 부서를 입력하세요"), + locgCttpc: z.string().min(1, "작성자 연락처를 입력하세요"), + locgCnfirmNm: z.string().min(1, "승인자명을 입력하세요"), + locgCnfirmJob: z.string().min(1, "승인자 직제를 입력하세요"), + locgCnfirmDept: z.string().min(1, "승인자 부서를 입력하세요"), + locgCnfirmPh: z.string().min(1, "승인자 연락처를 입력하세요"), +}); + +export type StatisticsInputSubmitInfoFormData = z.infer< + typeof statisticsInputSubmitInfoSchema +>; \ No newline at end of file From 5600ce01bd5670374be2887aaee9ec5a5cbdb5d4 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Wed, 25 Feb 2026 16:13:48 +0900 Subject: [PATCH 018/150] =?UTF-8?q?st:=20fix:=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=93=9C=20mergeCell=20sort=20=EC=98=A4=EB=A5=98=20&=20ESLint?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/module/CompactDataGrid.tsx | 4 +- apps/st/src/components/module/DataGrid.tsx | 15 +- apps/st/src/components/module/TabDataGrid.tsx | 166 ++-- .../input/SeweragePenetrationRateInp.tsx | 4 +- .../statistics/review/PumpingStationRvw.tsx | 2 +- .../review/SewagePipeMaintainRvw.tsx | 32 +- .../statistics/review/SewagePipeRvw.tsx | 2 +- .../review/SewageReservoirPumpRvw.tsx | 2 +- .../review/SeweragePenetrationRateRvw.tsx | 24 +- .../statistics/review/StatsInputStatusRvw.tsx | 14 +- apps/st/src/components/type/DataGrid.type.ts | 1 + .../column/review/PumpingStationRvwColumn.ts | 2 +- .../review/SewagePipeMaintainRvwColumn.ts | 498 ++++++------ .../util/column/review/SewagePipeRvwColumn.ts | 750 +++++++++--------- .../review/SewageReservoirPumpRvwColumn.ts | 18 +- .../SeweragePenetrationRateRvwColumn.ts | 40 +- .../src/components/util/grid/footerSummary.ts | 16 +- .../api/statistics-management-service.ts | 194 ++--- .../statistics-input-review-interface.ts | 2 +- 19 files changed, 897 insertions(+), 889 deletions(-) diff --git a/apps/st/src/components/module/CompactDataGrid.tsx b/apps/st/src/components/module/CompactDataGrid.tsx index 4377eb762..4e193c532 100644 --- a/apps/st/src/components/module/CompactDataGrid.tsx +++ b/apps/st/src/components/module/CompactDataGrid.tsx @@ -62,7 +62,7 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string }; } -const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, rowData, setRowData }: DataGridProps) => { +const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, rowData }: DataGridProps) => { const { columns, columnLayout } = columnInfo; const gridRef = useRef(null); @@ -255,7 +255,7 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, autoGenerateField={true} layout={columnLayout} rows={rowData} - onInitialized={(grid) => { + onInitialized={() => { const dp = gridRef.current?.dataProvider as { setFields?: (fields: unknown[]) => void } | undefined; if (dp?.setFields) { dp.setFields(buildFieldsFromColumns(columns)); diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index e9d4a9620..8b614f4b7 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -112,12 +112,14 @@ function createOnSortingHandler( ), ]; // mergeCells: true인 컬럼 찾기 (시군구 등) - 그룹 필드로 사용 - // 시군구를 우선으로 찾고, 없으면 첫 번째 mergeCells 컬럼 사용 + // 시군구(sggNm) 우선, 없으면 행정구역명(admdstNm), 없으면 첫 번째 mergeCells 컬럼 const mergeCellCols = columns.filter( (c) => c.mergeCells === true && c.fieldName, ); const mergeCellCol = - mergeCellCols.find((c) => c.fieldName === "gugun") || mergeCellCols[0]; + mergeCellCols.find((c) => c.fieldName === "sggNm") || + mergeCellCols.find((c) => c.fieldName === "admdstNm") || + mergeCellCols[0]; const groupField = mergeCellCol?.fieldName || null; const orderByField = "displayYear"; // 고정값 또는 설정 가능하게 할 수 있음 const hasMergeCells = mergeCellCols.length > 0; @@ -148,7 +150,8 @@ function createOnSortingHandler( const col = columns.find( (c) => c.fieldName === sortField || c.name === sortField, ); - if (!col || col.sortdisable) return true; + // sortdisable 컬럼은 정렬 불가(클릭 무시) + if (!col || col.sortdisable) return false; // mergeCells: true인 컬럼이 없으면 RealGrid 기본 정렬 사용 if (!hasMergeCells || !groupField) return true; @@ -173,6 +176,7 @@ function createOnSortingHandler( if (orig && setRows) { setRows(orig); setRowData(orig); + (g as { refresh?: () => void }).refresh?.(); } sortDirRef.current[sortField] = "none"; return false; @@ -191,11 +195,13 @@ function createOnSortingHandler( ); setRows?.(sorted); setRowData(sorted); + // 데이터 변경 후 merge 재적용 + (g as { refresh?: () => void }).refresh?.(); return false; }; } -/** sortPreserveGroupOrder: 그룹 간에는 sortField로 정렬, 그룹 내에서는 원본 순서 유지. */ +/** sortPreserveGroupOrder: 그룹 간에는 sortField로 정렬, 그룹 내에서는 원본 순서 유지(구분 포함). */ function sortRowsGroupPreserving( rows: Record[], sortField: string, @@ -852,6 +858,7 @@ const DataGrid = ({ ...(col.mergeCells && { mergeRule: { criteria: "value" as const }, }), + sortable: !col.sortdisable, }; if (col.footer) { diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index 71d94fad3..96aec2a94 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -57,96 +57,96 @@ function toNum(v: unknown): number { } /** 헤더에 정렬 화살표 표시 (CSS 방식) */ -function updateHeaderSortIndicator( - gridView: { - columnByIndex?: (i: number) => { fieldName?: string; name?: string } | undefined; - columnByName?: (name: string) => { fieldName?: string; name?: string } | undefined; - getColumn?: (i: number) => { fieldName?: string; name?: string } | undefined; - getElement?: () => HTMLElement | null; - container?: HTMLElement; - }, - columns: { fieldName?: string; name: string }[], - sortDir: Record, - gridElement?: HTMLElement | null -) { - try { - // 그리드 DOM 요소 찾기 - let gridEl: HTMLElement | null = null; - if (gridElement) { - gridEl = gridElement; - } else if (gridView.getElement) { - gridEl = gridView.getElement(); - } else if (gridView.container) { - gridEl = gridView.container; - } else { - // 폴백: 모든 그리드 중에서 찾기 - const allGrids = document.querySelectorAll('.rg-grid, [data-grid-id]'); - if (allGrids.length > 0) { - // 활성 탭의 그리드를 찾기 위해 TabsContent 내부의 그리드 찾기 - const activeTabContent = document.querySelector('[data-state="active"]')?.closest('[role="tabpanel"]'); - if (activeTabContent) { - gridEl = activeTabContent.querySelector('.rg-grid, [data-grid-id]') as HTMLElement; - } - if (!gridEl && allGrids.length === 1) { - gridEl = allGrids[0] as HTMLElement; - } - } - } +// function updateHeaderSortIndicator( +// gridView: { +// columnByIndex?: (i: number) => { fieldName?: string; name?: string } | undefined; +// columnByName?: (name: string) => { fieldName?: string; name?: string } | undefined; +// getColumn?: (i: number) => { fieldName?: string; name?: string } | undefined; +// getElement?: () => HTMLElement | null; +// container?: HTMLElement; +// }, +// columns: { fieldName?: string; name: string }[], +// sortDir: Record, +// gridElement?: HTMLElement | null +// ) { +// try { +// // 그리드 DOM 요소 찾기 +// let gridEl: HTMLElement | null = null; +// if (gridElement) { +// gridEl = gridElement; +// } else if (gridView.getElement) { +// gridEl = gridView.getElement(); +// } else if (gridView.container) { +// gridEl = gridView.container; +// } else { +// // 폴백: 모든 그리드 중에서 찾기 +// const allGrids = document.querySelectorAll('.rg-grid, [data-grid-id]'); +// if (allGrids.length > 0) { +// // 활성 탭의 그리드를 찾기 위해 TabsContent 내부의 그리드 찾기 +// const activeTabContent = document.querySelector('[data-state="active"]')?.closest('[role="tabpanel"]'); +// if (activeTabContent) { +// gridEl = activeTabContent.querySelector('.rg-grid, [data-grid-id]') as HTMLElement; +// } +// if (!gridEl && allGrids.length === 1) { +// gridEl = allGrids[0] as HTMLElement; +// } +// } +// } - if (!gridEl) return; +// if (!gridEl) return; - columns.forEach((col) => { - if (!col.fieldName && !col.name) return; +// columns.forEach((col) => { +// if (!col.fieldName && !col.name) return; - const fieldName = col.fieldName || col.name; - const direction = sortDir[fieldName] || sortDir[col.name] || "none"; - if (direction === "none") return; +// const fieldName = col.fieldName || col.name; +// const direction = sortDir[fieldName] || sortDir[col.name] || "none"; +// if (direction === "none") return; - // 헤더 셀 찾기 (fieldName 또는 name으로) - const headerCells = gridEl.querySelectorAll('.rg-header-cell, [class*="header"], .rg-header'); - let targetCell: HTMLElement | null = null; +// // 헤더 셀 찾기 (fieldName 또는 name으로) +// const headerCells = gridEl.querySelectorAll('.rg-header-cell, [class*="header"], .rg-header'); +// let targetCell: HTMLElement | null = null; - headerCells.forEach((cell) => { - const el = cell as HTMLElement; - const text = el.textContent?.trim(); - const colName = col.name; - const colFieldName = col.fieldName; - // 헤더 텍스트와 컬럼 이름이 일치하는지 확인 - if (text && ( - text === colName || - text === colFieldName || - text.includes(colName) || - (colFieldName && text.includes(colFieldName)) - )) { - targetCell = el; - } - }); +// headerCells.forEach((cell) => { +// const el = cell as HTMLElement; +// const text = el.textContent?.trim(); +// const colName = col.name; +// const colFieldName = col.fieldName; +// // 헤더 텍스트와 컬럼 이름이 일치하는지 확인 +// if (text && ( +// text === colName || +// text === colFieldName || +// text.includes(colName) || +// (colFieldName && text.includes(colFieldName)) +// )) { +// targetCell = el; +// } +// }); - if (!targetCell) return; +// if (!targetCell) return; - const headerCell = targetCell as HTMLElement; +// const headerCell = targetCell as HTMLElement; - // 기존 화살표 제거 - const existingIndicator = headerCell.querySelector(".rg-sort-indicator") as HTMLElement | null; - if (existingIndicator) { - existingIndicator.remove(); - } +// // 기존 화살표 제거 +// const existingIndicator = headerCell.querySelector(".rg-sort-indicator") as HTMLElement | null; +// if (existingIndicator) { +// existingIndicator.remove(); +// } - // 화살표 추가 - const indicator = document.createElement("span"); - indicator.className = "rg-sort-indicator"; - indicator.textContent = direction === "asc" ? "▲" : "▼"; - indicator.style.marginLeft = "4px"; - indicator.style.fontSize = "10px"; - indicator.style.color = "#666"; - indicator.style.display = "inline-block"; - indicator.style.verticalAlign = "middle"; - headerCell.appendChild(indicator); - }); - } catch (e) { - console.warn("updateHeaderSortIndicator error:", e); - } -} +// // 화살표 추가 +// const indicator = document.createElement("span"); +// indicator.className = "rg-sort-indicator"; +// indicator.textContent = direction === "asc" ? "▲" : "▼"; +// indicator.style.marginLeft = "4px"; +// indicator.style.fontSize = "10px"; +// indicator.style.color = "#666"; +// indicator.style.display = "inline-block"; +// indicator.style.verticalAlign = "middle"; +// headerCell.appendChild(indicator); +// }); +// } catch (e) { +// console.warn("updateHeaderSortIndicator error:", e); +// } +// } /** onSorting: mergeCells: true인 컬럼이 있으면 그룹 내 순서 유지 정렬, 없으면 RealGrid 기본 정렬 */ function createOnSortingHandler( @@ -154,13 +154,13 @@ function createOnSortingHandler( setRowData: React.Dispatch[]>>, sortDirRef: { current: Record }, originalRowsRef: { current: Record[]> }, - findGridElement?: () => HTMLElement | null + _findGridElement?: () => HTMLElement | null ) { const fieldNames = [...new Set(columns.map((c) => c.fieldName).filter((x): x is string => typeof x === "string"))]; // mergeCells: true인 컬럼 찾기 (시군구 등) - 그룹 필드로 사용 // 시군구를 우선으로 찾고, 없으면 첫 번째 mergeCells 컬럼 사용 const mergeCellCols = columns.filter((c) => c.mergeCells === true && c.fieldName); - const mergeCellCol = mergeCellCols.find((c) => c.fieldName === 'gugun') || mergeCellCols[0]; + const mergeCellCol = mergeCellCols.find((c) => c.fieldName === 'sggNm') || mergeCellCols[0]; const groupField = mergeCellCol?.fieldName || null; const orderByField = 'displayYear'; // 고정값 또는 설정 가능하게 할 수 있음 const hasMergeCells = mergeCellCols.length > 0; diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 93353d927..13249fdf8 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -63,8 +63,8 @@ const SeweragePenetrationRateInp = () => { const [footerFromApi, setFooterFromApi] = useState<{ totalPopl?: number; poplSm?: number; frgnrPoplSm?: number; refArea?: number } | null>(null); const getGridDataRef = useRef<(() => Record[]) | null>(null); - const footerFieldNames = ['totalPopl', 'poplSm', 'frgnrPoplSm', 'refArea'] as const; - const footerWithApiDefault = (fieldName: (typeof footerFieldNames)[number], numberFormat: string) => ({ + const _footerFieldNames = ['totalPopl', 'poplSm', 'frgnrPoplSm', 'refArea'] as const; + const footerWithApiDefault = (fieldName: (typeof _footerFieldNames)[number], numberFormat: string) => ({ expression: 'sum' as const, numberFormat, valueCallback: (grid: Parameters[0], column: Parameters[1]) => diff --git a/apps/st/src/components/statistics/review/PumpingStationRvw.tsx b/apps/st/src/components/statistics/review/PumpingStationRvw.tsx index b1a1d6f9d..3fc34a034 100644 --- a/apps/st/src/components/statistics/review/PumpingStationRvw.tsx +++ b/apps/st/src/components/statistics/review/PumpingStationRvw.tsx @@ -16,7 +16,7 @@ import { toGridRows } from "@/components/util/grid/gridFunction"; // 하수도 통계 검토 05.중계펌프장 const PumpingStationRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); - const [rowData, setRowData] = useState([]); + const [rowData, setRowData] = useState[]>([]); const [isLoading, setIsLoading] = useState(false); const columnInfo = useMemo(() => { diff --git a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx index a841bc273..d8cdc7d0e 100644 --- a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx @@ -55,28 +55,28 @@ const SewagePipeMaintainRvw = () => { setIsLoading(true); const searchParam: SewerPipeMtcRequestParams = { - statsYr: values.year, - sewerBzmnCd: values.bsnmCd, - ctpvNm: values.sido, - sggNm: values.gugun === 'ALL' ? '' : values.gugun - }; + statsYr: values.year, + sewerBzmnCd: values.bsnmCd, + ctpvNm: values.sido, + sggNm: values.gugun === 'ALL' ? '' : values.gugun + }; searchParamRef.current = searchParam; const response = await statisticsManagementApi.getSewerPipeMtcList(searchParam); if (response?.status === 'success' && response.data) { const newRowDataMap = new Map[]>(); - - const listData = Array.isArray(response.data) - ? response.data + + const listData = Array.isArray(response.data) + ? response.data : (response.data.mtcStatusList || []); - + newRowDataMap.set('ST00030001', toGridRows(listData as object[])); setRowDataMap(newRowDataMap); } else { alert(response?.message || "데이터가 없습니다."); } - } catch (error) { + } catch { alert('데이터 조회 중 오류가 발생했습니다.'); } finally { setIsLoading(false); @@ -100,18 +100,18 @@ const SewagePipeMaintainRvw = () => { if (tabKey === 'ST00030001') { response = await statisticsManagementApi.getSewerPipeMtcList(searchParamRef.current); - }else if (tabKey === 'ST00030002') { + } else if (tabKey === 'ST00030002') { // response = await statisticsManagementApi.getSewerPipeDrdgList(searchParamRef.current); } if (response?.status === 'success' && response.data) { setRowDataMap(prev => { const next = new Map(prev); - - const listData = Array.isArray(response.data) - ? response.data + + const listData = Array.isArray(response.data) + ? response.data : (response.data.mtcStatusList || []); - + next.set(tabKey, toGridRows(listData as object[])); return next; }); @@ -128,7 +128,7 @@ const SewagePipeMaintainRvw = () => { return (
- +
diff --git a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx index 157fd87fd..fab4d2cbc 100644 --- a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx @@ -71,7 +71,7 @@ const SewagePipeRvw = () => { } else { alert(response?.message || "데이터가 없습니다."); } - } catch (error) { + } catch { alert('데이터 조회 중 오류가 발생했습니다.'); } finally { setIsLoading(false); diff --git a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx index 346edb0cd..13303f6c1 100644 --- a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx +++ b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx @@ -75,7 +75,7 @@ const SewageReservoirPumpRvw = () => { } else { alert(response?.message || "데이터가 없습니다."); } - } catch (error) { + } catch { alert('데이터 조회 중 오류가 발생했습니다.'); } finally { setIsLoading(false); diff --git a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx index 65f3cea58..18a0443cc 100644 --- a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx +++ b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx @@ -5,7 +5,7 @@ import SaveButton from "@/components/button/SaveButton"; import ApproveButton from "@/components/button/ApproveButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; import ReturnButton from "@/components/button/ReturnButton"; -import { LoadingWithAnimation } from "@common/components"; +import { LoadingWithAnimation } from "@common/components"; import { useAlertStore } from "@/shared/lib"; import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn"; @@ -17,8 +17,8 @@ import { toGridRows } from "@/components/util/grid/gridFunction"; // 하수도 통계 검토 01.하수도 보급률 const SeweragePenetrationRateRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); - const [rowData, setRowData] = useState([]); - const [isLoading, setIsLoading] = useState(false); + const [rowData, setRowData] = useState[]>([]); + const [isLoading, setIsLoading] = useState(false); const columnInfo = useMemo(() => { const { columns, columnLayout, fixedColumns, rowStartNum } = SeweragePenetrationRateInfoList; @@ -49,17 +49,17 @@ const SeweragePenetrationRateRvw = () => { setIsLoading(true); const response = await statisticsManagementApi.getSewerageSupplyRateReviewList({ - ctpvNm: values.sido, - sggNm: values.gugun === 'ALL' ? '' : values.gugun, - sewerBzmnCd: values.bsnmCd, - statsYr: values.year, + ctpvNm: values.sido, + sggNm: values.gugun === 'ALL' ? '' : values.gugun, + sewerBzmnCd: values.bsnmCd, + statsYr: values.year, }); if (response.status === 'success' && response.data) { const rawList = response.data.sewerageSupplyRateList; - const convertedData: SewerageSupplyRateReviewItem[] = rawList.map((item) => ({ + const convertedData: SewerageSupplyRateReviewItem[] = rawList.map((item) => ({ ...item, - + spcCtpnRgnCn: !item.spcCtpnRgnCn ? '해당없음' : item.spcCtpnRgnCn, })); @@ -72,7 +72,7 @@ const SeweragePenetrationRateRvw = () => { console.error("조회 중 에러 발생:", error); alert('데이터 조회 중 오류가 발생했습니다.'); } finally { - setIsLoading(false); + setIsLoading(false); } }; @@ -82,9 +82,9 @@ const SeweragePenetrationRateRvw = () => { const returnFunc = () => console.log('returnFunc'); return ( -
+
- +
diff --git a/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx b/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx index 2c11f7abc..8fdd74d4f 100644 --- a/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx +++ b/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx @@ -11,12 +11,13 @@ import { useAlertStore } from "@/shared/lib"; import { StatsInputStatusInfoList } from "@/components/util/column/review/StatsInputStatusRvwColumn"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import type { StatsInputStatusReviewItem } from "@/entities/statistics/model/statistics-input-review-interface"; -import { toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import type { SearchFormValues } from "@/components/type/SearchForm.type"; // 하수도 통계 검토 00. 통계 입력 현황 const StatsInputStatusRvw = () => { const { setMessage: alert } = useAlertStore((state) => state); - const [rowData, setRowData] = useState([]); + const [rowData, setRowData] = useState[]>([]); const [isLoading, setIsLoading] = useState(false); const columnInfo = useMemo(() => { @@ -34,7 +35,7 @@ const StatsInputStatusRvw = () => { setRowData([]); }; - const handleSearch = async (values: any) => { + const handleSearch = async (values: SearchFormValues) => { if (!values.sido || values.sido === 'ALL') { alert('시도를 선택해주세요.'); return; @@ -46,14 +47,13 @@ const StatsInputStatusRvw = () => { const response = await statisticsManagementApi.getStatsInputStatusList({ sido: values.sido, gugun: values.gugun === 'ALL' ? '' : values.gugun, - yr: values.year || values.yr, + yr: values.year, bsnmCd: values.bsnmCd }); if (response.status === 'success' && response.data) { - const rawList = response.data.sewerageSupplyRateList || response.data; - - const convertedData: StatsInputStatusReviewItem[] = rawList.map((item: any) => ({ + const rawList = response.data.sewerageSupplyRateList ?? []; + const convertedData: StatsInputStatusReviewItem[] = rawList.map((item: StatsInputStatusReviewItem) => ({ ...item, status: item.status || '미확인', })); diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index 5635b7f8a..69d82b6d2 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -13,6 +13,7 @@ export type RealGridColumnInfo = { dataFormat?: string; styleName?: string; mergeRule?: { criteria: "value" }; + sortable?: boolean; footer?: | { text?: string; diff --git a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts index f031fed5e..b42f1637a 100644 --- a/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts +++ b/apps/st/src/components/util/column/review/PumpingStationRvwColumn.ts @@ -1,6 +1,6 @@ import { ValueType } from "realgrid"; import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; -import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; +import type { GridView } from "@/components/type/DataGrid.type"; export const rawPumpingStationInfoList = { columns: [ diff --git a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts index a9433e643..cbc536253 100644 --- a/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeMaintainRvwColumn.ts @@ -1,277 +1,277 @@ import { ValueType } from "realgrid"; -import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; -import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; +import { footerIntArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; +import type { GridView } from "@/components/type/DataGrid.type"; // 하수도 통계 관리 03. 하수관로 유지 관리 const rawMaintainList = [ - { - tabKey: 'ST00030001', - tabLabel: '개보수', - rowStartNum: 4, - fixedColumns: 2, - columns: [ - { - name: 'ctpvNm', - fieldName: 'ctpvNm', - header: { text: '시도' }, - mergeCells: true, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + { + tabKey: 'ST00030001', + tabLabel: '개보수', + rowStartNum: 4, + 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 rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; } - 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 }, - { - name: 'displayYear', - fieldName: 'displayYear', - header: { text: '구분' }, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + ] + }, + { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : ''; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : ''; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : ''; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : ''; } - } - ] - }, - { name: 'mendngWhol', fieldName: 'mendngWhol', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'cbsysMendngPrlg', fieldName: 'cbsysMendngPrlg', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'sprsysExlnPrlg', fieldName: 'sprsysExlnPrlg', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'sprsysDrtwPrlg', fieldName: 'sprsysDrtwPrlg', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'partMendngWhol', fieldName: 'partMendngWhol', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'cbsysPartMendngPrlg', fieldName: 'cbsysPartMendngPrlg', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'sprsysExlnMendngPrlg', fieldName: 'sprsysExlnMendngPrlg', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'sprsysDrtwMendngPrlg', fieldName: 'sprsysDrtwMendngPrlg', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'cbsysMnhlCnt', fieldName: 'cbsysMnhlCnt', header: { text: '합류식\n맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'sprsysDrtwMnhlCnt', fieldName: 'sprsysDrtwMnhlCnt', header: { text: '오수맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'sprsysExlnMnhlCnt', fieldName: 'sprsysExlnMnhlCnt', header: { text: '우수맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'drtwRcvprpCnt', fieldName: 'drtwRcvprpCnt', header: { text: '오수받이' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'exlnRcvprpCnt', fieldName: 'exlnRcvprpCnt', header: { text: '빗물받이' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'stofcbrCnt', fieldName: 'stofcbrCnt', header: { text: '토실' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'otflCnt', fieldName: 'otflCnt', header: { text: '토구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'cctvExmnPrlg', fieldName: 'cctvExmnPrlg', header: { text: '수밀검사·\nCCTV조사량\n(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - ], - columnLayout: [ - { - name: 'addrGroup', - header: { text: '행정구역' }, - items: [ - { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }, { colspan: 2 }] }, - 'sggNm', - 'admdstNm', - ] - }, - 'displayYear', - { - name: 'mtcPipeGroup', header: { text: '개·보수관로' }, - items: [ - { - name: 'totalRepairGroup', header: { text: '전체보수(m)' }, - items: ['mendngWhol', 'cbsysMendngPrlg', { name: 'sepTotalGroup', header: { text: '분류식' }, items: ['sprsysExlnPrlg', 'sprsysDrtwPrlg'] }] - }, - { - name: 'partRepairGroup', header: { text: '부분보수(개소)' }, - items: ['partMendngWhol', 'cbsysPartMendngPrlg', { name: 'sepPartGroup', header: { text: '분류식' }, items: ['sprsysExlnMendngPrlg', 'sprsysDrtwMendngPrlg'] }] - } - ] - }, - 'cbsysMnhlCnt', - { name: 'sepMnhleGroup', header: { text: '분류식' }, items: ['sprsysDrtwMnhlCnt', 'sprsysExlnMnhlCnt'] }, - 'drtwRcvprpCnt', 'exlnRcvprpCnt', - { name: 'soilGroup', header: { text: '토실·토구(개소)' }, items: ['stofcbrCnt', 'otflCnt'] }, - 'cctvExmnPrlg', 'rmrk' - ] - }, - { - tabKey: 'ST00030002', - tabLabel: '관로준설', - rowStartNum: 4, - fixedColumns: 2, - columns: [ - /* 1. 행정 및 기본 정보 */ - { - name: 'sido', - fieldName: 'sido', - header: { text: '시도' }, - mergeCells: true, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue) uniqueValues.add(String(divisionValue)); + ] + }, + { name: 'mendngWhol', fieldName: 'mendngWhol', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'cbsysMendngPrlg', fieldName: 'cbsysMendngPrlg', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'sprsysExlnPrlg', fieldName: 'sprsysExlnPrlg', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'sprsysDrtwPrlg', fieldName: 'sprsysDrtwPrlg', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'partMendngWhol', fieldName: 'partMendngWhol', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'cbsysPartMendngPrlg', fieldName: 'cbsysPartMendngPrlg', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysExlnMendngPrlg', fieldName: 'sprsysExlnMendngPrlg', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysDrtwMendngPrlg', fieldName: 'sprsysDrtwMendngPrlg', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'cbsysMnhlCnt', fieldName: 'cbsysMnhlCnt', header: { text: '합류식\n맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysDrtwMnhlCnt', fieldName: 'sprsysDrtwMnhlCnt', header: { text: '오수맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysExlnMnhlCnt', fieldName: 'sprsysExlnMnhlCnt', header: { text: '우수맨홀' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drtwRcvprpCnt', fieldName: 'drtwRcvprpCnt', header: { text: '오수받이' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'exlnRcvprpCnt', fieldName: 'exlnRcvprpCnt', header: { text: '빗물받이' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'stofcbrCnt', fieldName: 'stofcbrCnt', header: { text: '토실' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'otflCnt', fieldName: 'otflCnt', header: { text: '토구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'cctvExmnPrlg', fieldName: 'cctvExmnPrlg', header: { text: '수밀검사·\nCCTV조사량\n(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 2 }, { colspan: 2 }] }, + 'sggNm', + 'admdstNm', + ] + }, + 'displayYear', + { + name: 'mtcPipeGroup', header: { text: '개·보수관로' }, + items: [ + { + name: 'totalRepairGroup', header: { text: '전체보수(m)' }, + items: ['mendngWhol', 'cbsysMendngPrlg', { name: 'sepTotalGroup', header: { text: '분류식' }, items: ['sprsysExlnPrlg', 'sprsysDrtwPrlg'] }] + }, + { + name: 'partRepairGroup', header: { text: '부분보수(개소)' }, + items: ['partMendngWhol', 'cbsysPartMendngPrlg', { name: 'sepPartGroup', header: { text: '분류식' }, items: ['sprsysExlnMendngPrlg', 'sprsysDrtwMendngPrlg'] }] + } + ] + }, + 'cbsysMnhlCnt', + { name: 'sepMnhleGroup', header: { text: '분류식' }, items: ['sprsysDrtwMnhlCnt', 'sprsysExlnMnhlCnt'] }, + 'drtwRcvprpCnt', 'exlnRcvprpCnt', + { name: 'soilGroup', header: { text: '토실·토구(개소)' }, items: ['stofcbrCnt', 'otflCnt'] }, + 'cctvExmnPrlg', 'rmrk' + ] + }, + { + tabKey: 'ST00030002', + tabLabel: '관로준설', + rowStartNum: 4, + fixedColumns: 2, + columns: [ + /* 1. 행정 및 기본 정보 */ + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue) uniqueValues.add(String(divisionValue)); + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; } - } - ] - }, - { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, - { - name: 'displayYear', - fieldName: 'displayYear', - header: { text: '구분' }, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView) => { - const uniqueValues = new Set(); - for (let i = 0; i < grid.getDataSource().getRowCount(); i++) { - const val = grid.getDataSource().getValue(i, 'displayYear'); - if (val) uniqueValues.add(String(val)); + ] + }, + { name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const uniqueValues = new Set(); + for (let i = 0; i < grid.getDataSource().getRowCount(); i++) { + const val = grid.getDataSource().getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > 0 ? divisionValues[0] + ' 합계' : ''; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > 0 ? divisionValues[0] + ' 합계' : ''; - } - }, - { - valueCallback: (grid: GridView) => { - const uniqueValues = new Set(); - for (let i = 0; i < grid.getDataSource().getRowCount(); i++) { - const val = grid.getDataSource().getValue(i, 'displayYear'); - if (val) uniqueValues.add(String(val)); + }, + { + valueCallback: (grid: GridView) => { + const uniqueValues = new Set(); + for (let i = 0; i < grid.getDataSource().getRowCount(); i++) { + const val = grid.getDataSource().getValue(i, 'displayYear'); + if (val) uniqueValues.add(String(val)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > 1 ? divisionValues[1] + ' 합계' : ''; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > 1 ? divisionValues[1] + ' 합계' : ''; } - } - ] - }, - - /* 2. 시설 기본 정보 */ - { name: 'piplFcltyExtn', fieldName: 'piplFcltyExtn', header: { text: '하수관로\n총시설연장\n(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + ] + }, - /* 3. 준설연장 및 개소수 */ - { name: 'drdgPipeSm', fieldName: 'drdgPipeSm', header: { text: '합계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'drdgCmbnPipe', fieldName: 'drdgCmbnPipe', header: { text: '합류식' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'drdgRainwtrPipe', fieldName: 'drdgRainwtrPipe', header: { text: '우수' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'drdgPltwtrPipe', fieldName: 'drdgPltwtrPipe', header: { text: '오수' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'mnhleSite', fieldName: 'mnhleSite', header: { text: '맨홀\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'pltwtrRcvrSite', fieldName: 'pltwtrRcvrSite', header: { text: '오·우수받이\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'soilspllSoilholeSite', fieldName: 'soilspllSoilholeSite', header: { text: '토실·토구\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + /* 2. 시설 기본 정보 */ + { name: 'piplFcltyExtn', fieldName: 'piplFcltyExtn', header: { text: '하수관로\n총시설연장\n(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 4. 준설 계획 및 율 */ - { name: 'drdgTrgetExtn', fieldName: 'drdgTrgetExtn', header: { text: '준설 계획\n연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'drdgTrgetRt', fieldName: 'drdgTrgetRt', header: { text: '계획대비\n준설율\n(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'piplFcltyRt', fieldName: 'piplFcltyRt', header: { text: '준설계획\n연장대비\n준설율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 3. 준설연장 및 개소수 */ + { name: 'drdgPipeSm', fieldName: 'drdgPipeSm', header: { text: '합계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgCmbnPipe', fieldName: 'drdgCmbnPipe', header: { text: '합류식' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgRainwtrPipe', fieldName: 'drdgRainwtrPipe', header: { text: '우수' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgPltwtrPipe', fieldName: 'drdgPltwtrPipe', header: { text: '오수' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'mnhleSite', fieldName: 'mnhleSite', header: { text: '맨홀\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'pltwtrRcvrSite', fieldName: 'pltwtrRcvrSite', header: { text: '오·우수받이\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'soilspllSoilholeSite', fieldName: 'soilspllSoilholeSite', header: { text: '토실·토구\n(개소)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 5. 준설실적 */ - { name: 'drdgAcmsltAmount', fieldName: 'drdgAcmsltAmount', header: { text: '금액\n(백만원)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'drdgAcmsltQy', fieldName: 'drdgAcmsltQy', header: { text: '준설량\n(㎥)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + /* 4. 준설 계획 및 율 */ + { name: 'drdgTrgetExtn', fieldName: 'drdgTrgetExtn', header: { text: '준설 계획\n연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'drdgTrgetRt', fieldName: 'drdgTrgetRt', header: { text: '계획대비\n준설율\n(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'piplFcltyRt', fieldName: 'piplFcltyRt', header: { text: '준설계획\n연장대비\n준설율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - /* 6. 준설토사처리 */ - { name: 'drdgAcmsltUntpc', fieldName: 'drdgAcmsltUntpc', header: { text: '준설토\n처리비\n(백만원)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'drdgEtndSm', fieldName: 'drdgEtndSm', header: { text: '처리량 계\n(톤)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'rclm', fieldName: 'rclm', header: { text: '매립' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'oceanDpsl', fieldName: 'oceanDpsl', header: { text: '해양투기' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'ruse', fieldName: 'ruse', header: { text: '재활용' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'etc', fieldName: 'etc', header: { text: '기타' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 5. 준설실적 */ + { name: 'drdgAcmsltAmount', fieldName: 'drdgAcmsltAmount', header: { text: '금액\n(백만원)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drdgAcmsltQy', fieldName: 'drdgAcmsltQy', header: { text: '준설량\n(㎥)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - /* 7. 기타 및 의견 */ - { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, - ], - columnLayout: [ - { name: 'addrGroup', header: { text: '행정구역' }, items: [{ column: 'sido', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, 'gugun'] }, - 'displayYear', 'piplFcltyExtn', - { - name: 'drdgGroup', header: { text: '준설연장 및 개소수' }, - items: [ - { - name: 'pipeLenGroup', header: { text: '관로(m)' }, - items: ['drdgPipeSm', 'drdgCmbnPipe', { name: 'sepRepairGroup', header: { text: '분류식' }, items: ['drdgRainwtrPipe', 'drdgPltwtrPipe'] }] - }, - 'mnhleSite', 'pltwtrRcvrSite', 'soilspllSoilholeSite' - ] - }, - 'drdgTrgetExtn', 'drdgTrgetRt', 'piplFcltyRt', - { name: 'acmsltGroup', header: { text: '준설실적' }, items: ['drdgAcmsltAmount', 'drdgAcmsltQy'] }, - { - name: 'processGroup', header: { text: '준설토사처리' }, - items: ['drdgAcmsltUntpc', 'drdgEtndSm', 'rclm', 'oceanDpsl', 'ruse', 'etc'] - }, - 'rmk' - ] -} + /* 6. 준설토사처리 */ + { name: 'drdgAcmsltUntpc', fieldName: 'drdgAcmsltUntpc', header: { text: '준설토\n처리비\n(백만원)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'drdgEtndSm', fieldName: 'drdgEtndSm', header: { text: '처리량 계\n(톤)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rclm', fieldName: 'rclm', header: { text: '매립' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'oceanDpsl', fieldName: 'oceanDpsl', header: { text: '해양투기' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'ruse', fieldName: 'ruse', header: { text: '재활용' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'etc', fieldName: 'etc', header: { text: '기타' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + + /* 7. 기타 및 의견 */ + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ + { name: 'addrGroup', header: { text: '행정구역' }, items: [{ column: 'sido', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, 'gugun'] }, + 'displayYear', 'piplFcltyExtn', + { + name: 'drdgGroup', header: { text: '준설연장 및 개소수' }, + items: [ + { + name: 'pipeLenGroup', header: { text: '관로(m)' }, + items: ['drdgPipeSm', 'drdgCmbnPipe', { name: 'sepRepairGroup', header: { text: '분류식' }, items: ['drdgRainwtrPipe', 'drdgPltwtrPipe'] }] + }, + 'mnhleSite', 'pltwtrRcvrSite', 'soilspllSoilholeSite' + ] + }, + 'drdgTrgetExtn', 'drdgTrgetRt', 'piplFcltyRt', + { name: 'acmsltGroup', header: { text: '준설실적' }, items: ['drdgAcmsltAmount', 'drdgAcmsltQy'] }, + { + name: 'processGroup', header: { text: '준설토사처리' }, + items: ['drdgAcmsltUntpc', 'drdgEtndSm', 'rclm', 'oceanDpsl', 'ruse', 'etc'] + }, + 'rmk' + ] + } ]; export const SewagePipeMaintainColumnInfoList = rawMaintainList.map(tab => ({ diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts index db5034cb3..cc86bd201 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -1,22 +1,22 @@ import { ValueType } from "realgrid"; const baseYear = new Date().getFullYear(); import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; -import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; +import type { GridView } from "@/components/type/DataGrid.type"; // 하수도 통계 관리 02. 하수관로 const rawList = [ - { + { tabKey: 'ST00020001', tabLabel: '지역별 시설현황', rowStartNum: 4, fixedColumns: 2, columns: [ { - name: 'ctpvNm', - fieldName: 'ctpvNm', - header: { text: '시도' }, - mergeCells: true, - sortdisable: true, + name: 'ctpvNm', + fieldName: 'ctpvNm', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, footer: [ { valueCallback: (grid: GridView) => { @@ -170,392 +170,392 @@ const rawList = [ 'pltwtrRcvrCnt', 'rainwtrRcvrCnt', 'rainwtrChmbrCnt', 'rainwtrOutltCnt', 'swrgRegstrCmptExtn', 'swrgRegstrCmptRt', 'rmrk' ] }, - { - tabKey: 'ST00020002', - tabLabel: '시설별 설치현황', - rowStartNum: 4, - fixedColumns: 3, - columns: [ - { - name: 'ctpvNm', - fieldName: 'ctpvNm', - header: { text: '시도' }, - mergeCells: true, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + { + tabKey: 'ST00020002', + tabLabel: '시설별 설치현황', + rowStartNum: 4, + fixedColumns: 3, + columns: [ + { + name: 'ctpvNm', + fieldName: 'ctpvNm', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + ' 합계' : '합계'; } - } - ] - }, - { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, - { name: 'swrmtFcltyNm', fieldName: 'swrmtFcltyNm', header: { text: '하수처리시설 명칭' }, mergeCells: true }, - { - name: 'displayYear', - fieldName: 'displayYear', - header: { text: '구분' }, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const uniqueValues = new Set(); - for (let i = 0; i < dataSource.getRowCount(); i++) { - const val = dataSource.getValue(i, 'displayYear'); - if (val) uniqueValues.add(String(val)); + ] + }, + { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'swrmtFcltyNm', fieldName: 'swrmtFcltyNm', 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(); + 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] + ' 합계' : ''; } - return Array.from(uniqueValues)[divisionIndex] ? Array.from(uniqueValues)[divisionIndex] + ' 합계' : ''; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const uniqueValues = new Set(); - for (let i = 0; i < dataSource.getRowCount(); i++) { - const val = dataSource.getValue(i, 'displayYear'); - if (val) uniqueValues.add(String(val)); + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + 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] + ' 합계' : ''; } - return Array.from(uniqueValues)[divisionIndex] ? Array.from(uniqueValues)[divisionIndex] + ' 합계' : ''; } - } - ] - }, - { name: 'swrmtFcltyCpc', fieldName: 'swrmtFcltyCpc', header: { text: '하수처리\n시설용량\n(㎥/일)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'procZoneArSm', fieldName: 'procZoneArSm', header: { text: '합계' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'cmbnProcZoneAr', fieldName: 'cmbnProcZoneAr', header: { text: '합류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'sprsysProcZoneAr', fieldName: 'sprsysProcZoneAr', header: { text: '분류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, - { name: 'drngEqpSm', fieldName: 'drngEqpSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'cmbnDrngEqp', fieldName: 'cmbnDrngEqp', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'sprsysDrngEqpSm', fieldName: 'sprsysDrngEqpSm', header: { text: '분류식 합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'swrgPipeFcltyExtn', fieldName: 'swrgPipeFcltyExtn', header: { text: '총\n시설연장\n(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'icgPltwtrMnnSm', fieldName: 'icgPltwtrMnnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'icgExtn', fieldName: 'icgExtn', header: { text: '차집' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'pltwtrMnnExtn', fieldName: 'pltwtrMnnExtn', header: { text: '오수간선' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'cmbnRainwtrChmbrCnt', fieldName: 'cmbnRainwtrChmbrCnt', header: { text: '우수토실(합류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'sprsysRainwtrChmbrCnt', fieldName: 'sprsysRainwtrChmbrCnt', header: { text: '우수토실(분류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'sprsysRainwtrOutltCnt', fieldName: 'sprsysRainwtrOutltCnt', header: { text: '우수토구(분류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, - { name: 'brnchlnSm', fieldName: 'brnchlnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'cmbnBrnchlnExtn', fieldName: 'cmbnBrnchlnExtn', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'sprsysExlnBrnchlnExtn', fieldName: 'sprsysExlnBrnchlnExtn', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'sprsysDrtwBrnchlnExtn', fieldName: 'sprsysDrtwBrnchlnExtn', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, - { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, - ], - columnLayout: [ - { - name: 'addrGroup', - header: { text: '행정구역' }, - items: [ - { column: 'ctpvNm', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, - 'sggNm', - 'swrmtFcltyNm', - ] - }, - 'swrmtFcltyCpc', - 'displayYear', - { name: 'areaGroup', header: { text: '처리구역면적(㎢)' }, items: ['procZoneArSm', 'cmbnProcZoneAr', 'sprsysProcZoneAr'] }, - { - name: 'drainGroup', header: { text: '배수설비(개소)' }, - items: ['drngEqpSm', 'cmbnDrngEqp', 'sprsysDrngEqpSm'], - }, - { - name: 'pipeGroup', header: { text: '하수관로' }, - items: [ - 'swrgPipeFcltyExtn', - { name: 'trunkGroup', header: { text: '차집/오수간선(m)' }, items: ['icgPltwtrMnnSm', 'icgExtn', 'pltwtrMnnExtn'] }, - { - name: 'siteGroup', header: { text: '토실/토구(개소)' }, - items: [ - { name: 'cmbnSiteGroup', header: { text: '합류식' }, items: ['cmbnRainwtrChmbrCnt'] }, - { name: 'sepSiteGroup', header: { text: '분류식' }, items: ['sprsysRainwtrChmbrCnt', 'sprsysRainwtrOutltCnt'] } - ] - }, - { - name: 'branchGroup', header: { text: '지선(m)' }, - items: ['brnchlnSm', 'cmbnBrnchlnExtn', { name: 'sepBranchGroup', header: { text: '우수/오수' }, items: ['sprsysExlnBrnchlnExtn', 'sprsysDrtwBrnchlnExtn'] }] - } - ], - }, - 'rmrk' - ] -}, - { - tabKey: 'ST00020003', - tabLabel: '관종별 현황', - rowStartNum: 2, - fixedColumns: 3, - columns: [ - { - name: 'sido', - fieldName: 'sido', - header: { text: '시도' }, - mergeCells: true, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const uniqueValues = new Set(); - for (let i = 0; i < dataSource.getRowCount(); i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue) uniqueValues.add(String(divisionValue)); + ] + }, + { name: 'swrmtFcltyCpc', fieldName: 'swrmtFcltyCpc', header: { text: '하수처리\n시설용량\n(㎥/일)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'procZoneArSm', fieldName: 'procZoneArSm', header: { text: '합계' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'cmbnProcZoneAr', fieldName: 'cmbnProcZoneAr', header: { text: '합류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'sprsysProcZoneAr', fieldName: 'sprsysProcZoneAr', header: { text: '분류식' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.000' }, + { name: 'drngEqpSm', fieldName: 'drngEqpSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'cmbnDrngEqp', fieldName: 'cmbnDrngEqp', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysDrngEqpSm', fieldName: 'sprsysDrngEqpSm', header: { text: '분류식 합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'swrgPipeFcltyExtn', fieldName: 'swrgPipeFcltyExtn', header: { text: '총\n시설연장\n(m)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'icgPltwtrMnnSm', fieldName: 'icgPltwtrMnnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'icgExtn', fieldName: 'icgExtn', header: { text: '차집' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'pltwtrMnnExtn', fieldName: 'pltwtrMnnExtn', header: { text: '오수간선' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnRainwtrChmbrCnt', fieldName: 'cmbnRainwtrChmbrCnt', header: { text: '우수토실(합류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysRainwtrChmbrCnt', fieldName: 'sprsysRainwtrChmbrCnt', header: { text: '우수토실(분류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'sprsysRainwtrOutltCnt', fieldName: 'sprsysRainwtrOutltCnt', header: { text: '우수토구(분류)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0' }, + { name: 'brnchlnSm', fieldName: 'brnchlnSm', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'cmbnBrnchlnExtn', fieldName: 'cmbnBrnchlnExtn', header: { text: '합류식' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'sprsysExlnBrnchlnExtn', fieldName: 'sprsysExlnBrnchlnExtn', header: { text: '우수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'sprsysDrtwBrnchlnExtn', fieldName: 'sprsysDrtwBrnchlnExtn', header: { text: '오수' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.00' }, + { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ + { column: 'ctpvNm', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, + 'sggNm', + 'swrmtFcltyNm', + ] + }, + 'swrmtFcltyCpc', + 'displayYear', + { name: 'areaGroup', header: { text: '처리구역면적(㎢)' }, items: ['procZoneArSm', 'cmbnProcZoneAr', 'sprsysProcZoneAr'] }, + { + name: 'drainGroup', header: { text: '배수설비(개소)' }, + items: ['drngEqpSm', 'cmbnDrngEqp', 'sprsysDrngEqpSm'], + }, + { + name: 'pipeGroup', header: { text: '하수관로' }, + items: [ + 'swrgPipeFcltyExtn', + { name: 'trunkGroup', header: { text: '차집/오수간선(m)' }, items: ['icgPltwtrMnnSm', 'icgExtn', 'pltwtrMnnExtn'] }, + { + name: 'siteGroup', header: { text: '토실/토구(개소)' }, + items: [ + { name: 'cmbnSiteGroup', header: { text: '합류식' }, items: ['cmbnRainwtrChmbrCnt'] }, + { name: 'sepSiteGroup', header: { text: '분류식' }, items: ['sprsysRainwtrChmbrCnt', 'sprsysRainwtrOutltCnt'] } + ] + }, + { + name: 'branchGroup', header: { text: '지선(m)' }, + items: ['brnchlnSm', 'cmbnBrnchlnExtn', { name: 'sepBranchGroup', header: { text: '우수/오수' }, items: ['sprsysExlnBrnchlnExtn', 'sprsysDrtwBrnchlnExtn'] }] + } + ], + }, + 'rmrk' + ] + }, + { + tabKey: 'ST00020003', + tabLabel: '관종별 현황', + rowStartNum: 2, + fixedColumns: 3, + columns: [ + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const uniqueValues = new Set(); - for (let i = 0; i < dataSource.getRowCount(); i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue) uniqueValues.add(String(divisionValue)); + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; } - } - ] - }, - { name: 'gugun', fieldName: 'gugun', 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, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const uniqueValues = new Set(); - for (let i = 0; i < dataSource.getRowCount(); i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue) uniqueValues.add(String(divisionValue)); + ] + }, + { name: 'gugun', fieldName: 'gugun', 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(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const uniqueValues = new Set(); - for (let i = 0; i < dataSource.getRowCount(); i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue) uniqueValues.add(String(divisionValue)); + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const uniqueValues = new Set(); + for (let i = 0; i < dataSource.getRowCount(); i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue) uniqueValues.add(String(divisionValue)); + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; } - } - ] - }, + ] + }, - /* 총계 필드 */ - { name: 'totalExtnSum', fieldName: 'totalExtnSum', header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'layingExtnSum', fieldName: 'layingExtnSum', header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'remvlExtnSum', fieldName: 'remvlExtnSum', header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + /* 총계 필드 */ + { name: 'totalExtnSum', fieldName: 'totalExtnSum', header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'layingExtnSum', fieldName: 'layingExtnSum', header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'remvlExtnSum', fieldName: 'remvlExtnSum', header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - ...Array.from({ length: 30 }, (_, i) => { - const yNum = (30 - i).toString().padStart(2, '0'); - const yrOffset = 29 - i; - return [ - { name: `totalExtn${yNum}y`, fieldName: `totalExtn${yNum}y`, header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: `layingExtn${yNum}y`, fieldName: `layingExtn${yNum}y`, header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: `remvlExtn${yNum}y`, fieldName: `remvlExtn${yNum}y`, header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' } - ]; - }).flat(), + ...Array.from({ length: 30 }, (_, i) => { + const yNum = (30 - i).toString().padStart(2, '0'); + // const yrOffset = 29 - i; + return [ + { name: `totalExtn${yNum}y`, fieldName: `totalExtn${yNum}y`, header: { text: '계' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: `layingExtn${yNum}y`, fieldName: `layingExtn${yNum}y`, header: { text: '매설연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: `remvlExtn${yNum}y`, fieldName: `remvlExtn${yNum}y`, header: { text: '철거연장(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' } + ]; + }).flat(), - { name: 'layingExtn', fieldName: 'layingExtn', header: { text: '매설연장(m)\n(당해년도)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, - ], - columnLayout: [ - { - name: 'areaGroup', - header: { text: '행정구역' }, - items: [ - - { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, - 'gugun', - 'swrgPipeNm', - ] - }, - 'displayYear', - { name: 'summaryGroup', header: { text: '총계' }, items: ['totalExtnSum', 'layingExtnSum', 'remvlExtnSum'] }, - - ...Array.from({ length: 30 }, (_, i) => { - const yNum = (30 - i).toString().padStart(2, '0'); - const yrOffset = 29 - i; - return { - name: `yr${yNum}Group`, - header: { text: `-${yrOffset}년` }, - items: [`totalExtn${yNum}y`, `layingExtn${yNum}y`, `remvlExtn${yNum}y`] - }; - }), - - { name: 'currentYrGroup', header: { text: `${baseYear}년` }, items: ['layingExtn'] }, - 'rmk' - ] -}, - { - tabKey: 'ST00020004', - tabLabel: '관경별 현황', - rowStartNum: 2, - fixedColumns: 3, - columns: [ - { - name: 'sido', - fieldName: 'sido', - header: { text: '시도' }, - mergeCells: true, - sortdisable: true, - footer: [ - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); - } - } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); - } - } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; - } - } - ] - }, - { name: 'gugun', fieldName: 'gugun', 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, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 0; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); - } - } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; - } - }, - { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { - const divisionIndex = 1; - const dataSource = grid.getDataSource(); - const rowCount = dataSource.getRowCount(); - const uniqueValues = new Set(); - for (let i = 0; i < rowCount; i++) { - const divisionValue = dataSource.getValue(i, 'displayYear'); - if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { - uniqueValues.add(String(divisionValue)); - } - } - const divisionValues = Array.from(uniqueValues); - return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; - } - } - ] - }, + { name: 'layingExtn', fieldName: 'layingExtn', header: { text: '매설연장(m)\n(당해년도)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'areaGroup', + header: { text: '행정구역' }, + items: [ - { name: 'totalExtnM', fieldName: 'totalExtnM', header: { text: '총계(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'under200', fieldName: 'under200', header: { text: '200mm 미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'over200Undev300', fieldName: 'over200Undev300', header: { text: '200mm이상~\n300mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'over300Undev500', fieldName: 'over300Undev500', header: { text: '300mm이상~\n500mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'over500Undev500', fieldName: 'over500Undev500', header: { text: '500mm이상~\n800mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'over800', fieldName: 'over800', header: { text: '800mm 이상' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'box', fieldName: 'box', header: { text: '사각형(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - { name: 'etc', fieldName: 'etc', header: { text: '기타\n(측구/개거 등)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, - - { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, - ], - columnLayout: [ - { - name: 'addrGroup', - header: { text: '행정구역' }, - items: [ - { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, - 'gugun', - 'swrgPipeNm', - ] - }, - 'displayYear', - 'totalExtnM', - { - name: 'circleGroup', - header: { text: '원형(m)' }, - items: ['under200', 'over200Undev300', 'over300Undev500', 'over500Undev500', 'over800'] - }, - 'box', - 'etc', - 'rmk' - ] -} + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', + 'swrgPipeNm', + ] + }, + 'displayYear', + { name: 'summaryGroup', header: { text: '총계' }, items: ['totalExtnSum', 'layingExtnSum', 'remvlExtnSum'] }, + + ...Array.from({ length: 30 }, (_, i) => { + const yNum = (30 - i).toString().padStart(2, '0'); + const yrOffset = 29 - i; + return { + name: `yr${yNum}Group`, + header: { text: `-${yrOffset}년` }, + items: [`totalExtn${yNum}y`, `layingExtn${yNum}y`, `remvlExtn${yNum}y`] + }; + }), + + { name: 'currentYrGroup', header: { text: `${baseYear}년` }, items: ['layingExtn'] }, + 'rmk' + ] + }, + { + tabKey: 'ST00020004', + tabLabel: '관경별 현황', + rowStartNum: 2, + fixedColumns: 3, + columns: [ + { + name: 'sido', + fieldName: 'sido', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, + footer: [ + { + valueCallback: (grid: GridView) => { + const divisionIndex = 0; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계'; + } + } + ] + }, + { name: 'gugun', fieldName: 'gugun', 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 rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + }, + { + valueCallback: (grid: GridView) => { + const divisionIndex = 1; + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + const uniqueValues = new Set(); + for (let i = 0; i < rowCount; i++) { + const divisionValue = dataSource.getValue(i, 'displayYear'); + if (divisionValue !== null && divisionValue !== undefined && divisionValue !== '') { + uniqueValues.add(String(divisionValue)); + } + } + const divisionValues = Array.from(uniqueValues); + return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : ''; + } + } + ] + }, + + { name: 'totalExtnM', fieldName: 'totalExtnM', header: { text: '총계(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'under200', fieldName: 'under200', header: { text: '200mm 미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over200Undev300', fieldName: 'over200Undev300', header: { text: '200mm이상~\n300mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over300Undev500', fieldName: 'over300Undev500', header: { text: '300mm이상~\n500mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over500Undev500', fieldName: 'over500Undev500', header: { text: '500mm이상~\n800mm미만' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'over800', fieldName: 'over800', header: { text: '800mm 이상' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'box', fieldName: 'box', header: { text: '사각형(m)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + { name: 'etc', fieldName: 'etc', header: { text: '기타\n(측구/개거 등)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }, + + { name: 'rmk', fieldName: 'rmk', header: { text: '비고' } }, + ], + columnLayout: [ + { + name: 'addrGroup', + header: { text: '행정구역' }, + items: [ + { column: 'sido', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + 'gugun', + 'swrgPipeNm', + ] + }, + 'displayYear', + 'totalExtnM', + { + name: 'circleGroup', + header: { text: '원형(m)' }, + items: ['under200', 'over200Undev300', 'over300Undev500', 'over500Undev500', 'over800'] + }, + 'box', + 'etc', + 'rmk' + ] + } ]; export const SewagePipeColumnInfoListRvw = rawList.map(tab => ({ diff --git a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts index 415d2896f..83d0ab65a 100644 --- a/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewageReservoirPumpRvwColumn.ts @@ -5,7 +5,7 @@ import { footerDecimal1ArrayByDivision, footerDecimal3ArrayByDivision } from "@/components/util/grid/footerSummary"; -import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; +import type { GridView } from "@/components/type/DataGrid.type"; // 하수도 통계 관리 04. 하수저류 시설 및 빗물 펌프장 const rawReservoirList = [ @@ -24,7 +24,7 @@ const rawReservoirList = [ sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 0; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); @@ -41,7 +41,7 @@ const rawReservoirList = [ } }, { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 1; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); @@ -69,7 +69,7 @@ const rawReservoirList = [ sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 0; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); @@ -85,7 +85,7 @@ const rawReservoirList = [ } }, { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 1; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); @@ -190,7 +190,7 @@ const rawReservoirList = [ sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 0; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); @@ -207,7 +207,7 @@ const rawReservoirList = [ } }, { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 1; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); @@ -235,7 +235,7 @@ const rawReservoirList = [ sortdisable: true, footer: [ { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 0; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); @@ -251,7 +251,7 @@ const rawReservoirList = [ } }, { - valueCallback: (grid: GridView, column: DataColumn, footerIndex: number, columnFooter: ColumnFooter, value: number | string | undefined) => { + valueCallback: (grid: GridView) => { const divisionIndex = 1; const dataSource = grid.getDataSource(); const rowCount = dataSource.getRowCount(); diff --git a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts index 490e79099..25449c6cc 100644 --- a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts @@ -1,16 +1,16 @@ import { ValueType } from "realgrid"; import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; -import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; +import type { GridView } from "@/components/type/DataGrid.type"; const rawSeweragePenetrationRateInfoList = { - columns: [ - { - name: 'ctpvNm', - fieldName: 'ctpvNm', - header: { text: '시도' }, - mergeCells: true, - sortdisable: true, + columns: [ + { + name: 'ctpvNm', + fieldName: 'ctpvNm', + header: { text: '시도' }, + mergeCells: true, + sortdisable: true, footer: [ { valueCallback: (grid: GridView) => { @@ -18,7 +18,7 @@ const rawSeweragePenetrationRateInfoList = { const dataSource = grid.getDataSource(); const uniqueValues = new Set(); for (let i = 0; i < dataSource.getRowCount(); i++) { - const val = dataSource.getValue(i, 'displayYear'); + const val = dataSource.getValue(i, 'displayYear'); if (val) uniqueValues.add(String(val)); } const divisionValues = Array.from(uniqueValues); @@ -40,12 +40,12 @@ const rawSeweragePenetrationRateInfoList = { } ] }, - { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, - { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true }, - { - name: 'displayYear', - fieldName: 'displayYear', - header: { text: '구분' }, + { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true }, + { + name: 'displayYear', + fieldName: 'displayYear', + header: { text: '구분' }, sortdisable: true, footer: [ { @@ -76,9 +76,9 @@ const rawSeweragePenetrationRateInfoList = { } ] }, - { name: 'wtsysNm', fieldName: 'rvbsn', header: { text: '수계' } }, + { name: 'wtsysNm', fieldName: 'rvbsn', header: { text: '수계' } }, { name: 'trbtrNm', fieldName: 'trbtrNm', header: { text: '지류' } }, - { name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' } }, + { name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' } }, { name: 'lgbsn', fieldName: 'lgbsn', header: { text: '중권역' } }, { name: 'mdmbsn', fieldName: 'mdmbsn', header: { text: '소권역' } }, { name: 'spcCtpnRgnCn', fieldName: 'spcCtpnRgnCn', header: { text: '특별대책지역' } }, @@ -111,9 +111,9 @@ const rawSeweragePenetrationRateInfoList = { hideChildHeaders: false, header: { text: '행정구역' }, items: [ - { column: 'ctpvNm', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, + { column: 'ctpvNm', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, 'sggNm', - 'admdstNm', + 'admdstNm', ] }, 'displayYear', @@ -161,7 +161,7 @@ const rawSeweragePenetrationRateInfoList = { 'sewerInstlRt', 'rmrk', ], - fixedColumns: 2, + fixedColumns: 2, rowStartNum: 3 }; diff --git a/apps/st/src/components/util/grid/footerSummary.ts b/apps/st/src/components/util/grid/footerSummary.ts index 0b9bd148b..530d65f8c 100644 --- a/apps/st/src/components/util/grid/footerSummary.ts +++ b/apps/st/src/components/util/grid/footerSummary.ts @@ -181,8 +181,8 @@ const createSumFooterArrayByDivision = (format: string, divisionFieldName: strin grid: GridView, column: DataColumn, footerIndex: number, - columnFooter: unknown, - value: number | string | undefined + // columnFooter: unknown, + // value: number | string | undefined ) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName), }, { @@ -191,8 +191,8 @@ const createSumFooterArrayByDivision = (format: string, divisionFieldName: strin grid: GridView, column: DataColumn, footerIndex: number, - columnFooter: unknown, - value: number | string | undefined + // columnFooter: unknown, + // value: number | string | undefined ) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName), }, ]; @@ -205,8 +205,8 @@ const createAvgFooterArrayByDivision = (format: string, divisionFieldName: strin grid: GridView, column: DataColumn, footerIndex: number, - columnFooter: unknown, - value: number | string | undefined + // columnFooter: unknown, + // value: number | string | undefined ) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName), }, { @@ -215,8 +215,8 @@ const createAvgFooterArrayByDivision = (format: string, divisionFieldName: strin grid: GridView, column: DataColumn, footerIndex: number, - columnFooter: unknown, - value: number | string | undefined + // columnFooter: unknown, + // value: number | string | undefined ) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName), }, ]; diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index 0e67b89fe..f7a75da29 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -2,31 +2,31 @@ import axios from "axios"; import { ENDPOINT } from "@/shared/config"; -import { - type StatsInputStatusReviewApiResponse -} from "../model/statistics-input-review-interface"; -import { +import { type CheckInputPeriodResponse } from "../model/statistics-input-interface"; +import { + type StatsInputStatusReviewApiResponse +} from "../model/statistics-input-review-interface"; +import { type SewerageSupplyRateReviewRequestParams, type SewerageSupplyRateReviewApiResponse, -} from "../model/statistics-sewerage-penetration-review-interface"; -import { +} from "../model/statistics-sewerage-penetration-review-interface"; +import { type SewagePipeReviewRequestParams, - type SewagePipeReviewRegionalApiResponse , + type SewagePipeReviewRegionalApiResponse, type SewagePipeFacilityApiResponse, type SewerPipeTypeApiResponse, type SewerPipeDiamApiResponse, - type SewagePipeReviewService } from "../model/statistics-sewage-pipe-review-interface"; -import { +import { type SewerPipeMtcApiResponse, type SewerPipeDrdgApiResponse, type SewerPipeMtcRequestParams } from "../model/statistics-sewage-pipe-mtc-review-interface"; -import { +import { type SewerReservoirRegionalApiResponse, type PumpingStationRegionalApiResponse } from "../model/sewage-reservoir-pump-review-interface"; -import { +import { type MediationPumpStationApiResponse, } from "../model/statistics-pumping-station-review-interface"; @@ -50,8 +50,8 @@ export class StatisticsManagementApiService { * 입력 기간 확인 (공통) * @param year 연도 */ - public async checkInputPeriod(year: string | number): Promise { - const response = await axios.get( + public async checkInputPeriod(year: string | number): Promise { + const response = await axios.get( `${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period`, { params: { year } } ); @@ -80,7 +80,7 @@ export class StatisticsManagementApiService { params: SewagePipeReviewRequestParams ): Promise { const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/regional-status`, + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/regional-status`, { params } ); return response.data; @@ -94,16 +94,16 @@ export class StatisticsManagementApiService { params: SewagePipeReviewRequestParams ): Promise { const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/facility-status`, + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/facility-status`, { params } ); return response.data; } - /** - * 02. 하수관로 관종별 시설현황 조회 (Tab3) - * @param params (sido, gugun, yr, bsnmCd) - */ + /** + * 02. 하수관로 관종별 시설현황 조회 (Tab3) + * @param params (sido, gugun, yr, bsnmCd) + */ public async getSewagePipeTypeList( params: { yr?: string; @@ -113,7 +113,7 @@ export class StatisticsManagementApiService { } ): Promise { const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/type-status`, + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/type-status`, { params } ); return response.data; @@ -132,7 +132,7 @@ export class StatisticsManagementApiService { } ): Promise { const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/diam-status`, + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe/diam-status`, { params } ); return response.data; @@ -145,89 +145,89 @@ export class StatisticsManagementApiService { public async getSewerPipeMtcList( params: SewerPipeMtcRequestParams ): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe-mtc/repair`, - { params } - ); - return response.data; -} - -/** - * 03. 하수관로 유지관리 관로준설 현황 조회 (Tab B) - * @param params (yr, bsnmCd, sido, gugun) - */ -public async getSewerPipeDrdgList( - params: { - yr?: string; - bsnmCd?: string; - sido?: string; - gugun?: string; + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe-mtc/repair`, + { params } + ); + return response.data; } -): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe-mtc/dredging`, - { params } - ); - return response.data; -} -/** - * 04. 하수저류시설 tab1. 시설현황 조회 - * @param params (yr, bsnmCd, sido, gugun) - */ -public async getSewerReservoirList( - params: { - yr?: string; - bsnmCd?: string; - sido?: string; - gugun?: string; + /** + * 03. 하수관로 유지관리 관로준설 현황 조회 (Tab B) + * @param params (yr, bsnmCd, sido, gugun) + */ + public async getSewerPipeDrdgList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-pipe-mtc/dredging`, + { params } + ); + return response.data; } -): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-reservoir/facilities`, - { params } - ); - return response.data; -} -/** - * 04. 빗물펌프장 tab2. 조회 - * @param params (yr, bsnmCd, sido, gugun) - */ -public async getPumpStationList( - params: { - yr?: string; - bsnmCd?: string; - sido?: string; - gugun?: string; + /** + * 04. 하수저류시설 tab1. 시설현황 조회 + * @param params (yr, bsnmCd, sido, gugun) + */ + public async getSewerReservoirList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-reservoir/facilities`, + { params } + ); + return response.data; } -): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-reservoir/pumping-stations`, - { params } - ); - return response.data; -} -/** - * 05. 중계펌프장 통계관리 조회 - * @param params (yr, bsnmCd, sido, gugun, bscFctNm) - */ -public async getMediationPumpStationList( - params: { - yr?: string; // 조회 연도 - bsnmCd?: string; // 사업소 코드 - sido?: string; // 시도 - gugun?: string; // 구군 - bscFctNm?: string; // 펌프장명 (시설명) + /** + * 04. 빗물펌프장 tab2. 조회 + * @param params (yr, bsnmCd, sido, gugun) + */ + public async getPumpStationList( + params: { + yr?: string; + bsnmCd?: string; + sido?: string; + gugun?: string; + } + ): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/sewer-reservoir/pumping-stations`, + { params } + ); + return response.data; + } + + /** + * 05. 중계펌프장 통계관리 조회 + * @param params (yr, bsnmCd, sido, gugun, bscFctNm) + */ + public async getMediationPumpStationList( + params: { + yr?: string; // 조회 연도 + bsnmCd?: string; // 사업소 코드 + sido?: string; // 시도 + gugun?: string; // 구군 + bscFctNm?: string; // 펌프장명 (시설명) + } + ): Promise { + const response = await axios.get( + `${ENDPOINT.ST_SERVICE.REVIEW}/mediation-pump-stations`, + { params } + ); + return response.data; } -): Promise { - const response = await axios.get( - `${ENDPOINT.ST_SERVICE.REVIEW}/mediation-pump-stations`, - { params } - ); - return response.data; -} } // 통합 인스턴스 수출 diff --git a/apps/st/src/entities/statistics/model/statistics-input-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-input-review-interface.ts index 93f1a4651..ff86a8799 100644 --- a/apps/st/src/entities/statistics/model/statistics-input-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-input-review-interface.ts @@ -104,7 +104,7 @@ export type StatsInputStatusReviewApiResponse = ApiResponse Date: Wed, 25 Feb 2026 17:04:27 +0900 Subject: [PATCH 019/150] =?UTF-8?q?st:=20fix:=20=ED=95=98=EC=88=98?= =?UTF-8?q?=EA=B4=80=EB=A1=9C=20=EA=B2=80=ED=86=A0=20=EC=BB=AC=EB=9F=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20&=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=20=ED=8C=90=EB=B3=84=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/TabDataGrid.tsx | 6 ++-- .../input/SeweragePenetrationRateInp.tsx | 28 ++++++++++-------- apps/st/src/components/type/DataGrid.type.ts | 4 ++- .../util/column/review/SewagePipeRvwColumn.ts | 6 ++-- .../pages/statistics/ui/statistics-page.tsx | 6 ++++ apps/st/src/store/admin-store.ts | 29 +++++++++++++++++++ 6 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 apps/st/src/store/admin-store.ts diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index 96aec2a94..b188ce3fe 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -708,7 +708,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r valueType: col.valueType, dataFormat: col.dataFormat, ...(col.styleName != null && { styleName: col.styleName }), - ...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }), + ...(col.mergeCells && { mergeRule: { criteria: col.mergeGroupField ? `values['${col.mergeGroupField}'] + value` : 'value' as const } }), }; if (col.footer) { if (Array.isArray(col.footer)) c.footer = col.footer; @@ -993,7 +993,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r valueType: col.valueType, dataFormat: col.dataFormat, ...(col.styleName != null && { styleName: col.styleName }), - ...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }), + ...(col.mergeCells && { mergeRule: { criteria: col.mergeGroupField ? `values['${col.mergeGroupField}'] + value` : 'value' as const } }), }; if (col.footer) { @@ -1151,7 +1151,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r expression: (column.footer as { expression?: 'sum' | 'avg' | 'count' | 'max' | 'min' }).expression, numberFormat: (column.footer as { numberFormat?: string }).numberFormat, } : undefined} - mergeRule={column.mergeCells ? { criteria: 'value' } : undefined} + mergeRule={column.mergeCells ? { criteria: column.mergeGroupField ? `values['${column.mergeGroupField}'] + value` : 'value' } : undefined} sortable={!column.sortdisable} /> ))} diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 13249fdf8..cf7e52e27 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -12,6 +12,7 @@ import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import SubmitDailog from "./SubmitDailog"; import { useAlertStore } from "@/shared/lib"; +import { useAdminStore } from "@/store/admin-store"; import { statisticsInputAPi } from "@/entities/statistics/api/statistics-input-service"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { @@ -60,6 +61,8 @@ const SeweragePenetrationRateInp = () => { const [showDailog, setShowDailog] = useState(false); const { confirm } = useConfirm(); + const { isAdminInfo } = useAdminStore(); + const [footerFromApi, setFooterFromApi] = useState<{ totalPopl?: number; poplSm?: number; frgnrPoplSm?: number; refArea?: number } | null>(null); const getGridDataRef = useRef<(() => Record[]) | null>(null); @@ -74,7 +77,7 @@ const SeweragePenetrationRateInp = () => { // 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요. const columnInfo = useMemo(() => { const { columns, columnLayout } = SeweragePenetrationRateInpColumn; - const base = isEnterable ? columns : columns.map((col) => ({ ...col, editable: false as const })); + const base = (isEnterable || isAdminInfo) ? columns : columns.map((col) => ({ ...col, editable: false as const })); return { columnLayout, columns: base.map((col) => { @@ -85,7 +88,7 @@ const SeweragePenetrationRateInp = () => { return col; }), }; - }, [isEnterable, footerFromApi]); + }, [isEnterable, isAdminInfo, footerFromApi]); const handleReset = () => { setRowData([]); @@ -389,26 +392,27 @@ const SeweragePenetrationRateInp = () => { <>
- {rowData.length > 0 && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) && + {rowData.length > 0 && (isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) || isAdminInfo) && (
- - getLastYearDataFunc(searchValues ?? {})} /> - - { - // 최고 관리자일땐 저장 가능하게 - - } + {isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) && ( + <> + + getLastYearDataFunc(searchValues ?? {})} /> + + + )} + {(isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true) || isAdminInfo) && }
- } + )} {isLoading &&
} 0) && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true)} rowStartNum={3} diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index 69d82b6d2..0106251d5 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -12,7 +12,7 @@ export type RealGridColumnInfo = { valueType?: import("realgrid").ValueType; dataFormat?: string; styleName?: string; - mergeRule?: { criteria: "value" }; + mergeRule?: { criteria: "value" | string }; sortable?: boolean; footer?: | { @@ -135,6 +135,8 @@ export type ColumnConfig = { /** 셀에 적용할 CSS 클래스명 (예: 'rg-cell-readonly', 'rg-cell-total') */ styleName?: string; mergeCells?: boolean; + /** 병합 시 그룹 기준 필드(예: 시군구). 지정 시 같은 그룹 내에서만 value 기준 병합 */ + mergeGroupField?: string; sortdisable?: boolean; sortPreserveGroupOrder?: SortPreserveGroupOrder; valueType?: ValueType; diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts index cc86bd201..1020ce3bc 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -218,7 +218,8 @@ const rawList = [ ] }, { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, - { name: 'swrmtFcltyNm', fieldName: 'swrmtFcltyNm', header: { text: '하수처리시설 명칭' }, mergeCells: true }, + { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true }, + { name: 'swrmtFcltyNm', fieldName: 'swrmtFcltyNm', header: { text: '하수처리시설 명' }, mergeCells: true, mergeGroupField: 'admdstNm' }, { name: 'displayYear', fieldName: 'displayYear', @@ -276,8 +277,9 @@ const rawList = [ name: 'addrGroup', header: { text: '행정구역' }, items: [ - { column: 'ctpvNm', footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] }, + { column: 'ctpvNm', footerUserSpans: [{ colspan: 4 }, { colspan: 4 }] }, 'sggNm', + 'admdstNm', 'swrmtFcltyNm', ] }, diff --git a/apps/st/src/pages/statistics/ui/statistics-page.tsx b/apps/st/src/pages/statistics/ui/statistics-page.tsx index 7513b0ffe..a228a4d46 100644 --- a/apps/st/src/pages/statistics/ui/statistics-page.tsx +++ b/apps/st/src/pages/statistics/ui/statistics-page.tsx @@ -3,6 +3,7 @@ import { Suspense, useRef, useEffect } from "react"; import dynamic from "next/dynamic"; import { useTabStore } from "@/store/tab-store"; +import { useAdminStore } from "@/store/admin-store"; import Dashboard from "@/components/Dashboard"; import { SkeletonPage } from "@/shared/ui/skeleton-page"; @@ -10,6 +11,7 @@ import "@rsone/components/index.css"; const StatisticsPage = () => { const { tabs, activeTabId } = useTabStore(); + const fetchAdminInfo = useAdminStore((state) => state.fetchAdminInfo); // 컴포넌트 캐시를 useRef로 유지 (재렌더링 시에도 유지됨) const componentCache = useRef(new Map()); @@ -26,6 +28,10 @@ const StatisticsPage = () => { }); }, [tabs]); + useEffect(() => { + fetchAdminInfo(); + }, [fetchAdminInfo]); + // 탭이 없으면 Dashboard 표시 if (tabs.length === 0) { return ( diff --git a/apps/st/src/store/admin-store.ts b/apps/st/src/store/admin-store.ts new file mode 100644 index 000000000..1b9937d4f --- /dev/null +++ b/apps/st/src/store/admin-store.ts @@ -0,0 +1,29 @@ +import { create } from "zustand"; +import { UserApi } from "@/entities/user"; +import { getUserInfo } from "../../../../packages/common/components/src/api"; + +interface AdminStore { + /** 현재 사용자가 관리자 목록에 있는지 */ + isAdminInfo: boolean; + setAdminInfo: (value: boolean) => void; + /** API로 관리자 목록 조회 후 현재 사용자 일치 여부 설정 */ + fetchAdminInfo: () => Promise; +} + +const useAdminStoreHook = create((set) => ({ + isAdminInfo: false, + setAdminInfo: (value) => set({ isAdminInfo: value }), + fetchAdminInfo: async () => { + try { + const userInfo = await getUserInfo(); + const adminList = await UserApi.findPageAdminUser({ userName: userInfo.userName }); + const matched = adminList.content?.filter((a) => a.userName === userInfo.userName) ?? []; + set({ isAdminInfo: matched.length > 0 }); + } catch { + set({ isAdminInfo: false }); + } + }, +})); + +export const useAdminStore = useAdminStoreHook; +export const getAdminStoreState = () => useAdminStoreHook.getState(); From 9cd3ce9f639561e2780f9cc7b89a831c9c7b3b1f Mon Sep 17 00:00:00 2001 From: LGS Date: Thu, 26 Feb 2026 13:47:14 +0900 Subject: [PATCH 020/150] =?UTF-8?q?=EB=AC=BC=20=EC=9E=AC=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=20=EB=8C=80=EB=AF=BC=EC=84=9C=EB=B9=84=EC=8A=A4=20lnb=20?= =?UTF-8?q?=EA=B3=A0=EC=A0=95(=EB=B3=B8=EB=AC=B8=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A1=A4=20=EC=8B=9C=20lnb=20=EA=B3=A0=EC=A0=95=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(landing)/reuse/_components/Lnb.tsx | 10 +++++++++- apps/dm_new/app/(landing)/reuse/layout.tsx | 19 ++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx b/apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx index 4a33e7314..22066069a 100644 --- a/apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx +++ b/apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx @@ -29,7 +29,15 @@ export default function Lnb({ title, items }: { title: string; items: LnbItem[] const [openLabel, setOpenLabel] = useState(defaultOpenLabel); return ( -
@@ -62,4 +66,4 @@ export default function Cases() {
); -} \ No newline at end of file +} diff --git a/apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx b/apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx index 8f93bdd72..c520f5aea 100644 --- a/apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx +++ b/apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx @@ -1,4 +1,5 @@ // apps/dm/app/(landing)/reuse/_sections/Hero.tsx +import Image from "next/image"; import styles from "../reuse.module.css"; export default function Hero() { @@ -21,9 +22,15 @@ export default function Hero() {
- + scroll down
); -} \ No newline at end of file +} diff --git a/apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx b/apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx index e7153d721..c8c69ad7b 100644 --- a/apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx +++ b/apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx @@ -1,12 +1,17 @@ // apps/dm/app/(landing)/reuse/_sections/IntroCards.tsx "use client"; +import Image, { type StaticImageData } from "next/image"; import Link from "next/link"; +import card01 from "@/public/images/landing/card_01.jpg"; +import card02 from "@/public/images/landing/card_02.jpg"; +import card03 from "@/public/images/landing/card_03.jpg"; +import card04 from "@/public/images/landing/card_04.jpg"; type IntroCardItem = { title: string; desc: string; - image: string; + image: StaticImageData; href: string; }; @@ -14,25 +19,25 @@ const INTRO_CARDS: IntroCardItem[] = [ { title: "물 재이용 법", desc: "물의 재이용 촉진 및 지원에 관한 법률을 시행하고, 일부 법률은 개정되었습니다.", - image: "/images/landing/card_01.jpg", + image: card01, href: "/reuse/intro/law", }, { title: "친환경 수자원 빗물", desc: "가뭄을 극복하고 홍수를 예방하기 위해 비가 내릴 때 빗물을 모아두었다가 사용합니다.", - image: "/images/landing/card_02.jpg", + image: card02, href: "/reuse/intro/rain", }, { title: "알뜰하게 재이용되는 중수", desc: "한번 사용한 물을 중수도 시설을 통해 재생하여 개별적 또는 지역적으로 재이용할 수 있습니다.", - image: "/images/landing/card_03.jpg", + image: card03, href: "/reuse/intro/greywater", }, { title: "하수처리수 재이용", desc: "사용하고 버려진 공공하수처리장으로 모여진 물을 용도에 맞는 처리 후 재이용 할 수 있습니다.", - image: "/images/landing/card_04.jpg", + image: card04, href: "/reuse/intro/treated", }, ]; @@ -61,10 +66,12 @@ export default function IntroCards() { className="group relative h-[280px] md:h-[320px] lg:h-[500px] overflow-hidden rounded-2xl cursor-pointer focus:outline-none focus:ring-2 focus:ring-sky-400/60" > {/* 배경 이미지 (이미 그라디언트 포함) */} - {card.title} {/* 텍스트 영역 */} @@ -83,4 +90,4 @@ export default function IntroCards() {
); -} \ No newline at end of file +} diff --git a/apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx b/apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx index 3ca2adb63..6d4d00af1 100644 --- a/apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx +++ b/apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx @@ -1,7 +1,12 @@ // apps/dm/app/(landing)/reuse/cases/greywater/page.tsx "use client"; +import Image, { type StaticImageData } from "next/image"; import SubPageHeader from "../../_components/SubPageHeader"; +import greywaterOverall from "@/public/images/landing/cases/greywater_overall.png"; +import greywaterFactory from "@/public/images/landing/cases/greywater_factory.png"; +import greywaterBuilding from "@/public/images/landing/cases/greywater_building.png"; +import greywaterUsage from "@/public/images/landing/cases/greywater_usage.png"; function SectionTitle({ children }: { children: React.ReactNode }) { return ( @@ -21,17 +26,18 @@ function Hr({ className = "" }: HrProps) { } type ImgCardProps = { - src: string; + src: StaticImageData; alt: string; }; function ImgCard({ src, alt }: ImgCardProps) { return (
- {alt}
); @@ -58,7 +64,7 @@ export default function GreywaterPage() {
전체 중수도 설치 및 이용 현황 (공장 및 발전시설 제외)
- +
@@ -68,7 +74,7 @@ export default function GreywaterPage() {
공장 및 발전시설 내 중수도 설치 및 이용 현황
- +
@@ -78,7 +84,7 @@ export default function GreywaterPage() {
중수도 건축물별 이용량 현황
- +
@@ -88,7 +94,7 @@ export default function GreywaterPage() {
중수도 용도별 이용량 현황
- +
diff --git a/apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx b/apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx index b9aa54f15..3d7aa9a3f 100644 --- a/apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx +++ b/apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx @@ -1,7 +1,11 @@ // apps/dm/app/(landing)/reuse/cases/rain/page.tsx "use client"; +import Image, { type StaticImageData } from "next/image"; import SubPageHeader from "../../_components/SubPageHeader"; +import rainwaterBuilding from "@/public/images/landing/cases/rainwater_building.png"; +import rainwaterUsage from "@/public/images/landing/cases/rainwater_usage.png"; +import rainwaterUsageAmount from "@/public/images/landing/cases/rainwater_usage_amount.png"; function SectionTitle({ children }: { children: React.ReactNode }) { return ( @@ -21,17 +25,18 @@ function Hr({ className = "" }: HrProps) { } type ImgCardProps = { - src: string; + src: StaticImageData; alt: string; }; function ImgCard({ src, alt }: ImgCardProps) { return (
- {alt}
); @@ -58,7 +63,7 @@ export default function GreywaterPage() {
빗물이용시설의 건축물별 빗물이용량 현황
- +
@@ -68,7 +73,7 @@ export default function GreywaterPage() {
빗물이용시설의 용도별 분포 (2023년 기준)
- +
@@ -78,7 +83,7 @@ export default function GreywaterPage() {
빗물이용시설의 용도별 빗물이용량 현황
- +
diff --git a/apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx b/apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx index e3c900557..b9e9073c0 100644 --- a/apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx +++ b/apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx @@ -1,7 +1,12 @@ // apps/dm/app/(landing)/reuse/cases/treated/page.tsx "use client"; +import Image, { type StaticImageData } from "next/image"; import SubPageHeader from "../../_components/SubPageHeader"; +import treatedWaterYear from "@/public/images/landing/cases/treated_water_year.png"; +import treatedWaterUsage from "@/public/images/landing/cases/treated_water_usage.png"; +import treatedWaterFacility from "@/public/images/landing/cases/treated_water_facility.png"; +import treatedWaterRegion from "@/public/images/landing/cases/treated_water_region.png"; function SectionTitle({ children }: { children: React.ReactNode }) { return ( @@ -21,17 +26,18 @@ function Hr({ className = "" }: HrProps) { } type ImgCardProps = { - src: string; + src: StaticImageData; alt: string; }; function ImgCard({ src, alt }: ImgCardProps) { return (
- {alt}
); @@ -58,7 +64,7 @@ export default function GreywaterPage() {
연도별 하수처리수 재이용 현황
- +
@@ -68,7 +74,7 @@ export default function GreywaterPage() {
용도별 하수처리수 재이용 현황
- +
@@ -78,7 +84,7 @@ export default function GreywaterPage() {
하수처리시설 설치 및 이용 현황
- +
@@ -88,7 +94,7 @@ export default function GreywaterPage() {
시도별 하수처리수 재이용 현황
- +
diff --git a/apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx b/apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx index 470f49ee2..d694d040e 100644 --- a/apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx +++ b/apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx @@ -1,7 +1,11 @@ // apps/dm/app/(landing)/reuse/intro/greywater/page.tsx "use client"; +import Image, { type StaticImageData } from "next/image"; import SubPageHeader from "../../_components/SubPageHeader"; +import greywaterIntro from "@/public/images/landing/intro/greywater_intro.png"; +import greywaterMandatory from "@/public/images/landing/intro/greywater_mandatory.png"; +import greywaterStandard from "@/public/images/landing/intro/greywater_standard.png"; function SectionTitle({ children }: { children: React.ReactNode }) { return ( @@ -21,17 +25,18 @@ function Hr({ className = "" }: HrProps) { } type ImgCardProps = { - src: string; + src: StaticImageData; alt: string; }; function ImgCard({ src, alt }: ImgCardProps) { return (
- {alt}
); @@ -58,7 +63,7 @@ export default function GreywaterPage() {
중수 재이용 소개
- +
@@ -68,7 +73,7 @@ export default function GreywaterPage() {
중수도 설치 의무 대상
- +
@@ -78,7 +83,7 @@ export default function GreywaterPage() {
중수도 시설기준 및 관리기준
- +
diff --git a/apps/dm_new/app/(landing)/reuse/intro/law/page.tsx b/apps/dm_new/app/(landing)/reuse/intro/law/page.tsx index 284b884de..c9952bf0b 100644 --- a/apps/dm_new/app/(landing)/reuse/intro/law/page.tsx +++ b/apps/dm_new/app/(landing)/reuse/intro/law/page.tsx @@ -1,7 +1,14 @@ // apps/dm/app/(landing)/reuse/intro/law/page.tsx "use client"; +import Image, { type StaticImageData } from "next/image"; import SubPageHeader from "../../_components/SubPageHeader"; +import lawTimeline from "@/public/images/landing/intro/law_timeline.png"; +import performanceLeft from "@/public/images/landing/intro/performance_left.png"; +import domesticTextbox from "@/public/images/landing/intro/domestic_textbox.png"; +import policyTimeline from "@/public/images/landing/intro/policy_timeline.png"; +import relNote from "@/public/images/landing/intro/rel_note.png"; +import linkage from "@/public/images/landing/intro/linkage.png"; function SectionTitle({ children }: { children: React.ReactNode }) { return ( @@ -21,17 +28,18 @@ function Hr({ className = "" }: HrProps) { } type ImgCardProps = { - src: string; + src: StaticImageData; alt: string; }; function ImgCard({ src, alt }: ImgCardProps) { return (
- {alt}
); @@ -58,7 +66,7 @@ export default function WaterReuseLawPage() {
법제도 · 계획 및 기본계획 수립
- +
@@ -68,7 +76,7 @@ export default function WaterReuseLawPage() {
제 2차 계획 대비 실적 현황
- +
@@ -78,7 +86,7 @@ export default function WaterReuseLawPage() {
국내 관련 현황
- +
@@ -88,7 +96,7 @@ export default function WaterReuseLawPage() {
물 재이용 정책 변천 추이
- +
@@ -98,7 +106,7 @@ export default function WaterReuseLawPage() {
타법령과의 관계도
- +
@@ -114,7 +122,7 @@ export default function WaterReuseLawPage() { • 물관리 계획(국가수도기본계획, 국가 및 유역하수도종합계획, 하천유역수자원관리계획 등)과의 연계성 확보 → 통합적인 관점에서 물이용 체계 구축 필요

- + diff --git a/apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx b/apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx index bb9dc37db..fdd48aa5a 100644 --- a/apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx +++ b/apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx @@ -1,7 +1,12 @@ // apps/dm/app/(landing)/reuse/intro/law/page.tsx "use client"; +import Image, { type StaticImageData } from "next/image"; import SubPageHeader from "../../_components/SubPageHeader"; +import rainreuseIntro from "@/public/images/landing/intro/rainreuse_intro.png"; +import rainreuseMandatory from "@/public/images/landing/intro/rainreuse_mandatory.png"; +import rainwaterDesign from "@/public/images/landing/intro/rainwater_design.png"; +import rainwaterStandards from "@/public/images/landing/intro/rainwater_standards.png"; function SectionTitle({ children }: { children: React.ReactNode }) { return ( @@ -21,17 +26,18 @@ function Hr({ className = "" }: HrProps) { } type ImgCardProps = { - src: string; + src: StaticImageData; alt: string; }; function ImgCard({ src, alt }: ImgCardProps) { return (
- {alt}
); @@ -58,7 +64,7 @@ export default function RainwaterReusePage() {
빗물 재이용 소개
- +
@@ -68,7 +74,7 @@ export default function RainwaterReusePage() {
빗물이용시설 설치 관리 법적 의무 대상
- +
@@ -78,7 +84,7 @@ export default function RainwaterReusePage() {
빗물이용시설 설계 방안
- +
@@ -88,7 +94,7 @@ export default function RainwaterReusePage() {
빗물이용시설 시설기준 및 관리기준
- +
diff --git a/apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx b/apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx index 14e4582cb..e64299f70 100644 --- a/apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx +++ b/apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx @@ -1,7 +1,10 @@ // apps/dm/app/(landing)/reuse/intro/treated/page.tsx "use client"; +import Image, { type StaticImageData } from "next/image"; import SubPageHeader from "../../_components/SubPageHeader"; +import treatedwaterIntro from "@/public/images/landing/intro/treatedwater_intro.png"; +import treatedwaterMandatory from "@/public/images/landing/intro/treatedwater_mandatory.png"; function SectionTitle({ children }: { children: React.ReactNode }) { return ( @@ -21,17 +24,18 @@ function Hr({ className = "" }: HrProps) { } type ImgCardProps = { - src: string; + src: StaticImageData; alt: string; }; function ImgCard({ src, alt }: ImgCardProps) { return (
- {alt}
); @@ -58,7 +62,7 @@ export default function TreatedWaterPage() {
하수처리수
- +
@@ -68,7 +72,7 @@ export default function TreatedWaterPage() {
하수처리수 재이용시설 의무 대상
- +
From fa21387c55c45961805b95ac665edc5ded74d228 Mon Sep 17 00:00:00 2001 From: ljpark Date: Thu, 26 Feb 2026 14:39:25 +0900 Subject: [PATCH 022/150] =?UTF-8?q?dm=5Fnew:=20Lnb=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dm_new/app/(landing)/reuse/layout.tsx | 1 - apps/dm_new/app/(with-layout)/intro/layout.tsx | 4 +++- apps/dm_new/app/(with-layout)/layout.tsx | 12 +++++++++--- apps/dm_new/components/Lnb.tsx | 12 +++++++++++- apps/dm_new/eslint.config.mjs | 2 +- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/apps/dm_new/app/(landing)/reuse/layout.tsx b/apps/dm_new/app/(landing)/reuse/layout.tsx index b6840bf71..7574782c9 100644 --- a/apps/dm_new/app/(landing)/reuse/layout.tsx +++ b/apps/dm_new/app/(landing)/reuse/layout.tsx @@ -3,7 +3,6 @@ import "./reuse.module.css"; -// import type { ReactNode } from "react"; import TopNav from "./_components/TopNav"; import { usePathname } from "next/navigation"; diff --git a/apps/dm_new/app/(with-layout)/intro/layout.tsx b/apps/dm_new/app/(with-layout)/intro/layout.tsx index 91e9e2e54..e025f4c80 100644 --- a/apps/dm_new/app/(with-layout)/intro/layout.tsx +++ b/apps/dm_new/app/(with-layout)/intro/layout.tsx @@ -5,7 +5,9 @@ export default function IntroLayout({ children }: { children: ReactNode }) { return (
-
{children}
+
+ {children} +
); } diff --git a/apps/dm_new/app/(with-layout)/layout.tsx b/apps/dm_new/app/(with-layout)/layout.tsx index 7ea176c6d..2c31919bc 100644 --- a/apps/dm_new/app/(with-layout)/layout.tsx +++ b/apps/dm_new/app/(with-layout)/layout.tsx @@ -1,13 +1,19 @@ // app/(with-layout)/layout.tsx +/** + * 메인을 제외한 메뉴안 페이지의 layout + */ + import "../reuse.module.css"; import { StoreProvider } from "../store"; import TopNav from "@/components/TopNav"; -export default function ReuseLandingLayout({ children }: { children: React.ReactNode }) { +export default function WithLayout({ children }: { children: React.ReactNode }) { return ( - -
{children}
{/* TopNav 높이만큼 */} +
{/* TopNav 높이만큼 */} + +
{children}
{/* Lnb 너비만큼 */} +
); } diff --git a/apps/dm_new/components/Lnb.tsx b/apps/dm_new/components/Lnb.tsx index a9a41d4a4..13c6523db 100644 --- a/apps/dm_new/components/Lnb.tsx +++ b/apps/dm_new/components/Lnb.tsx @@ -68,11 +68,21 @@ export default function Lnb() { }, [pathname, secondLevelMenus]); return ( -