diff --git a/apps/st/src/components/module/CompactDataGrid.tsx b/apps/st/src/components/module/CompactDataGrid.tsx index 04f7cc0b0..4377eb762 100644 --- a/apps/st/src/components/module/CompactDataGrid.tsx +++ b/apps/st/src/components/module/CompactDataGrid.tsx @@ -178,6 +178,7 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, editable: col.editable, valueType: col.valueType, dataFormat: col.dataFormat, + ...(col.styleName != null && { styleName: col.styleName }), ...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }), }; diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index ebab75ca8..cdf0e286d 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -591,6 +591,7 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport editable: col.editable, valueType: col.valueType, dataFormat: col.dataFormat, + ...(col.styleName != null && { styleName: col.styleName }), ...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }), }; diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index b2a470360..530b3579b 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -40,10 +40,11 @@ const SearchForm = (props: SearchFormProps = {}) => { const [values, setValues] = useState({ sido: props.defaultValues?.sido || "", - gugun: props.defaultValues?.gugun || "", + gugun: props.defaultValues?.gugun || '', year: props.defaultValues?.year || String(defaultYear), - order: props.defaultValues?.order || "", - fctName: props.defaultValues?.fctName || "", + order: props.defaultValues?.order || '', + fctName: props.defaultValues?.fctName || '', + bsnmCd: props.defaultValues?.bsnmCd || '', }); const { data: sidoList } = useSWR>( @@ -98,16 +99,28 @@ const SearchForm = (props: SearchFormProps = {}) => { const handleReset = () => { const resetValues: SearchFormValues = { - sido: "", - gugun: "", + sido: '', + gugun: '', year: String(defaultYear), - order: "", - fctName: "", + order: '', + fctName: '', + bsnmCd: '', }; setValues(resetValues); props.onReset?.(); }; + const handleSidoChange = (value: string) => { + const selectedBsnm = sidoList?.data.find((v) => v.sido === value)?.bsnmCd || ''; + + setValues((prev) => ({ + ...prev, + sido: value, + gugun: '', + bsnmCd: selectedBsnm, + })); + }; + return (
@@ -117,7 +130,7 @@ const SearchForm = (props: SearchFormProps = {}) => { - diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index 0a6fc37b9..16c0680be 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -793,6 +793,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r editable: col.editable, valueType: col.valueType, dataFormat: col.dataFormat, + ...(col.styleName != null && { styleName: col.styleName }), ...(col.mergeCells && { mergeRule: { criteria: 'value' as const } }), }; diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 047f241b8..b0069a82a 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -34,7 +34,7 @@ const SeweragePenetrationRateInp = () => { const response = await statisticsInputAPi.getSewerageSupplyRateList({ sido: values.sido, gugun: values.gugun === 'ALL' ? '' : values.gugun, - bsnmCd: '4139000000', + bsnmCd: values.bsnmCd, yr: values.year, demoYr: values.year, }); diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index efaccaf0d..4d31facbe 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -10,6 +10,7 @@ export type RealGridColumnInfo = { editable?: boolean; valueType?: import("realgrid").ValueType; dataFormat?: string; + styleName?: string; mergeRule?: { criteria: 'value' }; footer?: { text?: string; diff --git a/apps/st/src/components/type/SearchForm.type.ts b/apps/st/src/components/type/SearchForm.type.ts index 157c57ec3..689bee00f 100644 --- a/apps/st/src/components/type/SearchForm.type.ts +++ b/apps/st/src/components/type/SearchForm.type.ts @@ -4,6 +4,7 @@ export type SearchFormValues = { year?: string; order?: string; fctName?: string; + bsnmCd?: string; }; export type SearchFormProps = { diff --git a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts index acb635784..f9833685b 100644 --- a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts @@ -1,5 +1,5 @@ import { ValueType } from "realgrid"; -import { footerIntArrayByDivision, footerDecimal3ArrayByDivision } from "@/components/util/grid/footerSummary"; +import { footerIntArrayByDivision, footerDecimal3ArrayByDivision, footerAvgDecimalArrayByDivision } from "@/components/util/grid/footerSummary"; import type { GridView, DataColumn, ColumnFooter } from "@/components/type/DataGrid.type"; // 하수도 통계 관리 01. 하수도 보급률 @@ -166,9 +166,9 @@ export const SeweragePenetrationRateInfoList = { { name: 'town_swtt_popltn', fieldName: '오수처리인구', header: { text: '오수처리인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'town_wrrtn_popltn', fieldName: '정화조인구', header: { text: '정화조인구' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'swge_proc_zone_outer_ar', fieldName: 'swge_proc_zone_outer_ar', header: { text: '면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: 'pblswrg_popltn_suprt', fieldName: '공공하수처리구역인구보급율(%)', header: { text: '공공하수처리구역인구보급율(%)' } }, - { name: 'adtrt_popltn_suprt', fieldName: '고도처리인구보급율(%)', header: { text: `고도처리인구보급율(%)` } }, - { name: 'swrg_instlrt', fieldName: '하수도설치율(%)', header: { text: '하수도설치율(%)' } }, + { name: 'pblswrg_popltn_suprt', fieldName: '공공하수처리구역인구보급율(%)', header: { text: '공공하수처리구역인구보급율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'adtrt_popltn_suprt', fieldName: '고도처리인구보급율(%)', header: { text: `고도처리인구보급율(%)` }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, + { name: 'swrg_instlrt', fieldName: '하수도설치율(%)', header: { text: '하수도설치율(%)' }, footer: footerAvgDecimalArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, { name: 'rmk', fieldName: '비고', header: { text: '비고' } }, ], columnLayout: [ diff --git a/apps/st/src/components/util/grid/footerSummary.ts b/apps/st/src/components/util/grid/footerSummary.ts index 3ae0b0982..cbf60db84 100644 --- a/apps/st/src/components/util/grid/footerSummary.ts +++ b/apps/st/src/components/util/grid/footerSummary.ts @@ -107,6 +107,55 @@ export const getFooterSummaryByDivision = ( return sum; }; +// "구분" 필드 값에 따라 필터링하여 평균 계산하는 함수 +// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값 +export const getFooterAvgSummaryByDivision = ( + grid: GridView, + column: DataColumn, + divisionIndex: number, + divisionFieldName: string = '구분' +) => { + const dataSource = grid.getDataSource(); + const rowCount = dataSource.getRowCount(); + let sum = 0; + let count = 0; + + // "구분" 필드의 고유값 찾기 + const divisionValues = getDivisionValues(grid, divisionFieldName); + if (divisionValues.length <= divisionIndex) { + return 0; // 해당 인덱스의 고유값이 없으면 0 반환 + } + + const targetDivision = divisionValues[divisionIndex]; + + for (let i = 0; i < rowCount; i++) { + // "구분" 필드 값 확인 + const divisionValue = dataSource.getValue(i, divisionFieldName); + if (String(divisionValue) !== targetDivision) { + continue; // 해당 "구분" 값이 아니면 스킵 + } + + const cellValue = dataSource.getValue(i, column.fieldName); + + // 문자열인 경우 숫자로 변환 + const numericValue = typeof cellValue === 'string' + ? Number(String(cellValue).replace(/,/g, '')) + : Number(cellValue); + + if (!isNaN(numericValue) && numericValue !== null && numericValue !== undefined) { + sum += numericValue; + count += 1; + } + } + + if (count === 0) { + return 0; + } + + const avg = sum / count; + return Math.floor(avg * 10) / 10; +}; + // Footer 설정 객체 생성 함수 const createSumFooter = (format: string) => ({ expression: 'sum' as const, @@ -156,6 +205,38 @@ const createSumFooterArrayByDivision = (format: string) => [ }, ]; +// "구분" 값에 따라 필터링하는 평균 Footer 배열 생성 함수 (2줄) +const createAvgFooterArrayByDivision = (format: string) => [ + { + numberFormat: format, + valueCallback: ( + grid: GridView, + column: DataColumn, + footerIndex: number, + columnFooter: unknown, + value: number | string | undefined + ) => { + // footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값) + // footerIndex를 divisionIndex로 사용 + return getFooterAvgSummaryByDivision(grid, column, footerIndex); + }, + }, + { + numberFormat: format, + valueCallback: ( + grid: GridView, + column: DataColumn, + footerIndex: number, + columnFooter: unknown, + value: number | string | undefined + ) => { + // footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값) + // footerIndex를 divisionIndex로 사용 + return getFooterAvgSummaryByDivision(grid, column, footerIndex); + }, + }, +]; + // 1. 정수형 : #,##0 export const footerInt = createSumFooter('#,##0'); @@ -171,12 +252,17 @@ export const footerDecimal3 = createSumFooter('#,##0.000'); // "구분" 값에 따라 필터링하는 Footer 배열 (정수형, 2줄) export const footerIntArrayByDivision = createSumFooterArrayByDivision('#,##0'); +// "구분" 값에 따라 필터링하는 Footer 배열 (소수점 1자리, 2줄) export const footerDecimal1ArrayByDivision = createSumFooterArrayByDivision('#,##0.0'); +// "구분" 값에 따라 필터링하는 Footer 배열 (소수점 2자리, 2줄) export const footerDecimal2ArrayByDivision = createSumFooterArrayByDivision('#,##0.00'); // "구분" 값에 따라 필터링하는 Footer 배열 (소수점 3자리, 2줄) export const footerDecimal3ArrayByDivision = createSumFooterArrayByDivision('#,##0.000'); // 률(%) 평균 값(기본 소수점 1자리) -export const footerAvgDecimal = createAvgFooter('#,##0.0'); \ No newline at end of file +export const footerAvgDecimal = createAvgFooter('#,##0.0'); + +// "구분" 값에 따라 필터링하는 평균 Footer 배열 (소수점 1자리, 2줄) +export const footerAvgDecimalArrayByDivision = createAvgFooterArrayByDivision('#,##0.0'); \ No newline at end of file diff --git a/apps/st/src/entities/statistics/model/statistics-interface.ts b/apps/st/src/entities/statistics/model/statistics-interface.ts index ee956bdd0..5ff8a5ed7 100644 --- a/apps/st/src/entities/statistics/model/statistics-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-interface.ts @@ -1,11 +1,13 @@ export interface Sido { adstDstrCd: string; sido: string; + bsnmCd: string; } export interface Sgg { adstDstrCd: string; gugun: string; + bsnmCd: string; } export interface ApiResponse {