= {};
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
-
+
>
);
};
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