[st] - fix: 2-3 컬럼 로직 수정, 입력단 excel 업로드 시 행정동코드 매핑 검사
This commit is contained in:
@@ -24,8 +24,11 @@ import type {
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks";
|
||||
import {
|
||||
buildExcelExportFileBaseName,
|
||||
getExcelImportColumnFieldNames,
|
||||
getNonEditableColumnFieldNames,
|
||||
hasAdmdstCdColumn,
|
||||
isExcelImportAdmdstCdInvalid,
|
||||
mergeExcelImportWithExistingRows,
|
||||
prepareGridColumnsForExcelExport,
|
||||
restoreGridColumnsAfterExcelExport,
|
||||
@@ -728,8 +731,10 @@ const DataGrid = ({
|
||||
columnInfo,
|
||||
rowStartNum,
|
||||
excelExportFileSuffix,
|
||||
excelExportSearchContext,
|
||||
isExcelImport = true,
|
||||
excelImportSkipNonEditable = true,
|
||||
excelImportSewerBzmnCd,
|
||||
isExcelExport = true,
|
||||
fixedColumns,
|
||||
showHeaderCheckbox = false,
|
||||
@@ -951,9 +956,11 @@ useEffect(() => {
|
||||
? prepareGridColumnsForExcelExport(gridView, columns)
|
||||
: undefined;
|
||||
|
||||
const exportBaseName = excelExportFileSuffix
|
||||
? `${menuName}_${excelExportFileSuffix}`
|
||||
: menuName;
|
||||
const exportBaseName = buildExcelExportFileBaseName(
|
||||
menuName,
|
||||
excelExportFileSuffix,
|
||||
excelExportSearchContext,
|
||||
);
|
||||
|
||||
gridView.exportGrid({
|
||||
type: "excel",
|
||||
@@ -1075,11 +1082,18 @@ useEffect(() => {
|
||||
columnLayout,
|
||||
{ skipNonEditable: excelImportSkipNonEditable },
|
||||
);
|
||||
const excelColumnFieldNamesAll = getExcelImportColumnFieldNames(
|
||||
columns,
|
||||
columnLayout,
|
||||
{ skipNonEditable: false },
|
||||
);
|
||||
|
||||
// 데이터 행 읽기
|
||||
const dataRows: Record<string, string | number>[] = [];
|
||||
const admdstCdFromExcel: Array<string | number | undefined> = [];
|
||||
for (let row = dataStartRow; row <= range.e.r; row++) {
|
||||
const rowData: Record<string, string | number> = {};
|
||||
let rowAdmdstCd: string | number | undefined;
|
||||
let hasData = false;
|
||||
|
||||
for (
|
||||
@@ -1093,7 +1107,7 @@ useEffect(() => {
|
||||
XLSX.utils.encode_cell({ r: row, c: col })
|
||||
];
|
||||
const fieldName = excelColumnFieldNames[colIndex];
|
||||
if (!fieldName) continue;
|
||||
const allFieldName = excelColumnFieldNamesAll[colIndex];
|
||||
|
||||
let cellValue: string | number = "";
|
||||
if (
|
||||
@@ -1125,6 +1139,12 @@ useEffect(() => {
|
||||
}
|
||||
}
|
||||
|
||||
if (allFieldName === "admdstCd" && cellValue !== "") {
|
||||
rowAdmdstCd = cellValue;
|
||||
}
|
||||
|
||||
if (!fieldName) continue;
|
||||
|
||||
if (
|
||||
cellValue !== "" &&
|
||||
cellValue !== null &&
|
||||
@@ -1139,8 +1159,21 @@ useEffect(() => {
|
||||
// 데이터가 하나라도 있는 행만 추가
|
||||
if (hasData) {
|
||||
dataRows.push(rowData);
|
||||
admdstCdFromExcel.push(rowAdmdstCd);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
excelImportSewerBzmnCd &&
|
||||
hasAdmdstCdColumn(columns) &&
|
||||
isExcelImportAdmdstCdInvalid(
|
||||
excelImportSewerBzmnCd,
|
||||
admdstCdFromExcel,
|
||||
)
|
||||
) {
|
||||
alert("조회 대상과 업로드한 엑셀 데이터의 행정동 코드가 일치 하지 않습니다.");
|
||||
return;
|
||||
}
|
||||
const preserveFieldNames = excelImportSkipNonEditable
|
||||
? getNonEditableColumnFieldNames(columns, columnLayout)
|
||||
: [];
|
||||
|
||||
@@ -35,8 +35,11 @@ import SplitDataGrid from "./SplitDataGrid";
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
|
||||
import {
|
||||
buildExcelExportFileBaseName,
|
||||
getExcelImportColumnFieldNames,
|
||||
getNonEditableColumnFieldNames,
|
||||
hasAdmdstCdColumn,
|
||||
isExcelImportAdmdstCdInvalid,
|
||||
mergeExcelImportWithExistingRows,
|
||||
prepareGridColumnsForExcelExport,
|
||||
restoreGridColumnsAfterExcelExport,
|
||||
@@ -280,6 +283,8 @@ const TabDataGrid = ({
|
||||
columnInfo,
|
||||
isExcelImport = true,
|
||||
excelImportSkipNonEditable = true,
|
||||
excelImportSewerBzmnCd,
|
||||
excelExportSearchContext,
|
||||
isExcelExport = true,
|
||||
rows = [],
|
||||
showHeaderCheckbox = false,
|
||||
@@ -455,10 +460,16 @@ const TabDataGrid = ({
|
||||
? prepareGridColumnsForExcelExport(gridView, columns)
|
||||
: undefined;
|
||||
|
||||
const exportBaseName = buildExcelExportFileBaseName(
|
||||
menuName,
|
||||
tabLabel,
|
||||
excelExportSearchContext,
|
||||
);
|
||||
|
||||
gridView.exportGrid({
|
||||
type: 'excel',
|
||||
target: 'local',
|
||||
fileName: `${menuName}_${tabLabel}.xlsx`,
|
||||
fileName: `${exportBaseName}.xlsx`,
|
||||
footer: 'hidden',
|
||||
allColumns: !useExcelColumnVisibilityPrep,
|
||||
done: () => {
|
||||
@@ -557,18 +568,25 @@ const TabDataGrid = ({
|
||||
columnLayout,
|
||||
{ skipNonEditable: excelImportSkipNonEditable },
|
||||
);
|
||||
const excelColumnFieldNamesAll = getExcelImportColumnFieldNames(
|
||||
columns,
|
||||
columnLayout,
|
||||
{ skipNonEditable: false },
|
||||
);
|
||||
|
||||
// 데이터 행 읽기
|
||||
const dataRows: Record<string, string | number>[] = [];
|
||||
const admdstCdFromExcel: Array<string | number | undefined> = [];
|
||||
for (let row = dataStartRow; row <= range.e.r; row++) {
|
||||
const rowData: Record<string, string | number> = {};
|
||||
let rowAdmdstCd: string | number | undefined;
|
||||
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 fieldName = excelColumnFieldNames[colIndex];
|
||||
if (!fieldName) continue;
|
||||
const allFieldName = excelColumnFieldNamesAll[colIndex];
|
||||
|
||||
let cellValue: string | number = '';
|
||||
if (cell && cell.v !== undefined && cell.v !== null) {
|
||||
@@ -587,6 +605,12 @@ const TabDataGrid = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (allFieldName === 'admdstCd' && cellValue !== '') {
|
||||
rowAdmdstCd = cellValue;
|
||||
}
|
||||
|
||||
if (!fieldName) continue;
|
||||
|
||||
if (cellValue !== '' && cellValue !== null && cellValue !== undefined) {
|
||||
hasData = true;
|
||||
}
|
||||
@@ -597,19 +621,32 @@ const TabDataGrid = ({
|
||||
// 데이터가 하나라도 있는 행만 추가
|
||||
if (hasData) {
|
||||
dataRows.push(rowData);
|
||||
admdstCdFromExcel.push(rowAdmdstCd);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
excelImportSewerBzmnCd &&
|
||||
hasAdmdstCdColumn(columns) &&
|
||||
isExcelImportAdmdstCdInvalid(
|
||||
excelImportSewerBzmnCd,
|
||||
admdstCdFromExcel,
|
||||
)
|
||||
) {
|
||||
alert('조회 대상과 행정동 코드가 일치 하지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const preserveFieldNames = excelImportSkipNonEditable
|
||||
? getNonEditableColumnFieldNames(columns, columnLayout)
|
||||
: [];
|
||||
|
||||
setRowDataMap(prev => {
|
||||
const newMap = new Map(prev);
|
||||
const existingRows = prev.get(tabKey) ?? [];
|
||||
const prevRows = prev.get(tabKey) ?? [];
|
||||
newMap.set(
|
||||
tabKey,
|
||||
mergeExcelImportWithExistingRows(existingRows, dataRows, {
|
||||
mergeExcelImportWithExistingRows(prevRows, dataRows, {
|
||||
preserveFieldNames,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -24,6 +24,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -309,6 +310,10 @@ function createEmptyExcretaTreatmentFacilityRow(
|
||||
return row;
|
||||
}
|
||||
|
||||
const EXCRETA_TREATMENT_FACILITY_NAME_GUIDE_MESSAGE =
|
||||
"분뇨처리시설 등록시 분뇨처리시설명이 동일할 경우 같은 데이터로 분류 됩니다.\n\n" +
|
||||
"분뇨처리시설명이 동일 할 경우 구분을 위해 분뇨처리시설 1, 분뇨처리시설 2 형태로 구분하여 주시길 바랍니다.";
|
||||
|
||||
// 하수도 통계 관리 > 07. 분뇨처리시설
|
||||
const ExcretaTreatmentFacilityInp = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
@@ -320,6 +325,10 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
const setGlobalProgressLabel = useGlobalProgressStore((state) => state.setLabel);
|
||||
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
|
||||
|
||||
useEffect(() => {
|
||||
alert(EXCRETA_TREATMENT_FACILITY_NAME_GUIDE_MESSAGE);
|
||||
}, [alert]);
|
||||
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [isNotApplicable, setIsNotApplicable] = useState(false);
|
||||
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
|
||||
@@ -1059,6 +1068,8 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
isExcelImport={
|
||||
searchLoaded && tabEnterableForGrid && gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
|
||||
@@ -44,6 +44,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -1264,6 +1265,8 @@ const PrivateSewageFacilityInp = () => {
|
||||
tabEnterableForGrid &&
|
||||
gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
|
||||
@@ -38,6 +38,7 @@ import SubmitButton from '@/components/button/SumitButton';
|
||||
import UpdateOperateDataButton from '@/components/button/UpdateOperateDataButton';
|
||||
import VerificationResultButton from '@/components/button/VerificationResultButton';
|
||||
import DataGrid from '@/components/module/DataGrid';
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import SearchForm from '@/components/module/SearchForm';
|
||||
import SplitDataGrid from '@/components/module/SplitDataGrid';
|
||||
import { openVerificationResultDialogIfNeeded } from '@/components/module/post-submit-verification-results';
|
||||
@@ -1346,6 +1347,8 @@ const PublicSewageFacilityInp = () => {
|
||||
tab.tabKey === 'ST00060001' ? st60001GridGetRowsRef : undefined
|
||||
}
|
||||
isExcelImport={excelToolbarEnabledForTab}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
enableSort={false}
|
||||
|
||||
@@ -24,6 +24,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -304,6 +305,10 @@ function createEmptyPumpingStationRow(
|
||||
return row;
|
||||
}
|
||||
|
||||
const PUMPING_STATION_FACILITY_NAME_GUIDE_MESSAGE =
|
||||
"펌프장 등록시 시설명이 동일할 경우 같은 데이터로 분류 됩니다.\n\n" +
|
||||
"시설명이 동일 할 경우 구분을 위해 펌프장 1, 펌프장 2 형태로 구분하여 주시기 바랍니다.";
|
||||
|
||||
// 하수도 통계 관리 > 05. 펌프장 (SewageReservoirPumpInp 단일 탭 패턴)
|
||||
const PumpingStationInp = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
@@ -315,6 +320,10 @@ const PumpingStationInp = () => {
|
||||
const setGlobalProgressLabel = useGlobalProgressStore((state) => state.setLabel);
|
||||
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
|
||||
|
||||
useEffect(() => {
|
||||
alert(PUMPING_STATION_FACILITY_NAME_GUIDE_MESSAGE);
|
||||
}, [alert]);
|
||||
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [isNotApplicable, setIsNotApplicable] = useState(false);
|
||||
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
|
||||
@@ -1043,6 +1052,8 @@ const PumpingStationInp = () => {
|
||||
isExcelImport={
|
||||
searchLoaded && tabEnterableForGrid && gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -2056,6 +2057,8 @@ const SewageExcretaRelatedInp = () => {
|
||||
tabEnterableForGrid &&
|
||||
gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -24,6 +24,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -2147,6 +2148,8 @@ const SewageExcretaSlurgeInp = () => {
|
||||
enableSort={false}
|
||||
statusInfo={tabStatusInfo}
|
||||
isExcelImport={tabRowDataExists(rowDataMap, tabKey)}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -53,13 +53,14 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import GridTopActionButton from "@/components/module/GridTopActionButton";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import SplitDataGrid from "@/components/module/SplitDataGrid";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
||||
import type { ColumnConfig,ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList, ColumnLayoutItem } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { FactInstallStatusInpColumn } from "@/components/util/column/input/sewagePipe/FactInstallStatusInpColumn";
|
||||
@@ -110,28 +111,85 @@ const getPipeTypeLastDataYear = () => {
|
||||
|
||||
const PIPE_TYPE_STATUS_TAB_KEY = "ST00020003";
|
||||
|
||||
/** 조회 연도(statsYr)에 해당하는 연도별 매설연장 필드명 (y{year}LyngPrlg) */
|
||||
const resolvePipeTypeEditableLyngField = (statsYr: string | null | undefined): string | null => {
|
||||
/** 조회 연도(statsYr)에 해당하는 연도 (y{year}* 필드용) */
|
||||
const resolvePipeTypeEditableYear = (statsYr: string | null | undefined): number | 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`;
|
||||
return Math.min(getPipeTypeLastDataYear(), Math.max(1995, year));
|
||||
};
|
||||
|
||||
/** ST00020003: 입력기간 통과 시 조회 연도의 매설연장(y{year}LyngPrlg)만 편집 가능 */
|
||||
/** 조회 연도(statsYr)에 해당하는 연도별 매설연장 필드명 (y{year}LyngPrlg) */
|
||||
const resolvePipeTypeEditableLyngField = (statsYr: string | null | undefined): string | null => {
|
||||
const year = resolvePipeTypeEditableYear(statsYr);
|
||||
return year === null ? null : `y${year}LyngPrlg`;
|
||||
};
|
||||
|
||||
const pipeTypeYearDmltnField = (year: number) => `y${year}DmltnPrlg`;
|
||||
|
||||
const shouldHidePipeTypeEditableYearDmltn = (
|
||||
canEdit: boolean,
|
||||
periodEnterable: boolean,
|
||||
statsYr: string | null,
|
||||
): number | null => {
|
||||
if (!canEdit || !periodEnterable) return null;
|
||||
return resolvePipeTypeEditableYear(statsYr);
|
||||
};
|
||||
|
||||
const applyPipeTypeStatusColumnLayout = (
|
||||
layout: ColumnLayoutItem[] | undefined,
|
||||
hideDmltnYear: number | null,
|
||||
): ColumnLayoutItem[] => {
|
||||
if (hideDmltnYear === null) return layout ?? [];
|
||||
const hideField = pipeTypeYearDmltnField(hideDmltnYear);
|
||||
|
||||
const walk = (items: ColumnLayoutItem[]): ColumnLayoutItem[] => {
|
||||
const result: ColumnLayoutItem[] = [];
|
||||
for (const item of items) {
|
||||
if (typeof item === "string") {
|
||||
if (item !== hideField) result.push(item);
|
||||
continue;
|
||||
}
|
||||
if ("column" in item) {
|
||||
if (item.column !== hideField) result.push(item);
|
||||
continue;
|
||||
}
|
||||
const nextItems = walk(item.items as ColumnLayoutItem[]);
|
||||
if (nextItems.length > 0) {
|
||||
result.push({ ...item, items: nextItems });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
return walk(layout ?? []);
|
||||
};
|
||||
|
||||
/** ST00020003: 입력기간 통과 시 조회 연도의 매설연장(y{year}LyngPrlg)만 편집 가능, 해당 연도 철거연장 컬럼 숨김 */
|
||||
const applyPipeTypeStatusEditableColumns = (
|
||||
cols: ColumnConfig[] | undefined,
|
||||
canEdit: boolean,
|
||||
periodEnterable: boolean,
|
||||
statsYr: string | null,
|
||||
): ColumnConfig[] =>
|
||||
(cols ?? []).map((col) => {
|
||||
): ColumnConfig[] => {
|
||||
const hideDmltnYear = shouldHidePipeTypeEditableYearDmltn(canEdit, periodEnterable, statsYr);
|
||||
|
||||
return (cols ?? []).map((col) => {
|
||||
if (!canEdit) {
|
||||
return { ...col, editable: false as const, styleName: col.styleName ?? "rg-cell-readonly" };
|
||||
}
|
||||
|
||||
const fieldName = col.fieldName ?? col.name;
|
||||
|
||||
if (hideDmltnYear !== null && fieldName === pipeTypeYearDmltnField(hideDmltnYear)) {
|
||||
return {
|
||||
...col,
|
||||
visible: false,
|
||||
editable: false as const,
|
||||
excludeFromExcelExport: true,
|
||||
};
|
||||
}
|
||||
|
||||
const isYearLyngField =
|
||||
fieldName !== "lyngPrlg" && typeof fieldName === "string" && fieldName.endsWith("LyngPrlg");
|
||||
if (fieldName === "lyngPrlg" || isYearLyngField) {
|
||||
@@ -149,6 +207,7 @@ const applyPipeTypeStatusEditableColumns = (
|
||||
|
||||
return col;
|
||||
});
|
||||
};
|
||||
|
||||
const getManholeLastDataYear = () => {
|
||||
const lastDataYear = new Date().getFullYear() - 1;
|
||||
@@ -576,6 +635,18 @@ const SewagePipeInp = () => {
|
||||
return {
|
||||
...tab,
|
||||
columns: applyColumnGuides(applyEditable(tab.columns), tab.tabKey),
|
||||
...(tab.tabKey === PIPE_TYPE_STATUS_TAB_KEY && tab.columnLayout
|
||||
? {
|
||||
columnLayout: applyPipeTypeStatusColumnLayout(
|
||||
tab.columnLayout,
|
||||
shouldHidePipeTypeEditableYearDmltn(
|
||||
canEdit,
|
||||
inputPeriodEnterable,
|
||||
lastSearchedYear,
|
||||
),
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
});
|
||||
}, [tabStatusInfoMap, isAdminInfo, inputPeriodEnterable, columnGuidesByTab, lastSearchedYear]);
|
||||
@@ -2136,6 +2207,8 @@ const SewagePipeInp = () => {
|
||||
excelImportSkipNonEditable={
|
||||
tabKey === PIPE_TYPE_STATUS_TAB_KEY ? !isAdminInfo : true
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
|
||||
@@ -46,6 +46,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -1202,6 +1203,8 @@ const SewagePipeMaintainInp = () => {
|
||||
tabEnterableForGrid &&
|
||||
gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
|
||||
@@ -25,6 +25,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -97,6 +98,10 @@ const SEWAGE_RESERVOIR_PUMP_COLUMN_GUIDE_TAB_KEYS = Array.from(
|
||||
),
|
||||
);
|
||||
|
||||
const SEWAGE_RESERVOIR_FACILITY_NAME_GUIDE_MESSAGE =
|
||||
"하수저류시설 등록시 하수저류시설명이 동일할 경우 같은 데이터로 분류됩니다.\n\n" +
|
||||
"하수저류시설명이 동일 할 경우 구분을 위해 하수저류시설 1, 하수저류시설 2 형태로 구분하여 주시길 바랍니다.";
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: SewageReservoirVerificationResult;
|
||||
};
|
||||
@@ -459,6 +464,10 @@ const SewageReservoirPumpInp = () => {
|
||||
const setGlobalProgressLabel = useGlobalProgressStore((state) => state.setLabel);
|
||||
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
|
||||
|
||||
useEffect(() => {
|
||||
alert(SEWAGE_RESERVOIR_FACILITY_NAME_GUIDE_MESSAGE);
|
||||
}, [alert]);
|
||||
|
||||
const submitFunc = () => {
|
||||
void (async () => {
|
||||
const tabKey = activeTabRef.current;
|
||||
@@ -1655,6 +1664,8 @@ const SewageReservoirPumpInp = () => {
|
||||
tabEnterableForGrid &&
|
||||
gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
|
||||
@@ -48,6 +48,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -1330,6 +1331,8 @@ const SewerageFinanceInp = () => {
|
||||
tabEnterableForGrid &&
|
||||
gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
|
||||
@@ -40,6 +40,7 @@ import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
// import { DataGrid } from "@rsone/components";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -807,6 +808,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
key={(isEnterable || isAdminInfo) ? 'enterable' : 'readonly'}
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={(rowData.length > 0) && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true)}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
rowStartNum={3}
|
||||
fixedColumns={1}
|
||||
rowData={rowData}
|
||||
|
||||
@@ -13,6 +13,7 @@ import SaveButton from "@/components/button/SaveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -1240,6 +1241,8 @@ const SewerageServiceLevelInp = () => {
|
||||
tabEnterableForGrid &&
|
||||
gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
|
||||
@@ -25,6 +25,7 @@ import SubmitButton from "@/components/button/SumitButton";
|
||||
import UpdateOperateDataButton from "@/components/button/UpdateOperateDataButton";
|
||||
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
||||
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
||||
@@ -2194,6 +2195,8 @@ const WaterReuseInp = () => {
|
||||
tabEnterableForGrid &&
|
||||
gugunOk
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
|
||||
@@ -374,6 +374,12 @@ export type DataCellClickCallback = (
|
||||
params: DataCellClickCallbackParams,
|
||||
) => void;
|
||||
|
||||
export type ExcelExportSearchContext = {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
};
|
||||
|
||||
export type DataGridProps = {
|
||||
columnInfo: ColumnInfo;
|
||||
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
|
||||
@@ -382,6 +388,10 @@ export type DataGridProps = {
|
||||
isExcelImport?: boolean; // 엑셀 가져오기 유무
|
||||
/** 엑셀 가져오기 시 editable:false 컬럼 값 무시 (기본 true) */
|
||||
excelImportSkipNonEditable?: boolean;
|
||||
/** 엑셀 import 시 admdstCd 앞 2자리와 비교할 조회 sewerBzmnCd(SearchForm bsnmCd) */
|
||||
excelImportSewerBzmnCd?: string;
|
||||
/** 엑셀보내기 파일명에 조회 시도·시군구·연도 접미사 추가 */
|
||||
excelExportSearchContext?: ExcelExportSearchContext;
|
||||
isExcelExport?: boolean;
|
||||
onRowsChange?: (rows: Record<string, string | number>[]) => void;
|
||||
fixedColumns?: number; // 고정할 컬럼 개수 (좌측부터)
|
||||
@@ -436,6 +446,10 @@ export type TabDataGridProps = {
|
||||
isExcelImport?: boolean; // 엑셀 가져오기 유무
|
||||
/** 엑셀 가져오기 시 editable:false 컬럼 값 무시 (기본 true) */
|
||||
excelImportSkipNonEditable?: boolean;
|
||||
/** 엑셀 import 시 admdstCd 앞 2자리와 비교할 조회 sewerBzmnCd(SearchForm bsnmCd) */
|
||||
excelImportSewerBzmnCd?: string;
|
||||
/** 엑셀보내기 파일명에 조회 시도·시군구·연도 접미사 추가 */
|
||||
excelExportSearchContext?: ExcelExportSearchContext;
|
||||
isExcelExport?: boolean;
|
||||
onRowsChange?: (rows: Record<string, string | number>[]) => void;
|
||||
showHeaderCheckbox?: boolean; // 헤더 체크박스 표시 여부 (기본값: true)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
ColumnConfig,
|
||||
ColumnLayoutItem,
|
||||
ExcelExportSearchContext,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { GridView } from "realgrid";
|
||||
|
||||
@@ -33,6 +34,27 @@ export type ExcelImportColumnFieldNamesOptions = {
|
||||
skipNonEditable?: boolean;
|
||||
};
|
||||
|
||||
const ADMDST_CD_FIELD = "admdstCd";
|
||||
|
||||
export function hasAdmdstCdColumn(columns: ColumnConfig[]): boolean {
|
||||
return columns.some((col) => col.fieldName === ADMDST_CD_FIELD);
|
||||
}
|
||||
|
||||
/** 엑셀에서 읽은 admdstCd만 검증 — 빈 값 또는 sewerBzmnCd 앞 2자리 불일치 시 true(실패) */
|
||||
export function isExcelImportAdmdstCdInvalid(
|
||||
sewerBzmnCd: string | null | undefined,
|
||||
admdstCdFromExcel: ReadonlyArray<string | number | undefined>,
|
||||
): boolean {
|
||||
const expectedPrefix = sewerBzmnCd?.trim().slice(0, 2) ?? "";
|
||||
if (expectedPrefix.length < 2) return false;
|
||||
|
||||
return admdstCdFromExcel.some((raw) => {
|
||||
const admdstCd = String(raw ?? "").trim();
|
||||
if (!admdstCd) return true;
|
||||
return admdstCd.slice(0, 2) !== expectedPrefix;
|
||||
});
|
||||
}
|
||||
|
||||
/** 엑셀 가져오기 — N번째 열을 그리드 N번째 fieldName에 매핑 (보내기 컬럼 순서와 동일) */
|
||||
export function getExcelImportColumnFieldNames(
|
||||
columns: ColumnConfig[],
|
||||
@@ -170,3 +192,46 @@ export function restoreGridColumnsAfterExcelExport(
|
||||
export function shouldPrepareColumnsForExcelExport(columns: ColumnConfig[]): boolean {
|
||||
return columns.some((c) => c.excludeFromExcelExport || c.visible === false);
|
||||
}
|
||||
|
||||
const INVALID_EXCEL_FILE_NAME_CHARS = /[\\/:*?"<>|]/g;
|
||||
|
||||
function sanitizeExcelFileNamePart(value: string): string {
|
||||
return value.replace(INVALID_EXCEL_FILE_NAME_CHARS, "").trim();
|
||||
}
|
||||
|
||||
/** 엑셀 파일명: `{메뉴명}_{탭접미사}_{시도}_{시군구}_{연도}` */
|
||||
export function buildExcelExportFileBaseName(
|
||||
menuName: string,
|
||||
tabSuffix?: string,
|
||||
searchContext?: ExcelExportSearchContext | null,
|
||||
): string {
|
||||
const parts: string[] = [sanitizeExcelFileNamePart(menuName)];
|
||||
|
||||
if (tabSuffix?.trim()) {
|
||||
parts.push(sanitizeExcelFileNamePart(tabSuffix.trim()));
|
||||
}
|
||||
|
||||
const ctpvNm = searchContext?.ctpvNm?.trim();
|
||||
const sggNm = searchContext?.sggNm?.trim();
|
||||
const statsYr = searchContext?.statsYr?.trim();
|
||||
|
||||
if (ctpvNm) parts.push(sanitizeExcelFileNamePart(ctpvNm));
|
||||
if (sggNm && sggNm !== "ALL") parts.push(sanitizeExcelFileNamePart(sggNm));
|
||||
if (statsYr) parts.push(sanitizeExcelFileNamePart(statsYr));
|
||||
|
||||
return parts.filter(Boolean).join("_");
|
||||
}
|
||||
|
||||
export function toExcelExportSearchContextFromSearchForm(
|
||||
values: { sido?: string; gugun?: string; year?: string } | null | undefined,
|
||||
): ExcelExportSearchContext | undefined {
|
||||
if (!values) return undefined;
|
||||
|
||||
const ctpvNm = values.sido?.trim();
|
||||
const sggNm = values.gugun === "ALL" ? "" : (values.gugun?.trim() ?? "");
|
||||
const statsYr = values.year?.trim();
|
||||
|
||||
if (!ctpvNm && !sggNm && !statsYr) return undefined;
|
||||
|
||||
return { ctpvNm, sggNm, statsYr };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user