From b5f5938e51993c55fc6d7a46e888a37da483dbe5 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Thu, 29 Oct 2026 10:27:42 +0900 Subject: [PATCH] =?UTF-8?q?[ST]=20-=20fix:=20excel=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EA=B8=B0=20=EC=8B=9C=20=EC=BD=94=EB=93=9C=EA=B0=92?= =?UTF-8?q?=EC=9D=B4=20=EC=88=AB=EC=9E=90=EB=A1=9C=20=EC=9D=B8=EC=8B=9D?= =?UTF-8?q?=EB=90=98=EC=96=B4=20=EC=A0=80=EC=9E=A5=EC=9D=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=A3=A8=EC=96=B4=EC=A7=80=EC=A7=80=20=EC=95=8A=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=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/DataGrid.tsx | 43 +++------ apps/st/src/components/module/TabDataGrid.tsx | 30 +++---- .../st/src/components/util/gridExcelExport.ts | 88 ++++++++++++++++++- 3 files changed, 111 insertions(+), 50 deletions(-) diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index cef70a624..7516fa3d5 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -33,6 +33,7 @@ import { hasAdmdstCdColumn, isExcelImportAdmdstCdInvalid, mergeExcelImportWithExistingRows, + parseExcelImportCellValue, prepareGridColumnsForExcelExport, releaseFixedOptionsForExcelExport, resolveColumnStyleName, @@ -1346,6 +1347,11 @@ useEffect(() => { columnLayout, { skipNonEditable: false }, ); + const valueTypeByFieldName = new Map( + columns + .filter((c) => c.fieldName) + .map((c) => [c.fieldName as string, c.valueType]), + ); // 데이터 행 읽기 const dataRows: Record[] = []; @@ -1367,36 +1373,13 @@ useEffect(() => { ]; const fieldName = excelColumnFieldNames[colIndex]; const allFieldName = excelColumnFieldNamesAll[colIndex]; - - 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; - } - } - } + const typeFieldName = fieldName || allFieldName; + const cellValue = parseExcelImportCellValue( + cell, + typeFieldName + ? valueTypeByFieldName.get(typeFieldName) + : undefined, + ); if (allFieldName === "admdstCd" && cellValue !== "") { rowAdmdstCd = cellValue; diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index 3da27a42d..5c60f1a51 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -53,6 +53,7 @@ import { hasAdmdstCdColumn, isExcelImportAdmdstCdInvalid, mergeExcelImportWithExistingRows, + parseExcelImportCellValue, prepareGridColumnsForExcelExport, releaseFixedOptionsForExcelExport, resolveColumnStyleName, @@ -611,6 +612,11 @@ const TabDataGrid = ({ columnLayout, { skipNonEditable: false }, ); + const valueTypeByFieldName = new Map( + columns + .filter((c) => c.fieldName) + .map((c) => [c.fieldName as string, c.valueType]), + ); // 데이터 행 읽기 const dataRows: Record[] = []; @@ -625,23 +631,13 @@ const TabDataGrid = ({ const cell = worksheet[XLSX.utils.encode_cell({ r: row, c: col })]; const fieldName = excelColumnFieldNames[colIndex]; const allFieldName = excelColumnFieldNamesAll[colIndex]; - - 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; - } - } - } + const typeFieldName = fieldName || allFieldName; + const cellValue = parseExcelImportCellValue( + cell, + typeFieldName + ? valueTypeByFieldName.get(typeFieldName) + : undefined, + ); if (allFieldName === 'admdstCd' && cellValue !== '') { rowAdmdstCd = cellValue; diff --git a/apps/st/src/components/util/gridExcelExport.ts b/apps/st/src/components/util/gridExcelExport.ts index 400189a22..b1b94c974 100644 --- a/apps/st/src/components/util/gridExcelExport.ts +++ b/apps/st/src/components/util/gridExcelExport.ts @@ -1,4 +1,4 @@ -import type { GridView } from "realgrid"; +import { type GridView, ValueType } from "realgrid"; import type { ColumnConfig, @@ -124,6 +124,82 @@ export function getNonEditableColumnFieldNames( return fieldNames; } +type ExcelImportCell = { + t?: string; + v?: unknown; + w?: string; +}; + +/** 엑셀 셀 → 그리드 값. NUMBER 컬럼만 숫자화하고, 코드(01)는 문자열로 유지 */ +export function parseExcelImportCellValue( + cell: ExcelImportCell | undefined, + valueType?: ColumnConfig["valueType"], +): string | number { + if (!cell || cell.v === undefined || cell.v === null || cell.v === "") { + return ""; + } + + if (valueType === ValueType.NUMBER) { + if (typeof cell.v === "number" && !Number.isNaN(cell.v)) { + return cell.v; + } + const stringValue = String(cell.v); + const numericValue = Number(stringValue.replace(/,/g, "")); + if (!Number.isNaN(numericValue) && stringValue.trim() !== "") { + return numericValue; + } + return stringValue; + } + + const formatted = typeof cell.w === "string" ? cell.w.trim() : ""; + if (/^0\d+$/.test(formatted)) { + return formatted; + } + return String(cell.v); +} + +function collectZeroPaddedCodeWidths( + rows: Record[], +): Map { + const widthByField = new Map(); + for (const row of rows) { + for (const [field, value] of Object.entries(row)) { + const s = String(value ?? "").trim(); + if (!/^0\d+$/.test(s)) continue; + widthByField.set(field, Math.max(widthByField.get(field) ?? 0, s.length)); + } + } + return widthByField; +} + +function restoreImportedCodeLeadingZeros( + importedRow: Record, + existingRow: Record | undefined, + codeWidthByField: Map, +): Record { + const next: Record = { ...importedRow }; + for (const [field, value] of Object.entries(next)) { + if (value === "" || value == null) continue; + if (typeof value === "number" && !Number.isInteger(value)) continue; + const importedStr = String(value).trim(); + if (!/^\d+$/.test(importedStr)) continue; + + const existingStr = + existingRow != null ? String(existingRow[field] ?? "").trim() : ""; + let width = codeWidthByField.get(field); + if (!width && /^0\d+$/.test(existingStr)) { + width = existingStr.length; + } + if (!width && field.endsWith("Cd") && importedStr.length <= 2) { + width = 2; + } + if (width && importedStr.length < width) { + next[field] = importedStr.padStart(width, "0"); + } + } + return next; +} + /** 엑셀 import 결과를 기존 행과 병합 — preserveFieldNames는 기존 그리드 값 유지 */ export function mergeExcelImportWithExistingRows( existingRows: Record[], @@ -134,14 +210,20 @@ export function mergeExcelImportWithExistingRows( options?.preserveFieldNames && options.preserveFieldNames.length > 0 ? new Set(options.preserveFieldNames) : null; + const codeWidthByField = collectZeroPaddedCodeWidths(existingRows); return importedRows.map((importedRow, index) => { const existingRow = existingRows[index]; - if (!existingRow) return { ...importedRow }; + const normalizedImported = restoreImportedCodeLeadingZeros( + importedRow, + existingRow, + codeWidthByField, + ); + if (!existingRow) return normalizedImported; const merged: Record = { ...existingRow, - ...importedRow, + ...normalizedImported, }; if (preserveSet) { for (const fieldName of preserveSet) {