Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev

This commit is contained in:
2026-07-07 15:14:27 +09:00
7 changed files with 180 additions and 23 deletions
+18 -4
View File
@@ -25,6 +25,8 @@ import type {
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks";
import {
getExcelImportColumnFieldNames,
getNonEditableColumnFieldNames,
mergeExcelImportWithExistingRows,
prepareGridColumnsForExcelExport,
restoreGridColumnsAfterExcelExport,
shouldPrepareColumnsForExcelExport,
@@ -727,6 +729,7 @@ const DataGrid = ({
rowStartNum,
excelExportFileSuffix,
isExcelImport = true,
excelImportSkipNonEditable = true,
isExcelExport = true,
fixedColumns,
showHeaderCheckbox = false,
@@ -1070,6 +1073,7 @@ useEffect(() => {
const excelColumnFieldNames = getExcelImportColumnFieldNames(
columns,
columnLayout,
{ skipNonEditable: excelImportSkipNonEditable },
);
// 데이터 행 읽기
@@ -1137,7 +1141,14 @@ useEffect(() => {
dataRows.push(rowData);
}
}
setRowData(dataRows);
const preserveFieldNames = excelImportSkipNonEditable
? getNonEditableColumnFieldNames(columns, columnLayout)
: [];
setRowData((prev) =>
mergeExcelImportWithExistingRows(prev, dataRows, {
preserveFieldNames,
}),
);
} catch (error) {
console.error("엑셀 파일 파싱 오류:", error);
alert("엑셀 파일 파싱 중 오류가 발생했습니다.");
@@ -1263,6 +1274,11 @@ useEffect(() => {
"editable",
column.editable !== false,
);
gv.setColumnProperty?.(
column.name,
"styleName",
column.styleName ?? "",
);
applyColumnListEditorProperties(gv, column);
});
if (columns.some((c) => isDropDownEditorColumn(c))) {
@@ -1481,12 +1497,10 @@ useEffect(() => {
fieldName: col.fieldName,
header: realGridHeaderWithTooltip(col.header),
editable: col.editable,
styleName: col.styleName ?? "",
valueType: col.valueType,
dataFormat: col.dataFormat,
...(col.width != null && { width: col.width }),
...(col.styleName != null && {
styleName: col.styleName,
}),
...(col.mergeCells && {
mergeRule: { criteria: "value" as const },
}),
+15 -1
View File
@@ -36,6 +36,8 @@ import SplitDataGrid from "./SplitDataGrid";
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
import {
getExcelImportColumnFieldNames,
getNonEditableColumnFieldNames,
mergeExcelImportWithExistingRows,
prepareGridColumnsForExcelExport,
restoreGridColumnsAfterExcelExport,
shouldPrepareColumnsForExcelExport,
@@ -277,6 +279,7 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string
const TabDataGrid = ({
columnInfo,
isExcelImport = true,
excelImportSkipNonEditable = true,
isExcelExport = true,
rows = [],
showHeaderCheckbox = false,
@@ -552,6 +555,7 @@ const TabDataGrid = ({
const excelColumnFieldNames = getExcelImportColumnFieldNames(
columns,
columnLayout,
{ skipNonEditable: excelImportSkipNonEditable },
);
// 데이터 행 읽기
@@ -596,9 +600,19 @@ const TabDataGrid = ({
}
}
const preserveFieldNames = excelImportSkipNonEditable
? getNonEditableColumnFieldNames(columns, columnLayout)
: [];
setRowDataMap(prev => {
const newMap = new Map(prev);
newMap.set(tabKey, dataRows);
const existingRows = prev.get(tabKey) ?? [];
newMap.set(
tabKey,
mergeExcelImportWithExistingRows(existingRows, dataRows, {
preserveFieldNames,
}),
);
return newMap;
});
} catch (error) {
@@ -555,7 +555,7 @@ const PublicSewageFacilityInp = () => {
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
userId: userInfo.userUnqId,
// superAdmin: isAdminInfo ? 'superAdmin' : '',
superAdmin: isAdminInfo ? 'superAdmin' : '',
noApplicable,
saveList,
};
@@ -108,6 +108,48 @@ const getPipeTypeLastDataYear = () => {
return Math.max(1995, lastDataYear);
};
const PIPE_TYPE_STATUS_TAB_KEY = "ST00020003";
/** 조회 연도(statsYr)에 해당하는 연도별 매설연장 필드명 (y{year}LyngPrlg) */
const resolvePipeTypeEditableLyngField = (statsYr: string | null | undefined): string | null => {
if (!statsYr) return null;
const year = Number(statsYr);
if (!Number.isFinite(year)) return null;
const clamped = Math.min(getPipeTypeLastDataYear(), Math.max(1995, year));
return `y${clamped}LyngPrlg`;
};
/** ST00020003: 입력기간 통과 시 조회 연도의 매설연장(y{year}LyngPrlg)만 편집 가능 */
const applyPipeTypeStatusEditableColumns = (
cols: ColumnConfig[] | undefined,
canEdit: boolean,
periodEnterable: boolean,
statsYr: string | null,
): ColumnConfig[] =>
(cols ?? []).map((col) => {
if (!canEdit) {
return { ...col, editable: false as const, styleName: col.styleName ?? "rg-cell-readonly" };
}
const fieldName = col.fieldName ?? col.name;
const isYearLyngField =
fieldName !== "lyngPrlg" && typeof fieldName === "string" && fieldName.endsWith("LyngPrlg");
if (fieldName === "lyngPrlg" || isYearLyngField) {
const editableLyngField = periodEnterable ? resolvePipeTypeEditableLyngField(statsYr) : null;
const isTargetYear = editableLyngField !== null && fieldName === editableLyngField;
if (isTargetYear) {
const { styleName: _readonlyStyle, ...rest } = col;
return {
...rest,
editable: true as const,
};
}
return { ...col, editable: false as const, styleName: col.styleName ?? "rg-cell-readonly" };
}
return col;
});
const getManholeLastDataYear = () => {
const lastDataYear = new Date().getFullYear() - 1;
return Math.max(2014, lastDataYear);
@@ -507,11 +549,22 @@ const SewagePipeInp = () => {
const canEdit =
computeTabEnterable(tabStatusInfoMap.get(tab.tabKey) ?? null, inputPeriodEnterable) || isAdminInfo;
const applyEditable = (cols: ColumnConfig[] | undefined) =>
(cols ?? []).map((col: ColumnConfig) =>
canEdit
? col
: { ...col, editable: false as const, styleName: col.styleName ?? "rg-cell-readonly" }
);
tab.tabKey === PIPE_TYPE_STATUS_TAB_KEY
? applyPipeTypeStatusEditableColumns(
cols,
canEdit,
inputPeriodEnterable,
lastSearchedYear,
)
: (cols ?? []).map((col: ColumnConfig) =>
canEdit
? col
: {
...col,
editable: false as const,
styleName: col.styleName ?? "rg-cell-readonly",
},
);
if (tab.left && tab.right) {
return {
@@ -525,7 +578,7 @@ const SewagePipeInp = () => {
columns: applyColumnGuides(applyEditable(tab.columns), tab.tabKey),
};
});
}, [tabStatusInfoMap, isAdminInfo, inputPeriodEnterable, columnGuidesByTab]);
}, [tabStatusInfoMap, isAdminInfo, inputPeriodEnterable, columnGuidesByTab, lastSearchedYear]);
const commitToolbarSnapshotForLoadedTab = (
tabKey: string,
@@ -1546,7 +1599,7 @@ const SewagePipeInp = () => {
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
// superAdmin: isAdminInfo ? "superAdmin" : "",
superAdmin: isAdminInfo ? "superAdmin" : "",
saveList,
};
@@ -1626,7 +1679,7 @@ const SewagePipeInp = () => {
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
// superAdmin: isAdminInfo ? "superAdmin" : "",
superAdmin: isAdminInfo ? "superAdmin" : "",
saveList,
};
@@ -1711,7 +1764,7 @@ const SewagePipeInp = () => {
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
// superAdmin: isAdminInfo ? "superAdmin" : "",
superAdmin: isAdminInfo ? "superAdmin" : "",
saveList,
};
@@ -1774,7 +1827,7 @@ const SewagePipeInp = () => {
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
// superAdmin: isAdminInfo ? "superAdmin" : "",
superAdmin: isAdminInfo ? "superAdmin" : "",
saveList,
};
@@ -1853,7 +1906,7 @@ const SewagePipeInp = () => {
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
// superAdmin: isAdminInfo ? "superAdmin" : "",
superAdmin: isAdminInfo ? "superAdmin" : "",
saveList,
};
@@ -2080,6 +2133,9 @@ const SewagePipeInp = () => {
});
}}
isExcelImport={excelToolbarEnabledForTab}
excelImportSkipNonEditable={
tabKey === PIPE_TYPE_STATUS_TAB_KEY ? !isAdminInfo : true
}
isExcelExport
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
@@ -353,9 +353,9 @@ const SeweragePenetrationRateInp = () => {
statsCd: statsCd,
};
// if (isAdminInfo) {
// params.superAdmin = 'superAdmin';
// }
if (isAdminInfo) {
params.superAdmin = 'superAdmin';
}
try {
setIsLoading(true);
@@ -682,9 +682,9 @@ const SeweragePenetrationRateInp = () => {
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt // 행정참고자료 외국인 합계
};
// if (isAdminInfo) {
// requestData.superAdmin = 'superAdmin';
// }
if (isAdminInfo) {
requestData.superAdmin = 'superAdmin';
}
const response = await statisticsInputSewSupplyRateApi.saveSewerageSupplyRate(requestData);
@@ -380,6 +380,8 @@ export type DataGridProps = {
/** 있으면보내기 파일명: `{메뉴명}_{suffix}.xlsx` (TabDataGrid의 탭 라벨 접미사와 동일) */
excelExportFileSuffix?: string;
isExcelImport?: boolean; // 엑셀 가져오기 유무
/** 엑셀 가져오기 시 editable:false 컬럼 값 무시 (기본 true) */
excelImportSkipNonEditable?: boolean;
isExcelExport?: boolean;
onRowsChange?: (rows: Record<string, string | number>[]) => void;
fixedColumns?: number; // 고정할 컬럼 개수 (좌측부터)
@@ -432,6 +434,8 @@ export type TabDataGridProps = {
columnInfo: ColumnInfoList[];
rows?: Record<string, string | number>[];
isExcelImport?: boolean; // 엑셀 가져오기 유무
/** 엑셀 가져오기 시 editable:false 컬럼 값 무시 (기본 true) */
excelImportSkipNonEditable?: boolean;
isExcelExport?: boolean;
onRowsChange?: (rows: Record<string, string | number>[]) => void;
showHeaderCheckbox?: boolean; // 헤더 체크박스 표시 여부 (기본값: true)
@@ -28,14 +28,22 @@ function flattenColumnLayoutNames(layout: ColumnLayoutItem[]): string[] {
return result;
}
export type ExcelImportColumnFieldNamesOptions = {
/** false면 editable:false 컬럼도 import (기본 true — 읽기전용·계산 컬럼은 엑셀 값 무시) */
skipNonEditable?: boolean;
};
/** 엑셀 가져오기 — N번째 열을 그리드 N번째 fieldName에 매핑 (보내기 컬럼 순서와 동일) */
export function getExcelImportColumnFieldNames(
columns: ColumnConfig[],
columnLayout?: ColumnLayoutItem[],
options?: ExcelImportColumnFieldNamesOptions,
): string[] {
const skipNonEditable = options?.skipNonEditable !== false;
const excludedNames = new Set(
columns.filter((c) => c.excludeFromExcelExport).map((c) => c.name),
);
const columnByName = new Map(columns.map((c) => [c.name, c]));
const fieldByName = new Map(
columns
.filter((c) => c.fieldName)
@@ -54,11 +62,72 @@ export function getExcelImportColumnFieldNames(
const fieldName = fieldByName.get(name);
if (!fieldName) continue;
seen.add(name);
const col = columnByName.get(name);
if (skipNonEditable && col?.editable === false) {
fieldNames.push("");
continue;
}
fieldNames.push(fieldName);
}
return fieldNames;
}
/** 엑셀 import 시 기존 값을 유지할 fieldName 목록 (editable:false) */
export function getNonEditableColumnFieldNames(
columns: ColumnConfig[],
columnLayout?: ColumnLayoutItem[],
): string[] {
const excludedNames = new Set(
columns.filter((c) => c.excludeFromExcelExport).map((c) => c.name),
);
const columnByName = new Map(columns.map((c) => [c.name, c]));
const layoutNames =
columnLayout && columnLayout.length > 0
? flattenColumnLayoutNames(columnLayout)
: columns.map((c) => c.name);
const seen = new Set<string>();
const fieldNames: string[] = [];
for (const name of layoutNames) {
if (excludedNames.has(name) || seen.has(name)) continue;
const col = columnByName.get(name);
const fieldName = col?.fieldName;
if (!fieldName || col.editable !== false) continue;
seen.add(name);
fieldNames.push(fieldName);
}
return fieldNames;
}
/** 엑셀 import 결과를 기존 행과 병합 — preserveFieldNames는 기존 그리드 값 유지 */
export function mergeExcelImportWithExistingRows(
existingRows: Record<string, string | number>[],
importedRows: Record<string, string | number>[],
options?: { preserveFieldNames?: readonly string[] },
): Record<string, string | number>[] {
const preserveSet =
options?.preserveFieldNames && options.preserveFieldNames.length > 0
? new Set(options.preserveFieldNames)
: null;
return importedRows.map((importedRow, index) => {
const existingRow = existingRows[index];
if (!existingRow) return { ...importedRow };
const merged: Record<string, string | number> = { ...existingRow, ...importedRow };
if (preserveSet) {
for (const fieldName of preserveSet) {
const preserved = existingRow[fieldName];
if (preserved !== undefined) {
merged[fieldName] = preserved;
}
}
}
return merged;
});
}
export type ExcelExportVisibilitySnapshot = Map<string, boolean>;
/** 엑셀보내기 직전: 제외 컬럼 숨김, 그리드용 숨김(visible:false) 컬럼은 export에만 표시 */