From 32ad548de21782e68410dc40cbdcad25d679e0f5 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Tue, 30 Dec 2025 17:55:05 +0900 Subject: [PATCH] =?UTF-8?q?st:=20refactor:=20DataGrid=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/button/OperateDataButton.tsx | 16 +++ apps/st/src/components/module/DataGrid.tsx | 91 +++++++++-------- apps/st/src/components/module/TabDataGrid.tsx | 99 ++++++++++++++++--- .../ExcretaTreatmentFacilityMng.tsx | 5 +- .../management/PublicSewageFacilityMng.tsx | 17 ++-- .../management/PumpingStationMng.tsx | 5 +- .../management/SeweragePenetrationRateMng.tsx | 5 +- .../management/StatsInputSatusMng.tsx | 5 +- .../review/ExcretaTreatmentFacilityRvw.tsx | 5 +- .../review/PublicSewageFacilityRvw.tsx | 5 +- .../statistics/review/PumpingStationRvw.tsx | 5 +- .../review/SeweragePenetrationRateRvw.tsx | 17 +++- .../statistics/review/StatsInputSatusRvw.tsx | 5 +- .../total/SeweragePenetrationRateTot.tsx | 5 +- apps/st/src/components/type/DataGrid.type.ts | 17 +++- 15 files changed, 227 insertions(+), 75 deletions(-) create mode 100644 apps/st/src/components/button/OperateDataButton.tsx diff --git a/apps/st/src/components/button/OperateDataButton.tsx b/apps/st/src/components/button/OperateDataButton.tsx new file mode 100644 index 000000000..11f422f6e --- /dev/null +++ b/apps/st/src/components/button/OperateDataButton.tsx @@ -0,0 +1,16 @@ +import { Button } from "@common/components"; + +interface OperateDataButtonProps { + getOperateDataFunc: () => void; +} + +const OperateDataButton = ({ getOperateDataFunc }: OperateDataButtonProps) => { + return ( + + ); +}; + +export default OperateDataButton; \ No newline at end of file diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index 2365d3add..1e518c1f8 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect, useState } from "react" +import { useRef, useEffect } from "react" import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid" import { RealGridReact, RGDataColumn } from "realgrid-react" @@ -13,9 +13,10 @@ import { useTabStore } from "@/store/tab-store" // RealGrid 타입 확장 declare module "realgrid" { interface GridView { - setOptions(options: { + setOptions(options: { footer?: { visible?: boolean }; summaryMode?: 'aggregate' | 'statistical' | 'none'; + checkBar?: { visible?: boolean }; }): void; } } @@ -34,7 +35,7 @@ if (typeof window !== "undefined") { RealGrid.setLicenseKey(process.env.NEXT_PUBLIC_ST_REALGRID_KEY || ''); -const DataGrid = ({ columnInfo, rows = [], rowStartNum, isExcelImport = true, fixedColumns }: DataGridProps) => { +const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, fixedColumns, onRowsChange, showHeaderCheckbox = false, rowData, setRowData }: DataGridProps) => { const { columns, columnLayout } = columnInfo; const gridRef = useRef(null); const fileInputRef = useRef(null); @@ -42,29 +43,6 @@ const DataGrid = ({ columnInfo, rows = [], rowStartNum, isExcelImport = true, fi const activeTabId = useTabStore((state) => state.activeTabId); const menuList = useTabStore((state) => state.menuList); - const [rowData, setRowData] = useState[]>([]); - - // footer가 있는 컬럼의 데이터를 숫자로 변환 - const convertNumericData = (data: Record[]): Record[] => { - const footerColumns = columns.filter(col => col.footer); - const footerFieldNames = new Set(footerColumns.map(col => col.fieldName)); - - return data.map(row => { - const convertedRow: Record = { ...row }; - footerFieldNames.forEach(fieldName => { - const value = row[fieldName]; - if (value !== undefined && value !== null && value !== '') { - // 쉼표 제거 후 숫자로 변환 - const numericValue = Number(String(value).replace(/,/g, '')); - if (!isNaN(numericValue)) { - convertedRow[fieldName] = numericValue; - } - } - }); - return convertedRow; - }); - }; - const exportExcel = () => { if (rowData.length === 0) { alert('데이터가 없습니다.'); @@ -157,20 +135,34 @@ const DataGrid = ({ columnInfo, rows = [], rowStartNum, isExcelImport = true, fi const dataStartRow = maxHeaderRowIndex + 1; // 데이터 행 읽기 - const dataRows: Record[] = []; + const dataRows: Record[] = []; for (let row = dataStartRow; row <= range.e.r; row++) { - const rowData: Record = {}; + const rowData: Record = {}; let hasData = false; for (let colIndex = 0; colIndex < headers.length; colIndex++) { const col = range.s.c + colIndex; const cell = worksheet[XLSX.utils.encode_cell({ r: row, c: col })]; const headerName = headers[colIndex] || `Column${colIndex + 1}`; - const cellValue = cell && cell.v !== undefined && cell.v !== null - ? String(cell.v) - : ''; - if (cellValue !== '') { + let cellValue: string | number = ''; + if (cell && cell.v !== undefined && cell.v !== null) { + // 엑셀 셀 타입이 숫자이거나 값이 숫자인 경우 숫자로 변환 + if (cell.t === 'n' || (typeof cell.v === 'number' && !isNaN(cell.v))) { + cellValue = cell.v as number; + } else { + const stringValue = String(cell.v); + // 문자열이지만 숫자로 변환 가능한 경우 숫자로 변환 + const numericValue = Number(stringValue.replace(/,/g, '')); + if (!isNaN(numericValue) && stringValue.trim() !== '') { + cellValue = numericValue; + } else { + cellValue = stringValue; + } + } + } + + if (cellValue !== '' && cellValue !== null && cellValue !== undefined) { hasData = true; } @@ -182,9 +174,7 @@ const DataGrid = ({ columnInfo, rows = [], rowStartNum, isExcelImport = true, fi dataRows.push(rowData); } } - - const convertedDataRows = convertNumericData(dataRows); - setRowData(convertedDataRows); + setRowData(dataRows); } catch (error) { console.error('엑셀 파일 파싱 오류:', error); alert('엑셀 파일 파싱 중 오류가 발생했습니다.'); @@ -220,6 +210,12 @@ const DataGrid = ({ columnInfo, rows = [], rowStartNum, isExcelImport = true, fi (gridView.displayOptions as DisplayOptions).fitStyle = GridFitStyle.EVEN; gridView.header.height = 60; + // 헤더 체크박스 표시 여부 설정 + gridView.setOptions({ + checkBar: { visible: showHeaderCheckbox }, + rowIndicator: { visible: false }, + }); + // footer가 있는 컬럼이 있으면 footer 높이 설정 const hasFooter = columns.some(col => col.footer); if (hasFooter) { @@ -247,12 +243,26 @@ const DataGrid = ({ columnInfo, rows = [], rowStartNum, isExcelImport = true, fi } }); - if (rows.length > 0) { - const convertedRows = convertNumericData(rows); - setRowData(convertedRows); - } - }, [rows, columns, columnLayout, fixedColumns, rowData]); + // mergeCells가 true인 컬럼에 대해 2개씩 병합하는 mergeRule 설정 + columns.forEach(col => { + if (col.mergeCells === true) { + try { + const column = gridView.columnByName(col.name); + if (column) { + // mergeRule: 2개 행씩 병합 (row div 2) + (column as { mergeRule?: { criteria: string; breakMergeOnEmpty?: boolean } }).mergeRule = { + criteria: 'row div 2', + breakMergeOnEmpty: false + }; + } + } catch (error) { + console.warn(`Failed to set mergeRule for column ${col.name}:`, error); + } + } + }); + + }, [columns, columnLayout, fixedColumns, rowData, showHeaderCheckbox]); return (
@@ -288,6 +298,7 @@ const DataGrid = ({ columnInfo, rows = [], rowStartNum, isExcelImport = true, fi name={column.name} fieldName={column.fieldName} header={column.header} + editable={column.editable} footer={column.footer ? { text: column.footer.text, expression: column.footer.expression, diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index 886861848..071d32c87 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -1,6 +1,6 @@ import { useRef, useEffect, useState } from "react" -import RealGrid, { GridFitStyle } from "realgrid" +import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid" import { RealGridReact, RGDataColumn } from "realgrid-react" import type { TabDataGridProps } from "../type/DataGrid.type" import JSZip from "jszip" @@ -11,6 +11,17 @@ import { useTabStore } from "@/store/tab-store" import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components"; import { Button } from "@common/components"; +// RealGrid 타입 확장 +declare module "realgrid" { + interface GridView { + setOptions(options: { + footer?: { visible?: boolean }; + summaryMode?: 'aggregate' | 'statistical' | 'none'; + checkBar?: { visible?: boolean }; + }): void; + } +} + // Window 인터페이스 확장 declare global { interface Window { @@ -25,7 +36,7 @@ if (typeof window !== "undefined") { RealGrid.setLicenseKey(process.env.NEXT_PUBLIC_ST_REALGRID_KEY || ''); -const TabDataGrid = ({ columnInfo, isExcelImport = true, rows = [] }: TabDataGridProps) => { +const TabDataGrid = ({ columnInfo, isExcelImport = true, rows = [], showHeaderCheckbox = false }: TabDataGridProps) => { // 각 탭별로 그리드 ref와 데이터를 관리 const gridRefs = useRef>(new Map()); const fileInputRefs = useRef>(new Map()); @@ -34,7 +45,7 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, rows = [] }: TabDataGri const menuList = useTabStore((state) => state.menuList); // 각 탭별로 데이터를 관리 (key: tabKey) - const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); + const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); const [activeTab, setActiveTab] = useState(() => { // 초기 활성 탭을 첫 번째 tabKey로 설정 const firstTab = columnInfo[0]; @@ -143,20 +154,34 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, rows = [] }: TabDataGri const dataStartRow = maxHeaderRowIndex + 1; // 데이터 행 읽기 - const dataRows: Record[] = []; + const dataRows: Record[] = []; for (let row = dataStartRow; row <= range.e.r; row++) { - const rowData: Record = {}; + const rowData: Record = {}; let hasData = false; for (let colIndex = 0; colIndex < headers.length; colIndex++) { const col = range.s.c + colIndex; const cell = worksheet[XLSX.utils.encode_cell({ r: row, c: col })]; const headerName = headers[colIndex] || `Column${colIndex + 1}`; - const cellValue = cell && cell.v !== undefined && cell.v !== null - ? String(cell.v) - : ''; + + let cellValue: string | number = ''; + if (cell && cell.v !== undefined && cell.v !== null) { + // 엑셀 셀 타입이 숫자이거나 값이 숫자인 경우 숫자로 변환 + if (cell.t === 'n' || (typeof cell.v === 'number' && !isNaN(cell.v))) { + cellValue = cell.v as number; + } else { + const stringValue = String(cell.v); + // 문자열이지만 숫자로 변환 가능한 경우 숫자로 변환 + const numericValue = Number(stringValue.replace(/,/g, '')); + if (!isNaN(numericValue) && stringValue.trim() !== '') { + cellValue = numericValue; + } else { + cellValue = stringValue; + } + } + } - if (cellValue !== '') { + if (cellValue !== '' && cellValue !== null && cellValue !== undefined) { hasData = true; } @@ -308,14 +333,60 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, rows = [] }: TabDataGri const gridView = el.gridView; if (!gridView) return; + const currentInfo = columnInfo.find(info => info.tabKey === tabKey); + if (!currentInfo) return; + gridView.setDisplayOptions({ fitStyle: GridFitStyle.EVEN }); gridView.header.height = 60; + // 헤더 체크박스 표시 여부 설정 + gridView.setOptions({ + checkBar: { visible: showHeaderCheckbox }, + rowIndicator: { visible: false }, + }); + + // footer가 있는 컬럼이 있으면 footer 높이 설정 + const hasFooter = currentInfo.columns.some(col => col.footer); + if (hasFooter) { + gridView.footer.height = 40; + } + + gridView.setOptions({ + summaryMode: "aggregate" + }); + + // footer의 valueCallback 설정 + currentInfo.columns.forEach(col => { + if (col.footer && col.footer.valueCallback) { + const column = gridView.columnByName(col.name); + if (column && column.footer) { + (column.footer as { valueCallback?: typeof col.footer.valueCallback }).valueCallback = col.footer.valueCallback; + } + } + }); + + // mergeCells가 true인 컬럼에 대해 2개씩 병합하는 mergeRule 설정 + currentInfo.columns.forEach(col => { + if (col.mergeCells === true) { + try { + const column = gridView.columnByName(col.name); + if (column) { + // mergeRule: 2개 행씩 병합 (row div 2) + (column as { mergeRule?: { criteria: string; breakMergeOnEmpty?: boolean } }).mergeRule = { + criteria: 'row div 2', + breakMergeOnEmpty: false + }; + } + } catch (error) { + console.warn(`Failed to set mergeRule for column ${col.name}:`, error); + } + } + }); + // 고정 컬럼 설정 - const currentInfo = columnInfo.find(info => info.tabKey === tabKey); - if (currentInfo?.fixedColumns !== undefined && currentInfo.fixedColumns > 0) { + if (currentInfo.fixedColumns !== undefined && currentInfo.fixedColumns > 0) { gridView.setFixedOptions({ colCount: currentInfo.fixedColumns, }); @@ -336,6 +407,12 @@ const TabDataGrid = ({ columnInfo, isExcelImport = true, rows = [] }: TabDataGri name={column.name} fieldName={column.fieldName} header={column.header} + editable={column.editable} + footer={column.footer ? { + text: column.footer.text, + expression: column.footer.expression, + numberFormat: column.footer.numberFormat, + } : undefined} /> ))} diff --git a/apps/st/src/components/statistics/management/ExcretaTreatmentFacilityMng.tsx b/apps/st/src/components/statistics/management/ExcretaTreatmentFacilityMng.tsx index 6e683b07c..b393fa02a 100644 --- a/apps/st/src/components/statistics/management/ExcretaTreatmentFacilityMng.tsx +++ b/apps/st/src/components/statistics/management/ExcretaTreatmentFacilityMng.tsx @@ -4,6 +4,7 @@ import SubmitButton from "@/components/button/SumitButton"; import SaveButton from "@/components/button/SaveButton"; import LastYearDataButton from "@/components/button/LastYearDataButton"; import SubmitExceptionButton from "@/components/button/SubmitExceptionButton"; +import { useState } from "react"; // 하수도 통계 관리 > 07. 분뇨처리시설 const ExcretaTreatmentFacilityMng = () => { @@ -126,6 +127,8 @@ const ExcretaTreatmentFacilityMng = () => { // 제출 로직 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -137,7 +140,7 @@ const ExcretaTreatmentFacilityMng = () => {
- + ); }; diff --git a/apps/st/src/components/statistics/management/PublicSewageFacilityMng.tsx b/apps/st/src/components/statistics/management/PublicSewageFacilityMng.tsx index 569452b55..c9975aaa2 100644 --- a/apps/st/src/components/statistics/management/PublicSewageFacilityMng.tsx +++ b/apps/st/src/components/statistics/management/PublicSewageFacilityMng.tsx @@ -2,8 +2,9 @@ import SearchForm from '@/components/module/SearchForm'; import DataGrid from '@/components/module/DataGrid'; import SubmitButton from '@/components/button/SumitButton'; import SaveButton from '@/components/button/SaveButton'; -import LastYearDataButton from '@/components/button/LastYearDataButton'; import SubmitExceptionButton from '@/components/button/SubmitExceptionButton'; +import OperateDataButton from '@/components/button/OperateDataButton'; +import { useState } from 'react'; // 하수도 통계 관리 > 06. 공공하수처리시설 const PublicSewageFacilityMng = () => { @@ -212,9 +213,9 @@ const PublicSewageFacilityMng = () => { // 제출 로직: 데이터 검증 후 제출 팝업 호출 }; - const getLastYearDataFunc = () => { - console.log('get last year data'); - // 지난연도 데이터 가져오기 로직 + const getOperateDataFunc = () => { + console.log('get operate data'); + // 운영 데이터 가져오기 }; const SubmitExceptionFunc = () => { @@ -222,16 +223,18 @@ const PublicSewageFacilityMng = () => { // 예외제출 로직 }; + const [rowData, setRowData] = useState[]>([]); + return ( <>
+ + - -
- + ); }; diff --git a/apps/st/src/components/statistics/management/PumpingStationMng.tsx b/apps/st/src/components/statistics/management/PumpingStationMng.tsx index 84d36d225..3a776b4a5 100644 --- a/apps/st/src/components/statistics/management/PumpingStationMng.tsx +++ b/apps/st/src/components/statistics/management/PumpingStationMng.tsx @@ -4,6 +4,7 @@ import SubmitButton from "@/components/button/SumitButton"; import SaveButton from "@/components/button/SaveButton"; import LastYearDataButton from "@/components/button/LastYearDataButton"; import SubmitExceptionButton from "@/components/button/SubmitExceptionButton"; +import { useState } from "react"; // 하수도 통계 관리 > 05. 펌프장 const PumpingStationMng = () => { @@ -159,6 +160,8 @@ const PumpingStationMng = () => { // 예외 제출 로직 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -168,7 +171,7 @@ const PumpingStationMng = () => { - + ); }; diff --git a/apps/st/src/components/statistics/management/SeweragePenetrationRateMng.tsx b/apps/st/src/components/statistics/management/SeweragePenetrationRateMng.tsx index 64d826f85..fb5881659 100644 --- a/apps/st/src/components/statistics/management/SeweragePenetrationRateMng.tsx +++ b/apps/st/src/components/statistics/management/SeweragePenetrationRateMng.tsx @@ -6,6 +6,7 @@ import LastYearDataButton from "@/components/button/LastYearDataButton"; import SubmitExceptionButton from "@/components/button/SubmitExceptionButton"; import type { GridView, DataColumn } from "@/components/type/DataGrid.type"; import { getFooterSummary } from "@/components/util/grid/footerSummary"; +import { useState } from "react"; // 하수도 통계 관리 > 01. 하수도 보급률 const SeweragePenetrationRateMng = () => { @@ -143,6 +144,8 @@ const SeweragePenetrationRateMng = () => { // 예외 제출 로직 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -152,7 +155,7 @@ const SeweragePenetrationRateMng = () => { - + ); }; diff --git a/apps/st/src/components/statistics/management/StatsInputSatusMng.tsx b/apps/st/src/components/statistics/management/StatsInputSatusMng.tsx index 98345b7f4..d95df4a8e 100644 --- a/apps/st/src/components/statistics/management/StatsInputSatusMng.tsx +++ b/apps/st/src/components/statistics/management/StatsInputSatusMng.tsx @@ -4,6 +4,7 @@ import ApproveButton from "@/components/button/ApproveButton"; import RejectButton from "@/components/button/RejectButton"; import ReturnButton from "@/components/button/ReturnButton"; import ReviewOpinionButton from "@/components/button/ReviewOpinionButton"; +import { useState } from "react"; // 하수도 통계 관리 > 00. 통계 입력 현황 const StatsInputSatusMng = () => { @@ -55,6 +56,8 @@ const StatsInputSatusMng = () => { // 검토 의견 작성 로직 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -66,7 +69,7 @@ const StatsInputSatusMng = () => { - + ); }; diff --git a/apps/st/src/components/statistics/review/ExcretaTreatmentFacilityRvw.tsx b/apps/st/src/components/statistics/review/ExcretaTreatmentFacilityRvw.tsx index d9086a940..162621ed4 100644 --- a/apps/st/src/components/statistics/review/ExcretaTreatmentFacilityRvw.tsx +++ b/apps/st/src/components/statistics/review/ExcretaTreatmentFacilityRvw.tsx @@ -4,6 +4,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 { useState } from "react"; const ExcretaTreatmentFacilityRvw = () => { @@ -126,6 +127,8 @@ const ExcretaTreatmentFacilityRvw = () => { // 반려 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -135,7 +138,7 @@ const ExcretaTreatmentFacilityRvw = () => { - + ); }; diff --git a/apps/st/src/components/statistics/review/PublicSewageFacilityRvw.tsx b/apps/st/src/components/statistics/review/PublicSewageFacilityRvw.tsx index 0bc14802e..43b46a3a9 100644 --- a/apps/st/src/components/statistics/review/PublicSewageFacilityRvw.tsx +++ b/apps/st/src/components/statistics/review/PublicSewageFacilityRvw.tsx @@ -4,6 +4,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 { useState } from "react"; const PublicSewageFacilityRvw = () => { const columns = [ @@ -187,6 +188,8 @@ const PublicSewageFacilityRvw = () => { // 반려 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -196,7 +199,7 @@ const PublicSewageFacilityRvw = () => { - + ); }; diff --git a/apps/st/src/components/statistics/review/PumpingStationRvw.tsx b/apps/st/src/components/statistics/review/PumpingStationRvw.tsx index 1048ad40b..4d01201da 100644 --- a/apps/st/src/components/statistics/review/PumpingStationRvw.tsx +++ b/apps/st/src/components/statistics/review/PumpingStationRvw.tsx @@ -5,6 +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 { useState } from "react"; const PumpingStationRvw = () => { const columns = [ @@ -146,6 +147,8 @@ const PumpingStationRvw = () => { // 반려 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -155,7 +158,7 @@ const PumpingStationRvw = () => { - + ); }; diff --git a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx index 3ae0cda0a..37b2e0d24 100644 --- a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx +++ b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx @@ -5,12 +5,16 @@ 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 type { ColumnConfig } from "@/components/type/DataGrid.type"; + +import { useState } from "react"; const SeweragePenetrationRateRvw = () => { - const columns = [ - { name: 'sido', fieldName: '시도', header: { text: '시도' } }, - { name: 'sgg', fieldName: '구군', header: { text: '구군' } }, - { name: '구분', fieldName: '구분', header: { text: '구분' } }, + const columns: ColumnConfig[] = [ + { name: 'sn', fieldName: '순번', header: { text: '순번' }, mergeCells: true, editable: false }, + { name: 'sido', fieldName: '시도', header: { text: '시도' }, mergeCells: true, editable: false }, + { name: 'sgg', fieldName: '시군구', header: { text: '시군구' }, mergeCells: true, editable: false }, + { name: '구분', fieldName: '구분', header: { text: '구분' }, editable: false }, { name: '수계', fieldName: '수계', header: { text: '수계' } }, { name: 'bstrm', fieldName: '지류', header: { text: '지류' } }, { name: 'mdbsn', fieldName: '세부단위유역', header: { text: '세부단위유역' } }, @@ -49,6 +53,7 @@ const SeweragePenetrationRateRvw = () => { ]; const columnLayout = [ + 'sn', { name: 'groupA', hideChildHeaders: false, @@ -121,6 +126,8 @@ const SeweragePenetrationRateRvw = () => { // 반려 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -130,7 +137,7 @@ const SeweragePenetrationRateRvw = () => { - + ); }; diff --git a/apps/st/src/components/statistics/review/StatsInputSatusRvw.tsx b/apps/st/src/components/statistics/review/StatsInputSatusRvw.tsx index 11684751d..16c38830d 100644 --- a/apps/st/src/components/statistics/review/StatsInputSatusRvw.tsx +++ b/apps/st/src/components/statistics/review/StatsInputSatusRvw.tsx @@ -4,6 +4,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 { useState } from "react"; const StatsInputSatusRvw = () => { const columns = [ @@ -197,6 +198,8 @@ const StatsInputSatusRvw = () => { // 반려 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -206,7 +209,7 @@ const StatsInputSatusRvw = () => { - + ); } diff --git a/apps/st/src/components/statistics/total/SeweragePenetrationRateTot.tsx b/apps/st/src/components/statistics/total/SeweragePenetrationRateTot.tsx index e12fc5e70..cad60813a 100644 --- a/apps/st/src/components/statistics/total/SeweragePenetrationRateTot.tsx +++ b/apps/st/src/components/statistics/total/SeweragePenetrationRateTot.tsx @@ -4,6 +4,7 @@ import SubmitButton from "@/components/button/SumitButton"; import SaveButton from "@/components/button/SaveButton"; import LastYearDataButton from "@/components/button/LastYearDataButton"; import SubmitExceptionButton from "@/components/button/SubmitExceptionButton"; +import { useState } from "react"; const SeweragePenetrationRateTot = () => { const columns = [ @@ -122,6 +123,8 @@ const SeweragePenetrationRateTot = () => { // 예외 제출 로직 }; + const [rowData, setRowData] = useState[]>([]); + return ( <> @@ -131,7 +134,7 @@ const SeweragePenetrationRateTot = () => { - + ); }; diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index 81a65fd3d..c46a574c5 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -1,12 +1,18 @@ +import React from "react"; + // RealGrid 타입 정의 export type GridView = { getSummary(field: string, summary: 'sum' | 'avg' | 'count' | 'max' | 'min'): number | undefined; getDataSource(): { getRowCount(): number; - getValue(rowIndex: number, field: string): number; + getValue(rowIndex: number, field: string): number | string; + getDataRowId(dataRow: number): number | string; }; + getDataRow(itemIndex: number): number; }; +export type MergeCallback = (grid: GridView, column: DataColumn, itemIndex: number, value: number | string | null | undefined) => number | null; + export type DataColumn = { name: string; fieldName: string; @@ -22,6 +28,8 @@ export type ColumnConfig = { name: string; fieldName: string; header: { text: string }; + editable?: boolean; // 셀 편집 가능 여부 + mergeCells?: boolean; // 같은 값이 연속될 때 셀 병합 여부 footer?: { text?: string; expression?: 'sum' | 'avg' | 'count' | 'max' | 'min'; @@ -53,11 +61,13 @@ export type ColumnInfoList = { export type DataGridProps = { columnInfo: ColumnInfo; - rows?: Record[]; rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주) isExcelImport?: boolean; // 엑셀 가져오기 유무 - onRowsChange?: (rows: Record[]) => void; + onRowsChange?: (rows: Record[]) => void; fixedColumns?: number; // 고정할 컬럼 개수 (좌측부터) + showHeaderCheckbox?: boolean; // 헤더 체크박스 표시 여부 (기본값: true) + rowData: Record[]; + setRowData: React.Dispatch[]>>; }; export type TabDataGridProps = { @@ -65,4 +75,5 @@ export type TabDataGridProps = { rows?: Record[]; isExcelImport?: boolean; // 엑셀 가져오기 유무 onRowsChange?: (rows: Record[]) => void; + showHeaderCheckbox?: boolean; // 헤더 체크박스 표시 여부 (기본값: true) }; \ No newline at end of file