From 9eb40c46e52a0b07d58b76c510a54238e67882b5 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Tue, 3 Feb 2026 15:29:45 +0900 Subject: [PATCH 1/6] =?UTF-8?q?st:=20fix:=20cell=20style=20=EC=86=8D?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80,=20grid=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B3=80=ED=99=98=20=EA=B3=B5=ED=86=B5=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/module/CompactDataGrid.tsx | 5 +++++ apps/st/src/components/module/DataGrid.tsx | 5 +++++ apps/st/src/components/module/TabDataGrid.tsx | 5 +++++ .../input/SeweragePenetrationRateInp.tsx | 12 +---------- apps/st/src/components/type/DataGrid.type.ts | 2 ++ .../input/SeweragePenetrationRateInpColumn.ts | 21 ++++++++++++------- .../SeweragePenetrationRateRvwColumn.ts | 2 +- .../src/components/util/grid/footerSummary.ts | 4 ++++ .../src/components/util/grid/gridFunction.ts | 11 ++++++++++ apps/st/src/shared/config/api.ts | 2 +- .../rsone/components/grid/css/DataGrid.css | 10 ++++++++- 11 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 apps/st/src/components/util/grid/gridFunction.ts diff --git a/apps/st/src/components/module/CompactDataGrid.tsx b/apps/st/src/components/module/CompactDataGrid.tsx index 5eb83dfce..04f7cc0b0 100644 --- a/apps/st/src/components/module/CompactDataGrid.tsx +++ b/apps/st/src/components/module/CompactDataGrid.tsx @@ -270,6 +270,11 @@ const CompactDataGrid = ({ columnInfo, fixedColumns, showHeaderCheckbox = false, {...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })} header={column.header} editable={column.editable} + {...(column.styleName != null + ? { styleName: column.styleName } + : column.editable === false + ? { styleName: 'rg-cell-readonly' as const } + : {})} {...(column.visible === false && { visible: false })} footer={column.footer && !Array.isArray(column.footer) ? { text: column.footer.text, diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index b97e43345..ebab75ca8 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -706,6 +706,11 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport {...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })} header={column.header} editable={column.editable} + {...(column.styleName != null + ? { styleName: column.styleName } + : column.editable === false + ? { styleName: 'rg-cell-readonly' as const } + : {})} {...(column.visible === false && { visible: false })} footer={column.footer && !Array.isArray(column.footer) ? { text: column.footer.text, diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index 07dbaeef3..0a6fc37b9 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -940,6 +940,11 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, isExcelExport = true, r {...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.00' })} header={column.header} editable={column.editable} + {...(column.styleName != null + ? { styleName: column.styleName } + : column.editable === false + ? { styleName: 'rg-cell-readonly' as const } + : {})} {...(column.visible === false && { visible: false })} footer={column.footer && !Array.isArray(column.footer) ? { text: (column.footer as { text?: string }).text, diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 795722c0f..047f241b8 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -14,17 +14,7 @@ import { statisticsInputAPi } from "@/entities/statistics/api/statistics-input-s import type { SearchFormValues } from "@/components/type/SearchForm.type"; import type { SewerageSupplyRateItem } from "@/entities/statistics/model/statistics-input-interface"; import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn"; - -// API 응답 항목을 DataGrid row 타입으로 변환 (null/undefined → 빈 문자열) -function toGridRows(items: SewerageSupplyRateItem[]): Record[] { - return items.map((item) => { - const row: Record = {}; - for (const [key, value] of Object.entries(item)) { - row[key] = value === null || value === undefined ? "" : value; - } - return row; - }); -} +import { toGridRows } from "@/components/util/grid/gridFunction"; // 하수도 통계 관리 > 01. 하수도 보급률 const SeweragePenetrationRateInp = () => { diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index 2574b4d1b..efaccaf0d 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -84,6 +84,8 @@ export type ColumnConfig = { editable?: boolean; // 셀 편집 가능 여부 /** 그리드에서 컬럼 숨김 (수식용 필드 등) */ visible?: boolean; + /** 셀에 적용할 CSS 클래스명 (예: 'rg-cell-readonly', 'rg-cell-total') */ + styleName?: string; mergeCells?: boolean; sortdisable?: boolean; sortPreserveGroupOrder?: SortPreserveGroupOrder; diff --git a/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts b/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts index 50125939e..e2cb0ca12 100644 --- a/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts +++ b/apps/st/src/components/util/column/input/SeweragePenetrationRateInpColumn.ts @@ -46,7 +46,8 @@ export const SeweragePenetrationRateInpColumn = { editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], type: 'sum' as const } + computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], type: 'sum' as const }, + styleName: 'rg-cell-total', }, // 하수처리구역 내 > 공공하수처리인구 @@ -58,7 +59,8 @@ export const SeweragePenetrationRateInpColumn = { editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'], type: 'sum' as const } + computed: { fields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt'], 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' }, @@ -73,7 +75,8 @@ export const SeweragePenetrationRateInpColumn = { editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], type: 'sum' as const } + computed: { fields: ['wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt'], 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' }, @@ -94,7 +97,8 @@ export const SeweragePenetrationRateInpColumn = { editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', - computed: { fields: ['townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'sum' as const } + computed: { fields: ['townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], 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' }, @@ -114,7 +118,8 @@ export const SeweragePenetrationRateInpColumn = { numeratorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'pblswrgPopltnSuprtRate' as const - } + }, + styleName: 'rg-cell-total', }, // 고도처리 인구보급률 (고도처리 인구 / 전체 인구 * 100) @@ -130,7 +135,8 @@ export const SeweragePenetrationRateInpColumn = { numeratorFields: ['pblswrgTertTrt', 'wstrTertTrt'], denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn', 'townUnprocPopltn', 'townSwttPopltn', 'townWrrtnPopltn'], type: 'adtrtPopltnSuprtRate' as const - } + }, + styleName: 'rg-cell-total', }, // 하수도 설치율 @@ -147,7 +153,8 @@ export const SeweragePenetrationRateInpColumn = { denominatorFields: ['pblswrgPrmTrt', 'pblswrgScdTrt', 'pblswrgTertTrt', 'wstrPrmTrt', 'wstrScdTrt', 'wstrTertTrt', 'notConectPopltn'], thirdFields: ['piplSupprt'], type: 'swrgInstlrtRate' as const - } + }, + styleName: 'rg-cell-total', }, // 비고 diff --git a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts index 968c2732f..acb635784 100644 --- a/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SeweragePenetrationRateRvwColumn.ts @@ -147,7 +147,7 @@ export const SeweragePenetrationRateInfoList = { { name: 'specl_cntrpln_ar', fieldName: '특별대책지역', header: { text: '특별대책지역' } }, { name: 'total_popl', fieldName: '총인구', header: { text: '총인구' }, footer: footerIntArrayByDivision, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'ar_sm', fieldName: '총면적(㎢)', header: { text: '총면적(㎢)' }, footer: footerDecimal3ArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0.000' }, - { name: '계', fieldName: '계', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: '계', fieldName: '계', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-total' }, //공공하수처리인구 { name: '합계', fieldName: '합계', header: { text: '합계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: '1차처리', fieldName: '1차처리', header: { text: '1차처리' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, diff --git a/apps/st/src/components/util/grid/footerSummary.ts b/apps/st/src/components/util/grid/footerSummary.ts index 6364454eb..3ae0b0982 100644 --- a/apps/st/src/components/util/grid/footerSummary.ts +++ b/apps/st/src/components/util/grid/footerSummary.ts @@ -171,6 +171,10 @@ export const footerDecimal3 = createSumFooter('#,##0.000'); // "구분" 값에 따라 필터링하는 Footer 배열 (정수형, 2줄) export const footerIntArrayByDivision = createSumFooterArrayByDivision('#,##0'); +export const footerDecimal1ArrayByDivision = createSumFooterArrayByDivision('#,##0.0'); + +export const footerDecimal2ArrayByDivision = createSumFooterArrayByDivision('#,##0.00'); + // "구분" 값에 따라 필터링하는 Footer 배열 (소수점 3자리, 2줄) export const footerDecimal3ArrayByDivision = createSumFooterArrayByDivision('#,##0.000'); diff --git a/apps/st/src/components/util/grid/gridFunction.ts b/apps/st/src/components/util/grid/gridFunction.ts new file mode 100644 index 000000000..173a7ac6d --- /dev/null +++ b/apps/st/src/components/util/grid/gridFunction.ts @@ -0,0 +1,11 @@ +import type { SewerageSupplyRateItem } from "@/entities/statistics/model/statistics-input-interface"; + +export function toGridRows(items: SewerageSupplyRateItem[]): Record[] { + return items.map((item) => { + const row: Record = {}; + for (const [key, value] of Object.entries(item)) { + row[key] = value === null || value === undefined ? "" : value; + } + return row; + }); +}; \ No newline at end of file diff --git a/apps/st/src/shared/config/api.ts b/apps/st/src/shared/config/api.ts index 90e5a6cba..1fd2505c3 100644 --- a/apps/st/src/shared/config/api.ts +++ b/apps/st/src/shared/config/api.ts @@ -65,7 +65,7 @@ export const ENDPOINT = { POST_EVALUATION: "/board-service/api/v1/pstEvl", }, ST_SERVICE: { - COMMON: "/st-input-service/api/v1/common", + COMMON: "/st-management-service/api/v1/common", INPUT: "/st-input-service/api/v1/input", } }; diff --git a/packages/rsone/components/grid/css/DataGrid.css b/packages/rsone/components/grid/css/DataGrid.css index b7d8352da..4469b8490 100644 --- a/packages/rsone/components/grid/css/DataGrid.css +++ b/packages/rsone/components/grid/css/DataGrid.css @@ -101,5 +101,13 @@ border-right: 1px solid #ccc !important; } +/* 읽기 전용 cell */ +.rg-cell-readonly { + background-color: #f0f0f0 !important; + color: #666 !important; +} - +/* 소계, 합계, 율(%) cell */ +.rg-cell-total { + background-color: #e3eaf2 !important; +} \ No newline at end of file From 830cbf97d1b4d65075b8ae0558c2c3f29bb26666 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Tue, 3 Feb 2026 16:46:06 +0900 Subject: [PATCH 2/6] =?UTF-8?q?st:=20refactor:=20=EA=B5=AC=EB=B6=84=20?= =?UTF-8?q?=EB=B3=84=20footer=20=ED=8F=89=EA=B7=A0=20=EA=B0=92=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SeweragePenetrationRateRvwColumn.ts | 8 +- .../src/components/util/grid/footerSummary.ts | 88 ++++++++++++++++++- 2 files changed, 91 insertions(+), 5 deletions(-) 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 From 4ee12d127d74603b63bab6553c176d4019d3e38e Mon Sep 17 00:00:00 2001 From: rsone001 Date: Tue, 3 Feb 2026 17:06:11 +0900 Subject: [PATCH 3/6] =?UTF-8?q?st:=20refactor:=20cell=20=EC=83=89=EC=83=81?= =?UTF-8?q?=20=EC=86=8D=EC=84=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/CompactDataGrid.tsx | 1 + apps/st/src/components/module/DataGrid.tsx | 1 + apps/st/src/components/module/TabDataGrid.tsx | 1 + apps/st/src/components/type/DataGrid.type.ts | 1 + 4 files changed, 4 insertions(+) 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/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/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; From 6f07aca860ed9204c7fa553acc893103196bf305 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Tue, 3 Feb 2026 18:01:16 +0900 Subject: [PATCH 4/6] =?UTF-8?q?st:=20refactor:=20=EC=8B=9C=EA=B5=B0?= =?UTF-8?q?=EA=B5=AC=20=EA=B2=80=EC=83=89=20=EC=8B=9C=20bsnmCd=EB=8F=84=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/SearchForm.tsx | 29 ++++++++++++++----- .../input/SeweragePenetrationRateInp.tsx | 2 +- .../st/src/components/type/SearchForm.type.ts | 1 + .../statistics/model/statistics-interface.ts | 2 ++ 4 files changed, 25 insertions(+), 9 deletions(-) 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/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/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/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 { From 42d6010efaf62fbc3b138ce719b3bd4f2632e04e Mon Sep 17 00:00:00 2001 From: rsone001 Date: Wed, 4 Feb 2026 15:21:38 +0900 Subject: [PATCH 5/6] =?UTF-8?q?st:=20refactor:=20=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=83=89=EC=97=B0=EB=8F=84=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20feat:=20=ED=95=98=EC=88=98?= =?UTF-8?q?=EB=8F=84=EB=B3=B4=EA=B8=89=EB=A5=A0=20=EC=A0=84=EB=85=84?= =?UTF-8?q?=EB=8F=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9E=85=EB=A0=A5=20=EA=B8=B0=ED=95=9C=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=B0=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EC=85=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20or=20=EC=88=98=EC=A0=95=20=EB=B6=88?= =?UTF-8?q?=EA=B0=80=20=EB=A1=9C=EC=A7=81=20=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/SearchForm.tsx | 20 +-- .../input/SeweragePenetrationRateInp.tsx | 125 ++++++++++++------ .../st/src/components/type/SearchForm.type.ts | 3 +- .../api/statistics-input-service.ts | 9 ++ .../statistics/api/statistics-service.ts | 8 +- .../model/statistics-input-interface.ts | 13 +- apps/st/src/shared/config/api.ts | 1 + 7 files changed, 123 insertions(+), 56 deletions(-) diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index 530b3579b..bd15477f8 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import useSWR from 'swr'; import { @@ -18,9 +18,9 @@ from "@common/components"; import { useAlertStore } from "@/shared/lib"; import { type SearchFormProps, type SearchFormValues, defaultSearchFormProps } from "../type/SearchForm.type"; import { statisticsAPi } from "@/entities/statistics/api/statistics-service"; -import type { ApiResponse, Sido, Sgg } from "@/entities/statistics/model/statistics-interface"; +import type { ApiResponse, SearchCondition, Sgg } from "@/entities/statistics/model/statistics-interface"; -const getSidoListFetcher = async (): Promise> => { +const getSidoListFetcher = async (): Promise> => { return statisticsAPi.getSidoList(); }; @@ -36,8 +36,6 @@ const SearchForm = (props: SearchFormProps = {}) => { const defaultYear = currentYear - 1; const startYear = currentYear - 10; - const yearOptions = Array.from({ length: currentYear - startYear + 1 }, (_, i) => currentYear - i); - const [values, setValues] = useState({ sido: props.defaultValues?.sido || "", gugun: props.defaultValues?.gugun || '', @@ -47,7 +45,11 @@ const SearchForm = (props: SearchFormProps = {}) => { bsnmCd: props.defaultValues?.bsnmCd || '', }); - const { data: sidoList } = useSWR>( + useEffect(() => { + props.onValuesChange?.(values); + }, [values]); + + const { data: sidoList } = useSWR>( "sido-list", getSidoListFetcher, { @@ -111,7 +113,7 @@ const SearchForm = (props: SearchFormProps = {}) => { }; const handleSidoChange = (value: string) => { - const selectedBsnm = sidoList?.data.find((v) => v.sido === value)?.bsnmCd || ''; + const selectedBsnm = sidoList?.data.sidoList.find((v) => v.sido === value)?.bsnmCd || ''; setValues((prev) => ({ ...prev, @@ -136,7 +138,7 @@ const SearchForm = (props: SearchFormProps = {}) => { 선택 - {sidoList?.data?.map((sido) => ( + {sidoList?.data?.sidoList?.map((sido) => ( {sido.sido} @@ -175,7 +177,7 @@ const SearchForm = (props: SearchFormProps = {}) => { - {yearOptions.map((year) => ( + {sidoList?.data?.yearList.map((year) => ( {year}년 diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index b0069a82a..24a88436c 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useMemo } from "react"; import SearchForm from "@/components/module/SearchForm"; // import { DataGrid } from "@rsone/components"; @@ -12,47 +12,63 @@ import { LoadingWithAnimation } from "@common/components"; import { useAlertStore } from "@/shared/lib"; import { statisticsInputAPi } from "@/entities/statistics/api/statistics-input-service"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import type { SewerageSupplyRateItem } from "@/entities/statistics/model/statistics-input-interface"; +import type { + SewerageSupplyRateItem, + SewerageSupplyRateRequestParams, +} from "@/entities/statistics/model/statistics-input-interface"; import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +function getSewerageSupplyRateParams( + values: SearchFormValues, + overrides?: { yr?: string; demoYr?: string; mode?: string } +): SewerageSupplyRateRequestParams { + return { + sido: values.sido, + gugun: values.gugun === 'ALL' ? '' : values.gugun, + bsnmCd: values.bsnmCd, + yr: overrides?.yr ?? values.year, + demoYr: overrides?.demoYr ?? values.year, + ...(overrides?.mode && { mode: overrides.mode }), + }; +} + // 하수도 통계 관리 > 01. 하수도 보급률 const SeweragePenetrationRateInp = () => { const { setMessage: alert } = useAlertStore((state) => state); const [rowData, setRowData] = useState[]>([]); const [isLoading, setIsLoading] = useState(false); + const [searchValues, setSearchValues] = useState(null); + const [isEnterable, setIsEnterable] = useState(true); + + const columnInfo = useMemo(() => { + const { columns, columnLayout } = SeweragePenetrationRateInpColumn; + return { + columnLayout, + columns: isEnterable + ? columns + : columns.map((col) => ({ ...col, editable: false as const })), + }; + }, [isEnterable]); const handleReset = () => { setRowData([]); }; - const handleSearch = async (values: SearchFormValues) => { + const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams) => { try { - if (values.sido !== '' && values.sido !== 'ALL') { - setIsLoading(true); - - const response = await statisticsInputAPi.getSewerageSupplyRateList({ - sido: values.sido, - gugun: values.gugun === 'ALL' ? '' : values.gugun, - bsnmCd: values.bsnmCd, - yr: values.year, - demoYr: values.year, - }); - - if (response.status === 'success' && response.data) { - const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d) => ({ - ...d, - speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr, - })); - - setRowData(toGridRows(convertedData)); - } else { - alert(response.message); - } + setIsLoading(true); + const response = await statisticsInputAPi.getSewerageSupplyRateList(params); + if (response.status === 'success' && response.data) { + const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d) => ({ + ...d, + speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr, + })); + setRowData(toGridRows(convertedData)); } else { - alert('시도를 선택해주세요.'); + alert(response.message); } - } catch (error) { + } catch { alert('데이터 조회 중 오류가 발생했습니다.'); setRowData([]); } finally { @@ -60,12 +76,40 @@ const SeweragePenetrationRateInp = () => { } }; - const SubmitExceptionFunc = () => { - // 예외 제출 로직 + const handleSearch = async (values: SearchFormValues) => { + if (values.sido === '' || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + + try { + const response = await statisticsInputAPi.checkInputPeriod(searchValues?.year || ''); + + if (response.status === 'success') { + setIsEnterable(response.data); + + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values)); + } else { + alert(response.message); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } }; - const getLastYearDataFunc = () => { - // 지난연도 데이터 가져오기 + const SubmitExceptionFunc = () => { + // 예외 제출 로직 + }; + + const getLastYearDataFunc = async (values: SearchFormValues) => { + if (values.sido === '' || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + const prevYear = (Number(values.year) - 1).toString(); + await fetchSewerageSupplyRate( + getSewerageSupplyRateParams(values, { yr: prevYear, demoYr: prevYear, mode: 'PRE' }) + ); }; const submitFunc = () => { @@ -78,22 +122,25 @@ const SeweragePenetrationRateInp = () => { return (
- -
-
- - - - + + {rowData.length > 0 && +
+
+ + getLastYearDataFunc(searchValues ?? {})} /> + + +
-
+ } {isLoading &&
} void; onReset?: () => void; + onValuesChange?: (values: SearchFormValues) => void; defaultValues?: SearchFormValues; }; -export const defaultSearchFormProps: Required> = { +export const defaultSearchFormProps: Required> = { sido: true, gugun: true, year: true, 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 ca5d4e102..32cae5723 100644 --- a/apps/st/src/entities/statistics/api/statistics-input-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-input-service.ts @@ -5,6 +5,7 @@ import { type StatisticsInputService, type SewerageSupplyRateRequestParams, type SewerageSupplyRateApiResponse, + type CheckInputPeriodResponse, } from "../model/statistics-input-interface"; export class StatisticsInputAPiService implements StatisticsInputService { @@ -18,6 +19,14 @@ export class StatisticsInputAPiService implements StatisticsInputService { return response.data; } + + public async checkInputPeriod(params: string): Promise { + const response = await axios.get ( + `${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period?year=${params}`, + ); + + return response.data; + } } export const statisticsInputAPi = new StatisticsInputAPiService(); \ No newline at end of file diff --git a/apps/st/src/entities/statistics/api/statistics-service.ts b/apps/st/src/entities/statistics/api/statistics-service.ts index dfec7d694..8455e68c5 100644 --- a/apps/st/src/entities/statistics/api/statistics-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-service.ts @@ -3,16 +3,16 @@ import { ENDPOINT } from "@/shared/config"; import { type StatisticsService, - type Sido, + type SearchCondition, type ApiResponse, type Sgg } from "../model/statistics-interface"; export class StatisticsAPiService implements StatisticsService { - public async getSidoList(): Promise> { - const response = await axios.get>( - `${ENDPOINT.ST_SERVICE.COMMON}/sido`, + public async getSidoList(): Promise> { + const response = await axios.get>( + `${ENDPOINT.ST_SERVICE.COMMON}/search-condition`, ); return response.data; 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 111216424..6a76d567e 100644 --- a/apps/st/src/entities/statistics/model/statistics-input-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-input-interface.ts @@ -1,6 +1,6 @@ import type { ApiResponse } from "./statistics-interface"; -// 하수도 보급률 API 응답의 상태 정보 +// 하수도 보급률 데이터 항목 export interface SewerageSupplyRateStatusInfo { locgStatus: string; locgDt: string; @@ -13,7 +13,6 @@ export interface SewerageSupplyRateStatusInfo { returnOpinionSm: string | null; } -// 하수도 보급률 데이터 항목 export interface SewerageSupplyRateItem { sido: string; gugun: string; @@ -103,7 +102,7 @@ export interface SewerageSupplyRateResponseData { export type SewerageSupplyRateApiResponse = ApiResponse & { message: string; -}; +} export interface SewerageSupplyRateRequestParams { sido?: string; @@ -111,6 +110,14 @@ export interface SewerageSupplyRateRequestParams { bsnmCd?: string; yr?: string; demoYr?: string; + mode?: string; +} + +// 하수도 통계 입력 기간 조회 +export interface CheckInputPeriodResponse { + status: string; + message: string; + data: boolean; } export interface StatisticsInputService { diff --git a/apps/st/src/shared/config/api.ts b/apps/st/src/shared/config/api.ts index 1fd2505c3..cd1527192 100644 --- a/apps/st/src/shared/config/api.ts +++ b/apps/st/src/shared/config/api.ts @@ -67,6 +67,7 @@ export const ENDPOINT = { ST_SERVICE: { COMMON: "/st-management-service/api/v1/common", INPUT: "/st-input-service/api/v1/input", + INPUTCOMMON: "/st-input-service/api/v1/common", } }; From eca74c1f2ec48d7c80be3bcb8002ffd69702ff2d Mon Sep 17 00:00:00 2001 From: rsone001 Date: Wed, 4 Feb 2026 17:54:02 +0900 Subject: [PATCH 6/6] =?UTF-8?q?st:=20refactor:=20=EC=97=B0=EB=8F=84=20&=20?= =?UTF-8?q?=EC=A0=84=EB=85=84=EB=8F=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20&=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=93=9C=20=EB=82=B4=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20editable?= =?UTF-8?q?=20=ED=8C=90=EB=B3=84=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/SearchForm.tsx | 29 ++++++++--------- .../input/SeweragePenetrationRateInp.tsx | 32 +++++++++++++------ 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index bd15477f8..0b50ecf89 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -31,23 +31,9 @@ const getSggListFetcher = async (key: [string, string]): Promise { const { setMessage: alert } = useAlertStore((state) => state); const mergedProps = { ...defaultSearchFormProps, ...props }; - + const currentYear = new Date().getFullYear(); const defaultYear = currentYear - 1; - const startYear = currentYear - 10; - - const [values, setValues] = useState({ - sido: props.defaultValues?.sido || "", - gugun: props.defaultValues?.gugun || '', - year: props.defaultValues?.year || String(defaultYear), - order: props.defaultValues?.order || '', - fctName: props.defaultValues?.fctName || '', - bsnmCd: props.defaultValues?.bsnmCd || '', - }); - - useEffect(() => { - props.onValuesChange?.(values); - }, [values]); const { data: sidoList } = useSWR>( "sido-list", @@ -71,6 +57,15 @@ const SearchForm = (props: SearchFormProps = {}) => { } ); + const [values, setValues] = useState({ + sido: props.defaultValues?.sido || '', + gugun: props.defaultValues?.gugun || '', + year: props.defaultValues?.year || defaultYear.toString(), + order: props.defaultValues?.order || '', + fctName: props.defaultValues?.fctName || '', + bsnmCd: props.defaultValues?.bsnmCd || '', + }); + const { data: sggList } = useSWR>( values.sido && values.sido !== "ALL" ? ["sgg-list", values.sido] : null, getSggListFetcher, @@ -123,6 +118,10 @@ const SearchForm = (props: SearchFormProps = {}) => { })); }; + useEffect(() => { + props.onValuesChange?.(values); + }, [values]); + return (
diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 24a88436c..7644ab1e3 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -40,7 +40,10 @@ const SeweragePenetrationRateInp = () => { const [isLoading, setIsLoading] = useState(false); const [searchValues, setSearchValues] = useState(null); const [isEnterable, setIsEnterable] = useState(true); + const [lastSearchedYear, setLastSearchedYear] = useState(null); + + // 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요. const columnInfo = useMemo(() => { const { columns, columnLayout } = SeweragePenetrationRateInpColumn; return { @@ -53,6 +56,7 @@ const SeweragePenetrationRateInp = () => { const handleReset = () => { setRowData([]); + setLastSearchedYear(null); }; const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams) => { @@ -64,6 +68,7 @@ const SeweragePenetrationRateInp = () => { ...d, speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr, })); + setRowData(toGridRows(convertedData)); } else { alert(response.message); @@ -83,10 +88,11 @@ const SeweragePenetrationRateInp = () => { } try { - const response = await statisticsInputAPi.checkInputPeriod(searchValues?.year || ''); + const response = await statisticsInputAPi.checkInputPeriod(values.year || ''); if (response.status === 'success') { setIsEnterable(response.data); + setLastSearchedYear(values.year || null); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values)); } else { @@ -106,10 +112,14 @@ const SeweragePenetrationRateInp = () => { alert('시도를 선택해주세요.'); return; } - const prevYear = (Number(values.year) - 1).toString(); - await fetchSewerageSupplyRate( - getSewerageSupplyRateParams(values, { yr: prevYear, demoYr: prevYear, mode: 'PRE' }) - ); + + // 실제 조회된 연도가 없으면 실행 불가 + if (!lastSearchedYear) { + alert('먼저 데이터를 조회해주세요.'); + return; + } + + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' })); }; const submitFunc = () => { @@ -126,10 +136,13 @@ const SeweragePenetrationRateInp = () => { {rowData.length > 0 &&
- - getLastYearDataFunc(searchValues ?? {})} /> - - + {isEnterable && } + {isEnterable && getLastYearDataFunc(searchValues ?? {})} />} + {isEnterable && } + { + // 최고 관리자일땐 저장 가능하게 + + }
} @@ -141,6 +154,7 @@ const SeweragePenetrationRateInp = () => { 0) && isEnterable} rowStartNum={3} fixedColumns={1} rowData={rowData}