This commit is contained in:
2026-08-18 18:02:05 +09:00
70 changed files with 944 additions and 570 deletions
+20
View File
@@ -6,6 +6,17 @@
white-space: pre-line;
}
/* RealGrid Excel export — applyDynamicStyles가 class background-color를 읽을 수 있도록 */
.rg-cell-rowtype-biz-sum {
background-color: #ffe9cf !important;
}
.rg-cell-rowtype-summary {
background-color: #feee85 !important;
}
.rg-cell-rowtype-other {
background-color: #fff7bf !important;
}
@layer components {
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell {
@apply !bg-white;
@@ -14,6 +25,15 @@
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly {
@apply !bg-[#f0f0f0] !text-[#666];
}
/* 고정 컬럼 — readonly와 동일한 화면 스타일 (엑셀 export 시 exportStyleName에서 제외) */
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-fixed,
.rg-grid .rg-body .rg-data-cell.rg-cell-fixed {
@apply !bg-[#f0f0f0] !text-[#666];
}
.rg-grid .rg-fixed-body .rg-table .rg-data-row td.rg-cell-fixed,
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-fixed {
@apply !bg-[#f0f0f0] !text-[#666];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-rowtype-curr {
@apply !bg-white !text-[#333];
}
+121 -25
View File
@@ -1,4 +1,4 @@
import { useEffect, useLayoutEffect, useRef, useState, type MutableRefObject } from "react";
import { useEffect, useLayoutEffect, useMemo, useRef, useState, type MutableRefObject } from "react";
import { createPortal } from "react-dom";
import JSZip from "jszip";
@@ -32,33 +32,64 @@ import {
} from "@/components/util/input/verification-result-display";
import {
buildExcelExportFileBaseName,
buildFixedColumnNameSet,
getExcelImportColumnFieldNames,
getNonEditableColumnFieldNames,
hasAdmdstCdColumn,
isExcelImportAdmdstCdInvalid,
mergeExcelImportWithExistingRows,
prepareGridColumnsForExcelExport,
releaseFixedOptionsForExcelExport,
restoreFixedOptionsAfterExcelExport,
restoreGridColumnsAfterExcelExport,
resolveColumnStyleName,
shouldPrepareColumnsForExcelExport,
stripFixedStylesForExport,
} from "@/components/util/gridExcelExport";
import { useTabStore } from "@/store/tab-store";
function mergeCellStyleCallbackResult(
baseStyleName: string | undefined,
extra: CellStyleCallbackResult | undefined,
options: {
mirrorExportStyle?: boolean;
stripFixedForExport?: boolean;
} = {},
): CellStyleCallbackResult {
const { mirrorExportStyle = false, stripFixedForExport = false } = options;
let styleName: string | undefined;
let extraObj: Extract<CellStyleCallbackResult, object> | undefined;
if (!extra) {
return baseStyleName ? { styleName: baseStyleName } : {};
styleName = baseStyleName || undefined;
} else if (typeof extra === "string") {
styleName = [baseStyleName, extra].filter(Boolean).join(" ") || undefined;
} else {
extraObj = extra;
styleName =
[baseStyleName, extra.styleName].filter(Boolean).join(" ") ||
undefined;
}
if (typeof extra === "string") {
const styleName = [baseStyleName, extra].filter(Boolean).join(" ");
return styleName ? { styleName } : {};
}
const styleName = [baseStyleName, extra.styleName].filter(Boolean).join(" ");
return {
...extra,
if (!styleName && !extraObj) return {};
const result: CellStyleCallbackResult = {
...extraObj,
...(styleName ? { styleName } : {}),
};
if (typeof result !== "object") return result;
if (mirrorExportStyle && styleName) {
result.exportStyleName = extraObj?.exportStyleName ?? styleName;
} else if (stripFixedForExport && styleName) {
const stripped = stripFixedStylesForExport(styleName);
if (stripped) result.exportStyleName = stripped;
else delete result.exportStyleName;
}
return result;
}
/** 컬럼 footer.styleName 또는 footerVerificationResult(fieldName) → footer styleName */
@@ -159,19 +190,26 @@ function applyCellStyleCallbacksToColumns(
columns: ColumnConfig[],
rowDataRef: MutableRefObject<Record<string, string | number>[]>,
cellStyleCallbackRef: MutableRefObject<CellStyleCallback | undefined>,
excelExportApplyStylesRef: MutableRefObject<boolean>,
fixedColumnNameSet: Set<string>,
) {
if (!cellStyleCallbackRef.current || !gridView) return;
if (!gridView) return;
const gv = gridView as {
columnByName?: (name: string) => GridColumnWithStyleCallback | null | undefined;
};
if (typeof gv.columnByName !== "function") return;
const hasUserCellStyleCallback = Boolean(cellStyleCallbackRef.current);
columns.forEach((column) => {
const baseStyleName = column.styleName;
const isFixedColumn = fixedColumnNameSet.has(column.name);
// 고정 컬럼은 styleCallback 필수(고정 pane에 column styleName만으로는 클래스 미반영)
if (!isFixedColumn && !hasUserCellStyleCallback) return;
const baseStyleName = resolveColumnStyleName(column, fixedColumnNameSet);
let gridCol: GridColumnWithStyleCallback | null | undefined;
try {
// columnByName은 GridView 인스턴스 메서드 — this 바인딩을 위해 gv에서 직접 호출
gridCol = gv.columnByName!(column.name);
} catch {
return;
@@ -184,17 +222,32 @@ function applyCellStyleCallbacksToColumns(
const fieldName =
dataCell?.dataField ?? dataCell?.fieldName ?? column.fieldName;
const data = rowDataRef.current;
const styleOptions = {
mirrorExportStyle: excelExportApplyStylesRef.current,
stripFixedForExport:
isFixedColumn && !excelExportApplyStylesRef.current,
};
if (itemIndex < 0 || itemIndex >= data.length) {
return mergeCellStyleCallbackResult(baseStyleName, undefined);
return mergeCellStyleCallbackResult(
baseStyleName,
undefined,
styleOptions,
);
}
const row = data[itemIndex] as Record<string, string | number>;
const extra = cellStyleCallbackRef.current?.({
rowIndex: itemIndex,
fieldName,
row,
grid: grid as CellStyleCallbackParams["grid"],
});
return mergeCellStyleCallbackResult(baseStyleName, extra);
const extra = hasUserCellStyleCallback
? cellStyleCallbackRef.current?.({
rowIndex: itemIndex,
fieldName,
row,
grid: grid as CellStyleCallbackParams["grid"],
})
: undefined;
return mergeCellStyleCallbackResult(
baseStyleName,
extra,
styleOptions,
);
};
});
}
@@ -815,6 +868,7 @@ const DataGrid = ({
rowStartNum,
excelExportFileSuffix,
excelExportSearchContext,
excelExportApplyStyles = false,
isExcelImport = true,
excelImportSkipNonEditable = false,
excelImportSewerBzmnCd,
@@ -844,6 +898,10 @@ const DataGrid = ({
fitStyle = GridFitStyle.EVEN,
}: DataGridProps) => {
const { columns, columnLayout } = columnInfo;
const fixedColumnNameSet = useMemo(
() => buildFixedColumnNameSet(columns, columnLayout, fixedColumns),
[columns, columnLayout, fixedColumns],
);
const gridRef = useRef<RealGridReact>(null);
const gridHostRef = useRef<HTMLDivElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
@@ -856,6 +914,7 @@ const DataGrid = ({
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(
cellStyleCallback,
);
const excelExportApplyStylesRef = useRef(excelExportApplyStyles);
const cellTooltipCallbackRef = useRef<CellTooltipCallback | undefined>(
cellTooltipCallback,
);
@@ -885,6 +944,7 @@ const DataGrid = ({
columnsRef.current = columns;
rowDataRef.current = rowData;
cellStyleCallbackRef.current = cellStyleCallback;
excelExportApplyStylesRef.current = excelExportApplyStyles;
cellTooltipCallbackRef.current = cellTooltipCallback;
footerVerificationResultRef.current = footerVerificationResult;
onScrollToBottomRef.current = onScrollToBottom;
@@ -1039,7 +1099,8 @@ useEffect(() => {
}, [onCellDblClick]);
useLayoutEffect(() => {
const gv = gridRef.current?.gridView;
if (!gv || !cellStyleCallback) return;
if (!gv) return;
if (!cellStyleCallback && fixedColumnNameSet.size === 0) return;
const apply = () => {
applyCellStyleCallbacksToColumns(
@@ -1047,6 +1108,8 @@ useEffect(() => {
columns,
rowDataRef,
cellStyleCallbackRef,
excelExportApplyStylesRef,
fixedColumnNameSet,
);
(gv as { refresh?: () => void }).refresh?.();
};
@@ -1058,7 +1121,7 @@ useEffect(() => {
return () => {
window.clearTimeout(timerId);
};
}, [cellStyleCallback, columns, rowData]);
}, [cellStyleCallback, columns, rowData, excelExportApplyStyles, fixedColumnNameSet, fixedColumns]);
const { setMessage: alert } = useAlertStore((state) => state);
const activeTabId = useTabStore((state) => state.activeTabId);
@@ -1156,6 +1219,10 @@ useEffect(() => {
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
? prepareGridColumnsForExcelExport(gridView, columns)
: undefined;
const savedFixedColCount =
!excelExportApplyStyles && fixedColumns
? releaseFixedOptionsForExcelExport(gridView, fixedColumns)
: undefined;
const exportBaseName = buildExcelExportFileBaseName(
menuName,
@@ -1169,7 +1236,14 @@ useEffect(() => {
fileName: `${exportBaseName}.xlsx`,
footer: "hidden",
allColumns: !useExcelColumnVisibilityPrep,
...(excelExportApplyStyles ? { applyDynamicStyles: true } : {}),
done: () => {
if (savedFixedColCount !== undefined) {
restoreFixedOptionsAfterExcelExport(
gridView,
savedFixedColCount,
);
}
if (excelExportVisibilitySnapshot) {
restoreGridColumnsAfterExcelExport(
gridView,
@@ -1187,7 +1261,7 @@ useEffect(() => {
}
}, 100);
}
alert("내보내기가 완료되었습니다.");
// alert("내보내기가 완료되었습니다.");
},
});
};
@@ -1515,8 +1589,17 @@ useEffect(() => {
gv.setColumnProperty?.(
column.name,
"styleName",
column.styleName ?? "",
resolveColumnStyleName(column, fixedColumnNameSet),
);
if (fixedColumnNameSet.has(column.name) && !excelExportApplyStyles) {
gv.setColumnProperty?.(
column.name,
"exportStyleName",
stripFixedStylesForExport(
resolveColumnStyleName(column, fixedColumnNameSet),
) || "",
);
}
applyColumnListEditorProperties(gv, column);
});
if (columns.some((c) => isDropDownEditorColumn(c))) {
@@ -1705,6 +1788,8 @@ useEffect(() => {
columns,
rowDataRef,
cellStyleCallbackRef,
excelExportApplyStylesRef,
fixedColumnNameSet,
);
gridRef.current.gridView.refresh?.();
} catch (e) {
@@ -1794,6 +1879,8 @@ useEffect(() => {
columns,
rowDataRef,
cellStyleCallbackRef,
excelExportApplyStylesRef,
fixedColumnNameSet,
);
// setColumns 호출 후 footer 배열의 valueCallback을 다시 설정
@@ -1847,6 +1934,8 @@ useEffect(() => {
columns,
rowDataRef,
cellStyleCallbackRef,
excelExportApplyStylesRef,
fixedColumnNameSet,
);
liveGridView.refresh?.();
}, 50);
@@ -1930,6 +2019,8 @@ useEffect(() => {
columns,
columnLayout,
fixedColumns,
fixedColumnNameSet,
excelExportApplyStyles,
rowData,
showHeaderCheckbox,
getGridDataRef,
@@ -2732,13 +2823,18 @@ useEffect(() => {
columns,
rowDataRef,
cellStyleCallbackRef,
excelExportApplyStylesRef,
fixedColumnNameSet,
);
gv.refresh();
}, 0);
}}
>
{columns.map((column) => {
const baseStyleName = column.styleName;
const baseStyleName = resolveColumnStyleName(
column,
fixedColumnNameSet,
);
const numberFormat =
column.dataFormat && column.dataFormat.trim() !== ""
? column.dataFormat
+25 -4
View File
@@ -73,6 +73,21 @@ function buildSearchFormValues(
};
}
if (props.defaultRegionAll) {
return {
sido: "ALL",
gugun: "ALL",
year: d?.year || defaultYear.toString(),
order: d?.order || "",
fctName: d?.fctName || "",
bsnmCd: "",
isDetail: false,
isSummary: d?.isSummary || "",
clsfCd: initialClsfCd,
capacityGbn: d?.capacityGbn || "ALL",
};
}
const first = sidoList?.data?.sidoList?.[0];
const useFirstSido = (props.sido ?? defaultSearchFormProps.sido) && first;
@@ -132,6 +147,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
// 최초 진입만 시도 API가 늦게 오는 경우
useEffect(() => {
if (props.defaultRegionAll) return;
if (props.defaultValues?.sido) return;
const first = sidoList?.data?.sidoList?.[0];
if (!first || !mergedProps.sido) return;
@@ -145,7 +161,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
bsnmCd: first.bsnmCd,
},
);
}, [sidoList, props.defaultValues?.sido, mergedProps.sido]);
}, [sidoList, props.defaultValues?.sido, props.defaultRegionAll, mergedProps.sido]);
const sggKey: [string, string, string, string] | null =
values.sido && values.sido !== "ALL"
@@ -177,6 +193,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
// 시도 설정 후 시군구 목록이 오면 첫 번째 항목 자동 선택
useEffect(() => {
if (!mergedProps.gugun) return;
if (props.defaultRegionAll) return;
if (props.defaultValues?.gugun) return;
if (!values.sido || values.sido === "ALL") return;
@@ -196,6 +213,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
values.sido,
mergedProps.gugun,
props.defaultValues?.gugun,
props.defaultRegionAll,
]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
@@ -210,11 +228,13 @@ const SearchForm = (props: SearchFormProps = {}) => {
const handleSidoChange = (value: string) => {
const selectedBsnm =
sidoList?.data?.sidoList?.find((v) => v.sido === value)?.bsnmCd || "";
value === "ALL"
? ""
: sidoList?.data?.sidoList?.find((v) => v.sido === value)?.bsnmCd || "";
setValues((prev) => ({
...prev,
sido: value,
gugun: "",
gugun: props.defaultRegionAll || value === "ALL" ? "ALL" : "",
bsnmCd: selectedBsnm,
}));
};
@@ -403,7 +423,8 @@ const SearchForm = (props: SearchFormProps = {}) => {
)}
{mergedProps.isSummary && (
<div className="flex items-center gap-2">
<Label className="text-label cursor-pointer">
<Label className="text-label cursor-pointer" title="시·군·구 및 읍·면·동 변경에 따라 인구 및 관로 등 공표된 통계수치와 일부 상이할 수 있으니 유의 바랍니다.">
<i className="diveicon di-exclamation-circle mr-1" />
요약표 포함
</Label>
<Select
+119 -29
View File
@@ -17,33 +17,55 @@ import GridTopActionButton from "./GridTopActionButton";
function mergeCellStyleCallbackResult(
baseStyleName: string | undefined,
extra: CellStyleCallbackResult | undefined,
stripFixedForExport = false,
): CellStyleCallbackResult {
let styleName: string | undefined;
let extraObj: Extract<CellStyleCallbackResult, object> | undefined;
if (!extra) {
return baseStyleName ? { styleName: baseStyleName } : {};
styleName = baseStyleName || undefined;
} else if (typeof extra === "string") {
styleName = [extra, baseStyleName].filter(Boolean).join(" ") || undefined;
} else {
extraObj = extra;
styleName =
[extra.styleName, baseStyleName].filter(Boolean).join(" ") ||
undefined;
}
if (typeof extra === "string") {
const styleName = [extra, baseStyleName].filter(Boolean).join(" ");
return styleName ? { styleName } : {};
}
const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" ");
return {
...extra,
if (!styleName && !extraObj) return {};
const result: CellStyleCallbackResult = {
...extraObj,
...(styleName ? { styleName } : {}),
};
if (typeof result === "object" && stripFixedForExport && styleName) {
const stripped = stripFixedStylesForExport(styleName);
if (stripped) result.exportStyleName = stripped;
else delete result.exportStyleName;
}
return result;
}
import SplitDataGrid from "./SplitDataGrid";
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
import {
buildExcelExportFileBaseName,
buildFixedColumnNameSet,
getExcelImportColumnFieldNames,
getNonEditableColumnFieldNames,
hasAdmdstCdColumn,
isExcelImportAdmdstCdInvalid,
mergeExcelImportWithExistingRows,
prepareGridColumnsForExcelExport,
releaseFixedOptionsForExcelExport,
restoreFixedOptionsAfterExcelExport,
restoreGridColumnsAfterExcelExport,
resolveColumnStyleName,
shouldPrepareColumnsForExcelExport,
stripFixedStylesForExport,
} from "@/components/util/gridExcelExport"
import { useTabStore } from "@/store/tab-store"
@@ -313,17 +335,24 @@ const TabDataGrid = ({
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(undefined);
cellStyleCallbackRef.current = cellStyleCallback;
const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], attempt = 0) => {
if (!cellStyleCallbackRef.current) return;
const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], columnLayout: ColumnLayoutItem[] | undefined, fixedCols: number | undefined, attempt = 0) => {
const el = gridRefs.current.get(tabKey);
const gv = el?.gridView;
if (!gv) return;
const fixedColumnNameSet = buildFixedColumnNameSet(columns, columnLayout, fixedCols);
const hasUserCellStyleCallback = Boolean(cellStyleCallbackRef.current);
if (fixedColumnNameSet.size === 0 && !hasUserCellStyleCallback) return;
let attachedCount = 0;
for (const column of columns) {
const isFixedColumn = fixedColumnNameSet.has(column.name);
if (!isFixedColumn && !hasUserCellStyleCallback) continue;
const gridCol = safeColumnByName(gv, column.name);
if (!gridCol) continue;
const baseStyleName = column.styleName;
const baseStyleName = resolveColumnStyleName(column, fixedColumnNameSet);
const stripFixed = isFixedColumn;
gridCol.styleCallback = (grid, dataCell) => {
const dc = dataCell as {
itemIndex?: number;
@@ -335,22 +364,24 @@ const TabDataGrid = ({
const fieldName = dc?.dataField ?? dc?.fieldName ?? column.fieldName;
const data = rowDataMapRef.current.get(tabKey) || [];
if (itemIndex < 0 || itemIndex >= data.length) {
return mergeCellStyleCallbackResult(baseStyleName, undefined);
return mergeCellStyleCallbackResult(baseStyleName, undefined, stripFixed);
}
const row = data[itemIndex] as Record<string, string | number>;
const extra = cellStyleCallbackRef.current?.({
rowIndex: itemIndex,
fieldName,
row,
grid: grid as CellStyleCallbackParams["grid"],
});
return mergeCellStyleCallbackResult(baseStyleName, extra);
const extra = hasUserCellStyleCallback
? cellStyleCallbackRef.current?.({
rowIndex: itemIndex,
fieldName,
row,
grid: grid as CellStyleCallbackParams["grid"],
})
: undefined;
return mergeCellStyleCallbackResult(baseStyleName, extra, stripFixed);
};
attachedCount += 1;
}
if (attachedCount === 0 && attempt < 20) {
setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, attempt + 1), 100);
setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, columnLayout, fixedCols, attempt + 1), 100);
return;
}
@@ -459,6 +490,12 @@ const TabDataGrid = ({
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
? prepareGridColumnsForExcelExport(gridView, columns)
: undefined;
const savedFixedColCount = columnInfoItem.fixedColumns
? releaseFixedOptionsForExcelExport(
gridView,
columnInfoItem.fixedColumns,
)
: undefined;
const exportBaseName = buildExcelExportFileBaseName(
menuName,
@@ -473,6 +510,12 @@ const TabDataGrid = ({
footer: 'hidden',
allColumns: !useExcelColumnVisibilityPrep,
done: () => {
if (savedFixedColCount !== undefined) {
restoreFixedOptionsAfterExcelExport(
gridView,
savedFixedColCount,
);
}
if (excelExportVisibilitySnapshot) {
restoreGridColumnsAfterExcelExport(gridView, excelExportVisibilitySnapshot);
}
@@ -968,6 +1011,11 @@ const TabDataGrid = ({
return null;
}
const { columns, columnLayout } = info;
const fixedColumnNameSet = buildFixedColumnNameSet(
columns,
columnLayout,
info.fixedColumns,
);
const currentRowData = rowDataMap.get(tabKey) || [];
return (
@@ -1192,6 +1240,32 @@ const TabDataGrid = ({
});
}
const tabFixedSet = buildFixedColumnNameSet(
columns,
currentInfo.columnLayout,
currentInfo.fixedColumns,
);
columns.forEach((col) => {
const styleName = resolveColumnStyleName(
col,
tabFixedSet,
);
gridView.setColumnProperty?.(
col.name,
"styleName",
styleName,
);
if (tabFixedSet.has(col.name)) {
gridView.setColumnProperty?.(
col.name,
"exportStyleName",
stripFixedStylesForExport(
styleName,
) || "",
);
}
});
// 각 탭별 ref 초기화
if (!sortDirRefs.current.has(tabKey)) {
sortDirRefs.current.set(tabKey, { current: {} });
@@ -1247,9 +1321,17 @@ const TabDataGrid = ({
const onSortingHandler = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef, findGridElement);
(gridView as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = enableSort ? onSortingHandler : () => false;
if (cellStyleCallbackRef.current) {
if (
cellStyleCallbackRef.current ||
tabFixedSet.size > 0
) {
setTimeout(() => {
attachCellStyleCallbacksForTab(tabKey, columns);
attachCellStyleCallbacksForTab(
tabKey,
columns,
currentInfo.columnLayout,
currentInfo.fixedColumns,
);
}, 100);
}
}, 150);
@@ -1262,7 +1344,16 @@ const TabDataGrid = ({
layout={columnLayout}
rows={currentRowData}
>
{columns.map((column) => (
{columns.map((column) => {
const resolvedStyleName =
resolveColumnStyleName(
column,
fixedColumnNameSet,
) ||
(column.editable === false
? "rg-cell-readonly"
: undefined);
return (
<RGDataColumn
key={column.name}
name={column.name}
@@ -1271,11 +1362,9 @@ const TabDataGrid = ({
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.00' })}
header={column.header}
editable={column.editable}
{...(column.styleName != null
? { styleName: column.styleName }
: column.editable === false
? { styleName: 'rg-cell-readonly' as const }
: {})}
{...(resolvedStyleName != null
? { styleName: resolvedStyleName }
: {})}
{...(column.visible === false && { visible: false })}
footer={column.footer && !Array.isArray(column.footer) ? {
text: (column.footer as { text?: string }).text,
@@ -1285,7 +1374,8 @@ const TabDataGrid = ({
mergeRule={column.mergeCells ? { criteria: column.mergeGroupField ? `values['${column.mergeGroupField}'] + value` : 'value' } : undefined}
sortable={enableSort && !column.sortdisable}
/>
))}
);
})}
</RealGridReact>
) : null}
</div>
@@ -1022,7 +1022,7 @@ const ExcretaTreatmentFacilityInp = () => {
onValuesChange={setSearchValues}
/>
{showActionToolbarCard && (
<div className="card !py-0">
<div className="card !py-0">
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
<VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog}
@@ -1033,18 +1033,18 @@ const ExcretaTreatmentFacilityInp = () => {
<LastYearDataButton
getLastYearDataFunc={() => void getLastYearDataFunc()}
/>
<SubmitButton submitFunc={submitFunc} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{statusInfo?.laeSbmsnSttsCd === "2" && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div>
</div>
</div>
</div>
)}
{showArticleToolbarCard && (
<div className="card !py-0">
<div className="card !py-0">
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
{showNotApplicable && (
<NotApplicableCheckButton
@@ -1052,10 +1052,10 @@ const ExcretaTreatmentFacilityInp = () => {
setIsCheck={setIsNotApplicable}
/>
)}
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div>
</div>
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</div>
</div>
)}
{isLoading && (
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10 [data-theme='black']:bg-transparent">
@@ -1064,14 +1064,14 @@ const ExcretaTreatmentFacilityInp = () => {
</div>
</div>
)}
<DataGrid
<DataGrid
key={canEditGrid ? "enterable" : "readonly"}
getGridRef={gridRef as RefObject<RealGridReact | null>}
columnInfo={columnInfo}
rowStartNum={ExcretaTreatFactInpColumn.rowStartNum}
fixedColumns={ExcretaTreatFactInpColumn.fixedColumns}
rowData={rowData}
setRowData={setRowData}
rowData={rowData}
setRowData={setRowData}
getGridDataRef={gridGetRowsRef}
showHeaderCheckbox
statusInfo={statusInfo}
@@ -77,10 +77,10 @@ import { getUserInfo } from "../../../../../../packages/common/components/src/ap
import SubmitDailog from "./SubmitDailog";
const SewageExcretaRelatedInpTabList = [
PublicInstitutionSewerIndustryInpColumn,
PublicInstitutionExcretaIndustryInpColumn,
ExcretaRelatedIndustryInpColumn,
const SewageExcretaRelatedInpTabList = [
PublicInstitutionSewerIndustryInpColumn,
PublicInstitutionExcretaIndustryInpColumn,
ExcretaRelatedIndustryInpColumn,
PrivateSewageTreatRelatedIndustryInpColumn,
] as const;
@@ -1967,8 +1967,8 @@ const SewageExcretaRelatedInp = () => {
onValuesChange={setSearchValues}
/>
{showActionToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={
@@ -1980,19 +1980,19 @@ const SewageExcretaRelatedInp = () => {
<LastYearDataButton
getLastYearDataFunc={() => void lastYearDataFunc()}
/>
<SubmitButton submitFunc={submitFunc} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div>
</div>
</div>
)}
{showSecondaryToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
{showNotApplicableForActiveTab && (
<NotApplicableCheckButton
isCheck={isNotApplicableForActiveTab}
@@ -2002,11 +2002,11 @@ const SewageExcretaRelatedInp = () => {
{showArticleEditButtons && (
<>
<AddArticleButton addArticleFunc={() => void addArticleFunc()} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</>
)}
</div>
</div>
</div>
)}
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -2126,7 +2126,7 @@ const SewageExcretaRelatedInp = () => {
false,
);
}}
/>
/>
</>
);
};
@@ -181,10 +181,10 @@ function mapListToGridRows<T extends { verificationResult?: SewageExcretaSlurgeV
});
}
const SewageExcretaSlurgeInpTabList = [
SlurgeProcessingFacStatusInpColumn,
DigestorOperatingStatusInpColumn,
SewageSlurgeProcessingFacStatusInpColumn,
const SewageExcretaSlurgeInpTabList = [
SlurgeProcessingFacStatusInpColumn,
DigestorOperatingStatusInpColumn,
SewageSlurgeProcessingFacStatusInpColumn,
ExcretaSlurgeProcessingStatusInpColumn,
] as const;
@@ -2071,8 +2071,8 @@ const SewageExcretaSlurgeInp = () => {
onValuesChange={setSearchValues}
/>
{showActionToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={
@@ -2084,19 +2084,19 @@ const SewageExcretaSlurgeInp = () => {
<LastYearDataButton
getLastYearDataFunc={() => void lastYearDataFunc()}
/>
<SubmitButton submitFunc={submitFunc} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div>
</div>
</div>
</div>
)}
{showSecondaryToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
{showNotApplicableForActiveTab && (
<NotApplicableCheckButton
isCheck={isNotApplicableForActiveTab}
@@ -2105,12 +2105,12 @@ const SewageExcretaSlurgeInp = () => {
)}
{showArticleEditButtons && (
<>
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</>
)}
</div>
</div>
</div>
</div>
)}
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -217,6 +217,18 @@ const getManholeLastDataYear = () => {
return Math.max(2014, lastDataYear);
};
/** 2-3 관종별: verificationResult.prlgWhol → 총계 계(pipeByPrstWhol) */
function remapPipeTypePrlgWholVerification(
result?: SeweragePipeInputVerificationResult | null,
): SeweragePipeInputVerificationResult | null | undefined {
if (!result) return result;
const fromAlias = result.prlgWhol;
if (!Array.isArray(fromAlias) || fromAlias.length === 0) return result;
const existing = result.pipeByPrstWhol;
if (Array.isArray(existing) && existing.length > 0) return result;
return { ...result, pipeByPrstWhol: fromAlias };
}
/** 관종별 신 응답(yearlyStatList) -> 그리드 y{year}* 필드로 정규화 */
function normalizePipeTypeStatusRow(
row: SeweragePipeInputPipeTypeStatusItem,
@@ -227,6 +239,7 @@ function normalizePipeTypeStatusRow(
yearlyStatList,
lyngPrlgWhol,
dmltnPrlgWhol,
verificationResult,
...rest
} = row;
const yearFields: Record<string, number> = {};
@@ -254,6 +267,7 @@ function normalizePipeTypeStatusRow(
lyngPrlg: Number(row.lyngPrlg ?? lyngPrlgWhol ?? 0),
dmltnPrlg: Number(row.dmltnPrlg ?? dmltnPrlgWhol ?? 0),
...yearFields,
verificationResult: remapPipeTypePrlgWholVerification(verificationResult) ?? undefined,
};
}
@@ -787,7 +801,11 @@ const SewagePipeInp = () => {
patchVerificationFailedByTab(prev, tabKey, commonInfo),
);
setFooterVerificationByTab((prev) =>
patchFooterVerificationByTab(prev, tabKey, commonInfo),
patchFooterVerificationByTab(prev, tabKey, {
verificationResult:
remapPipeTypePrlgWholVerification(commonInfo.verificationResult) ??
null,
}),
);
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
if (updateLastSearchedValues) setLastSearchedValues(values);
@@ -1183,6 +1183,7 @@ const SewagePipeMaintainInp = () => {
toGridColumnInfo(info)
}
excelExportFileSuffix={info.tabLabel || tabKey}
fixedColumns={info.fixedColumns}
rowData={rowData}
setRowData={(updater) => {
setRowDataMap((prev) => {
@@ -1258,8 +1258,8 @@ const SewerageFinanceInp = () => {
onValuesChange={setSearchValues}
/>
{showActionToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={
@@ -1271,7 +1271,7 @@ const SewerageFinanceInp = () => {
<LastYearDataButton
getLastYearDataFunc={() => void lastYearDataFunc()}
/>
<SubmitButton submitFunc={submitFunc} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
@@ -1143,8 +1143,8 @@ const SewerageServiceLevelInp = () => {
onValuesChange={setSearchValues}
/>
{showActionToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={
@@ -1156,19 +1156,19 @@ const SewerageServiceLevelInp = () => {
<LastYearDataButton
getLastYearDataFunc={() => void getLastYearDataFunc()}
/>
<SubmitButton submitFunc={submitFunc} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div>
</div>
</div>
</div>
)}
{showSecondaryToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
{showNotApplicableForResidentTab && (
<NotApplicableCheckButton
isCheck={isNotApplicable}
@@ -88,11 +88,11 @@ import { getUserInfo } from "../../../../../../packages/common/components/src/ap
import SubmitDailog from "./SubmitDailog";
const WaterReuseInpTabList = [
PublicSewageTreatWaterReuseInpColumn,
RecycledWaterStatusInpColumn,
RainFallUtilFactStatusInpColumn,
TreatWaterReuseDesignConstStatusInpColumn,
const WaterReuseInpTabList = [
PublicSewageTreatWaterReuseInpColumn,
RecycledWaterStatusInpColumn,
RainFallUtilFactStatusInpColumn,
TreatWaterReuseDesignConstStatusInpColumn,
TreatWaterReuseBusinessApprStatusInpColumn,
] as const;
@@ -2089,8 +2089,8 @@ const WaterReuseInp = () => {
onValuesChange={setSearchValues}
/>
{showActionToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<VerificationResultButton
VerificationResultButtonFunc={openVerificationHistoryDialog}
isVerificationFailed={
@@ -2102,19 +2102,19 @@ const WaterReuseInp = () => {
<LastYearDataButton
getLastYearDataFunc={() => void getLastYearDataFunc()}
/>
<SubmitButton submitFunc={submitFunc} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div>
</div>
</div>
</div>
)}
{showSecondaryToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
{showNotApplicableForActiveTab && (
<NotApplicableCheckButton
isCheck={isNotApplicableForActiveTab}
@@ -2128,12 +2128,12 @@ const WaterReuseInp = () => {
)}
{isArticleEditTab && showEnterableToolbarButtons && (
<>
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
<AddArticleButton addArticleFunc={addArticleFunc} />
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
</>
)}
</div>
</div>
</div>
</div>
)}
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -31,7 +31,7 @@ import type {
ColumnLayoutItem,
} from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
import { SewagePipeColumnInfoListRvw, getST00020003RvwConfig, getST00020005RvwConfig } from "@/components/util/column/review/SewagePipeRvwColumn";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
import { toGridRows } from "@/components/util/grid/gridFunction";
@@ -148,15 +148,25 @@ const SewagePipeRvw = () => {
[tabStatsStatusInfoMap, activeTab],
);
const columnInfo = useMemo<ColumnInfoList[]>(
() =>
SewagePipeColumnInfoListRvw.map((tab) => ({
...tab,
columns: (tab.columns ?? []).map((col) => cloneColumnConfig(col)),
columnLayout: (tab.columnLayout ?? []).map((item) =>
cloneColumnLayoutItem(item),
),
})),
[],
() => {
const statsYr = lastSearchValues?.year ?? new Date().getFullYear();
const st00020003Config = getST00020003RvwConfig(statsYr);
const st00020005Config = getST00020005RvwConfig(statsYr);
return SewagePipeColumnInfoListRvw.map((tab) => {
const source =
tab.tabKey === "ST00020003" ? st00020003Config
: tab.tabKey === "ST00020005" ? st00020005Config
: tab;
return {
...source,
columns: (source.columns ?? []).map((col) => cloneColumnConfig(col as Parameters<typeof cloneColumnConfig>[0])),
columnLayout: (source.columnLayout ?? []).map((item) =>
cloneColumnLayoutItem(item as ColumnLayoutItem),
),
};
});
},
[lastSearchValues?.year],
);
const reviewCellStyleCallbackByTab = useMemo(
@@ -84,7 +84,7 @@ const ExcretaTreatmentFacilityTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -105,6 +105,7 @@ const ExcretaTreatmentFacilityTot = () => {
setRowData={setRowData} // 필수 프로퍼티 에러 해결
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={config.fixedColumns}
enableSort={false}
@@ -132,7 +132,7 @@ const PrivateSewageFacilityTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{/* 하수관로 규칙과 100% 싱크 맞춘 중앙 정렬 소형 팝업 로딩 바 고정 */}
{isLoading && (
@@ -184,6 +184,7 @@ const PrivateSewageFacilityTot = () => {
}}
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
enableSort={false}
@@ -52,7 +52,7 @@ const PublicSewageFacilityTot = () => {
return (
<>
<SearchForm isDetail={true} capacityGbn={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" />
<SearchForm isDetail={true} capacityGbn={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
@@ -65,6 +65,7 @@ const PublicSewageFacilityTot = () => {
columnInfo={PublicSewageFacilityColumn}
rowStartNum={2}
isExcelImport={false}
excelExportApplyStyles
fixedColumns={2}
rowData={rowData}
setRowData={setRowData}
@@ -84,7 +84,7 @@ const PumpingStationTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -105,6 +105,7 @@ const PumpingStationTot = () => {
setRowData={setRowData}
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={config.fixedColumns}
enableSort={false}
@@ -102,7 +102,7 @@ const SewageExcretaRelatedTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -140,6 +140,7 @@ const SewageExcretaRelatedTot = () => {
}}
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
enableSort={false}
@@ -146,7 +146,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -188,6 +188,7 @@ const loadGridData = async (tabKey: string, values: SearchFormValues): Promise<b
}}
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
enableSort={false}
@@ -133,7 +133,7 @@ const SewagePipeMaintainTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
@@ -186,6 +186,7 @@ const SewagePipeMaintainTot = () => {
}}
isExcelImport={false}
isExcelExport
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
enableSort={false}
@@ -8,7 +8,7 @@ import SearchForm from "@/components/module/SearchForm";
import DataGrid from "@/components/module/DataGrid";
import type { ColumnInfo } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewagePipeColumnInfoListTot, getST00020003Config } from "@/components/util/column/total/SewagePipeTotColumn";
import { SewagePipeColumnInfoListTot, getST00020003Config, getST00020005Config } from "@/components/util/column/total/SewagePipeTotColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
import type { SewagePipeTotalRequest } from "@/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface";
import CellColorInfo from "./CellColorInfo";
@@ -178,7 +178,7 @@ const SewagePipeTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
@@ -204,11 +204,12 @@ const SewagePipeTot = () => {
{columnInfo.map((info) => {
const tabKey = info.tabKey;
// ★ [동적 처리] ST00020003인 경우 함수를 통해 컬럼 정보를 실시간으로 생성
// ★ [동적 처리] ST00020003·ST00020005는 함수를 통해 컬럼 정보를 실시간으로 생성
const currentYear = lastSearchedValuesRef.current?.year || new Date().getFullYear();
const finalTabConfig = tabKey === "ST00020003"
? getST00020003Config(currentYear)
: info;
const finalTabConfig =
tabKey === "ST00020003" ? getST00020003Config(currentYear) :
tabKey === "ST00020005" ? getST00020005Config(currentYear) :
info;
if (!finalTabConfig.columns || !finalTabConfig.columnLayout) {
return null;
@@ -239,6 +240,7 @@ const SewagePipeTot = () => {
}}
isExcelImport={false}
isExcelExport
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={finalTabConfig.fixedColumns}
enableSort={false}
@@ -118,7 +118,7 @@ const SewageReservoirPumpTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -168,6 +168,7 @@ const SewageReservoirPumpTot = () => {
}}
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
enableSort={false}
@@ -199,6 +199,7 @@ const SewerageAggregateSummary = () => {
<SearchForm
isSummary={true}
sidoSelectText="전체"
defaultRegionAll
searchButtonText="내려받기"
onSubmit={(values) => void handleDownload(values)}
isReset={false}
@@ -144,7 +144,7 @@ const SewerageFinanceTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -195,6 +195,7 @@ const SewerageFinanceTot = () => {
}}
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
enableSort={false}
@@ -51,7 +51,7 @@ const SeweragePenetrationRateTot = () => {
return (
<>
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} sidoSelectText="전체" defaultRegionAll />
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
@@ -62,6 +62,7 @@ const SeweragePenetrationRateTot = () => {
columnInfo={SeweragePentrationRateTotColumn}
rowStartNum={2}
isExcelImport={false}
excelExportApplyStyles
fixedColumns={1}
rowData={rowData}
setRowData={setRowData}
@@ -91,7 +91,7 @@ const SewerageServiceLevelTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
@@ -129,6 +129,7 @@ const SewerageServiceLevelTot = () => {
}}
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
enableSort={false}
@@ -157,7 +157,7 @@ const WaterReuseTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" defaultRegionAll />
{/* 정중앙 5rem 크기 고정 격리형 로딩 애니메이션 */}
{isLoading && (
@@ -207,6 +207,7 @@ const WaterReuseTot = () => {
}}
isExcelImport={false}
isExcelExport={true}
excelExportApplyStyles
showHeaderCheckbox={false}
fixedColumns={info.fixedColumns}
enableSort={false}
@@ -16,6 +16,12 @@ type OperatorItem = {
opertKornNm: string;
};
// opertSeCd 별 operators 목록을 전역 캐싱해서, 수식 블록이 여러 개여도 중복 요청이 발생하지 않게 한다.
const operatorsCache: Map<
string,
OperatorItem[] | Promise<OperatorItem[]>
> = new Map();
interface OperatorsComponentsProps {
cmprOpert?: string | null;
onValueChange?: (value: string) => void;
@@ -37,13 +43,30 @@ const OperatorsComponents = ({
}, [cmprOpert]);
useEffect(() => {
const fetchOperators = async () => {
const response = await generalverificationApiService.getVrfcOperators(opertSeCd);
if (response.status === 'success' && response.data) {
setOperators(response.data);
}
};
void fetchOperators();
const cached = operatorsCache.get(opertSeCd);
if (Array.isArray(cached)) {
setOperators(cached);
return;
}
const promise =
cached ??
(async (): Promise<OperatorItem[]> => {
const response =
await generalverificationApiService.getVrfcOperators(
opertSeCd,
);
if (response.status === "success" && response.data) {
return response.data as OperatorItem[];
}
return [];
})();
operatorsCache.set(opertSeCd, promise);
void promise.then((items) => {
operatorsCache.set(opertSeCd, items);
setOperators(items);
});
}, [opertSeCd]);
// onValueChange가 있으면 부모가 제어(controlled), 없으면 내부 state로 표시
@@ -354,6 +354,8 @@ export type CellStyleCallbackResult =
| string
| {
styleName?: string;
/** RealGrid 엑셀 export 전용 스타일 (applyDynamicStyles 시 사용) */
exportStyleName?: string;
editable?: boolean;
readOnly?: boolean;
};
@@ -410,6 +412,8 @@ export type DataGridProps = {
excelImportSewerBzmnCd?: string;
/** 엑셀보내기 파일명에 조회 시도·시군구·연도 접미사 추가 */
excelExportSearchContext?: ExcelExportSearchContext;
/** true면 엑셀 export 시 cellStyleCallback 색상·고정 컬럼 배경을 모두 반영 (집계 total 화면용) */
excelExportApplyStyles?: boolean;
isExcelExport?: boolean;
onRowsChange?: (rows: Record<string, string | number>[]) => void;
fixedColumns?: number; // 고정할 컬럼 개수 (좌측부터)
@@ -34,10 +34,12 @@ export type SearchFormProps = {
defaultValues?: SearchFormValues;
searchButtonText?: string;
isReset?: boolean;
/** true면 시도·시군구 기본값을 계정 첫 항목이 아니라 「전체」로 둠 (집계표 등) */
defaultRegionAll?: boolean;
};
export const defaultSearchFormProps: Required<
Omit<SearchFormProps, "onSubmit" | "onReset" | "onValuesChange" | "defaultValues" | "sidoSelectText" | "searchButtonText">
Omit<SearchFormProps, "onSubmit" | "onReset" | "onValuesChange" | "defaultValues" | "sidoSelectText" | "searchButtonText" | "defaultRegionAll">
> = {
sido: true,
gugun: true,
@@ -8,11 +8,11 @@ import { ValueType } from "realgrid";
export const ExcretaTreatFactInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
// 기본 정보
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' }, styleName: 'rg-cell-fixed' },
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '소재지' } },
{ name: 'telNo', fieldName: 'telNo', header: { text: '전화번호' } },
@@ -8,11 +8,11 @@ import { footerDecimal1, footerDecimal3,footerInt } from "@/components/util/grid
export const PublicSewageFactColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-fixed' },
// 시설명
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '시설명(500m³/일 이상/미만)' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '시설명(500m³/일 이상/미만)' }, editable: false, styleName: 'rg-cell-fixed' },
// 소재지
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '소재지' }, editable: false, styleName: 'rg-cell-readonly' },
@@ -35,12 +35,12 @@ function dropdownColumn(
export const PumpingStationInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
// 기본 정보
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' } },
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' }, styleName: 'rg-cell-fixed' },
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' }, styleName: 'rg-cell-fixed' },
dropdownColumn(
{ name: 'pumpSeNm', fieldName: 'pumpSeNm', header: { text: '구분(합류식 분류식)' } },
PUMPING_STATION_PUMP_SE_VALUES,
@@ -7,9 +7,9 @@ import { ValueType } from "realgrid";
export const SeweragePenetrationRateInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-fixed' },
// 수계 및 권역
{ name: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' }, editable: false, styleName: 'rg-cell-readonly' },
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const PrivateSewageDesigStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 개인하수도관리지역공고현황
{ name: 'indvSwagePbancRgnCnt', fieldName: 'indvSwagePbancRgnCnt', header: { text: '공고지역(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const SepticTankStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 총 시설 수 — (B+C)/2 + (E+F)/2 = A + D
{
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const SewageTreatFactStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 총계
{
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const ExcretaRelatedIndustryInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 업체수(개소)
{ name: 'bzentyCnt', fieldName: 'bzentyCnt', header: { text: '업체수(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const PrivateSewageTreatRelatedIndustryInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 개인하수처리시설 설계·시공업
{ name: 'designMdsrCnt', fieldName: 'designMdsrCnt', header: { text: '업소수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const PublicInstitutionExcretaIndustryInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 직제
{ name: 'ognzNm', fieldName: 'ognzNm', header: { text: '직 제' } },
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const PublicInstitutionSewerIndustryInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 직제
{ name: 'ognzNm', fieldName: 'ognzNm', header: { text: '직 제' } },
@@ -49,11 +49,11 @@ function dropdownColumn(
export const DigestorOperatingStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 하수처리장명
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false },
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false, styleName: 'rg-cell-fixed' },
// 소화조 운영현황
dropdownColumn(
@@ -5,11 +5,11 @@ import { ValueType } from "realgrid";
export const ExcretaSlurgeProcessingStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 분뇨처리장명
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '분뇨 처리장명' }, editable: false },
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '분뇨 처리장명' }, editable: false, styleName: 'rg-cell-fixed' },
// 발생량
{
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const SewageSlurgeProcessingFacStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
// 찌꺼기처리시설명
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '찌꺼기처리시설명' } },
@@ -5,11 +5,11 @@ import { ValueType } from "realgrid";
export const SlurgeProcessingFacStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 하수처리장명
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, editable: false, styleName: 'rg-cell-fixed' },
// 찌꺼기발생량(톤/년, 탈수기준)
{
@@ -7,12 +7,12 @@ import { ValueType } from "realgrid";
export const FactInstallStatusInpColumn = {
columns: [
// 행정구역 (ctpvNm은 commonInfo에서 주입)
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 하수처리장 정보
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, footer: { text: '합계' }, editable: false },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리시설용량(m³/일)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
// 처리구역면적(km²)
@@ -136,21 +136,21 @@ export const ManholeStatusInpColumn = {
header: { text: "시도" },
footer: { text: "합계" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "sggNm",
fieldName: "sggNm",
header: { text: "시군구" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "admdstNm",
fieldName: "admdstNm",
header: { text: "행정구역명" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "emphsMngSeNm",
@@ -12,27 +12,28 @@ export const PipeDiameterStatusInpColumn = {
header: { text: "시도" },
footer: { text: "합계" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "sggNm",
fieldName: "sggNm",
header: { text: "시군구" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "pipeSeNm",
fieldName: "pipeSeNm",
header: { text: "관종" },
editable: false,
styleName: 'rg-cell-fixed',
},
{
name: "admdstNm",
fieldName: "admdstNm",
header: { text: "행정구역명" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "pdmtByTsmWhol",
@@ -90,14 +90,14 @@ export const PipeTypeStatusInpColumn = {
header: { text: "시도" },
footer: { text: "합계" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "sggNm",
fieldName: "sggNm",
header: { text: "시군구" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "pipeSeNm",
@@ -105,13 +105,14 @@ export const PipeTypeStatusInpColumn = {
header: { text: "관종" },
footer: { text: "합계" },
editable: false,
styleName: "rg-cell-fixed",
},
{
name: "admdstNm",
fieldName: "admdstNm",
header: { text: "행정구역명" },
editable: false,
styleName: "rg-cell-readonly",
styleName: "rg-cell-fixed",
},
{
name: "pipeByPrstWhol",
@@ -6,9 +6,9 @@ import { ValueType } from "realgrid";
export const RegionalFactStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-fixed' },
// 기본 정보
{ name: 'goalYr', fieldName: 'goalYr', header: { text: '하수정비기본계획목표년도' } },
@@ -6,8 +6,8 @@ import { footerDecimal1, footerDecimal2, footerInt } from "@/components/util/gri
export const PipeDredgingStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구명' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구명' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-fixed' },
// 하수관로 총 시설연장
{ name: 'wholFcltPrlg', fieldName: 'wholFcltPrlg', header: { text: '하수관로총시설연장(m)' }, editable: false, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00', styleName: 'rg-cell-readonly' },
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const RepairStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구명' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구명' }, editable: false, footer: { text: '합계' }, styleName: 'rg-cell-fixed' },
// 개·보수관로 > 전체보수(m)
{
@@ -31,10 +31,10 @@ function dropdownColumn(
// 하수도 통계 관리 > 04.하수저류시설 및 빗물펌프장 > tab 2: 빗물펌프장
export const RainfallPumpStationInpColumn = {
columns: [
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' }, styleName: 'rg-cell-fixed' },
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '위치' } },
dropdownColumn(
@@ -50,11 +50,11 @@ function oxYnDropdownColumn(
export const SewageReservoirInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
// 기본 정보
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' }, styleName: 'rg-cell-fixed' },
{ name: 'fcltPstnCn', fieldName: 'fcltPstnCn', header: { text: '위치' } },
{
name: 'oprtngStrtYmd',
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const CauserBurdenInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 부과액
{ name: 'levyEvntcsCnt', fieldName: 'levyEvntcsCnt', header: { text: '건수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const SewerageChargeInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 요금
{ name: 'anlLevyQnt', fieldName: 'anlLevyQnt', header: { text: '연간조정량(m³)(A)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const SewerageFinanceSettleInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 재정별 사업비
{
@@ -5,8 +5,8 @@ import { ValueType } from "realgrid";
export const SewerageUsageFeeInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 총계 > 부과액
{
@@ -15,8 +15,8 @@ const COMPLAINT_TYPE_COUNT_FIELDS = [
export const ComplaintInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 하수처리인구(명)
{ name: 'swtrmtPpltnCnt', fieldName: 'swtrmtPpltnCnt', header: { text: '하수처리인구(명)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
@@ -16,12 +16,12 @@ const RESIDENT_FRIENDLY_FACILITY_AREA_FIELDS = [
export const ResidentFriendlyFactInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 시설 기본 정보
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'fcltCpct', fieldName: 'fcltCpct', header: { text: '시설용량(m³/일)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '처리장명' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'fcltCpct', fieldName: 'fcltCpct', header: { text: '시설용량(m³/일)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-fixed' },
// 처리장면적(㎡)
{ name: 'tmplntArea', fieldName: 'tmplntArea', header: { text: '처리장면적(㎡)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
@@ -54,8 +54,8 @@ const PUBLIC_SEWAGE_REUSE_USE_QNT_SUM_FIELDS = [
export const PublicSewageTreatWaterReuseInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-fixed' },
// 하수처리장
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '시설명' }, editable: false, styleName: 'rg-cell-readonly' },
@@ -56,8 +56,8 @@ function dropdownColumn(
export const RainFallUtilFactStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
// 시설명
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '시설명' } },
@@ -63,8 +63,8 @@ function dropdownColumn(
export const RecycledWaterStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
// 건축물명/개발사업명
{ name: 'fcltNm', fieldName: 'fcltNm', header: { text: '건축물명/개발사업명' } },
@@ -2,8 +2,8 @@
export const TreatWaterReuseBusinessApprStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
// 상호(명칭)
{ name: 'bplcNm', fieldName: 'bplcNm', header: { text: '상호(명칭)' } },
@@ -2,8 +2,8 @@
export const TreatWaterReuseDesignConstStatusInpColumn = {
columns: [
// 행정구역
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-fixed' },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, styleName: 'rg-cell-fixed' },
// 상호(명칭)
{ name: 'bplcNm', fieldName: 'bplcNm', header: { text: '상호(명칭)' } },
@@ -315,129 +315,15 @@ const rawList = [
]
},
// ST00020003 관종별 현황은 연도 범위가 동적이므로 rawList에 포함하지 않고
// getST00020003RvwConfig() 로 런타임에 생성합니다.
{
tabKey: 'ST00020003',
tabKey: 'ST00020003_PLACEHOLDER',
tabLabel: '관종별 현황',
rowStartNum: 2,
fixedColumns: 2,
columns: [
{
name: 'ctpvNm',
fieldName: 'ctpvNm',
header: { text: '시도' },
mergeCells: true,
sortdisable: true,
footer: [
{
valueCallback: (grid: GridView) => {
const divisionIndex = 0;
const dataSource = grid.getDataSource();
const uniqueValues = new Set<string>();
for (let i = 0; i < dataSource.getRowCount(); i++) {
const val = dataSource.getValue(i, 'displayYear');
if (val) uniqueValues.add(String(val));
}
const divisionValues = Array.from(uniqueValues);
return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계';
}
},
{
valueCallback: (grid: GridView) => {
const divisionIndex = 1;
const dataSource = grid.getDataSource();
const uniqueValues = new Set<string>();
for (let i = 0; i < dataSource.getRowCount(); i++) {
const val = dataSource.getValue(i, 'displayYear');
if (val) uniqueValues.add(String(val));
}
const divisionValues = Array.from(uniqueValues);
return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계';
}
}
]
},
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true },
{ name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, mergeCells: true },
{
name: 'displayYear',
fieldName: 'displayYear',
header: { text: '구분' },
sortdisable: true,
footer: [
{
valueCallback: (grid: GridView) => {
const divisionIndex = 0;
const dataSource = grid.getDataSource();
const uniqueValues = new Set<string>();
for (let i = 0; i < dataSource.getRowCount(); i++) {
const val = dataSource.getValue(i, 'displayYear');
if (val) uniqueValues.add(String(val));
}
return Array.from(uniqueValues)[divisionIndex] ? Array.from(uniqueValues)[divisionIndex] + '합계' : '';
}
},
{
valueCallback: (grid: GridView) => {
const divisionIndex = 1;
const dataSource = grid.getDataSource();
const uniqueValues = new Set<string>();
for (let i = 0; i < dataSource.getRowCount(); i++) {
const val = dataSource.getValue(i, 'displayYear');
if (val) uniqueValues.add(String(val));
}
return Array.from(uniqueValues)[divisionIndex] ? Array.from(uniqueValues)[divisionIndex] + '합계' : '';
}
}
]
},
{ name: 'pipeByPrstWhol', fieldName: 'pipeByPrstWhol', header: { text: '계' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
{ name: 'lyngPrlg', fieldName: 'lyngPrlg', header: { text: '매설연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
{ name: 'dmltnPrlg', fieldName: 'dmltnPrlg', header: { text: '철거연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
...Array.from({ length: totalCount }, (_, i) => {
const dtoNum = i + 1;
const wholField = `yy${dtoNum}PrlgWhol`;
const lyngField = `yy${dtoNum}LyngPrlg`;
const dmltnField = `yy${dtoNum}DmltnPrlg`;
return [
{ name: wholField, fieldName: wholField, header: { text: '계' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
{ name: lyngField, fieldName: lyngField, header: { text: '매설연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' },
{ name: dmltnField, fieldName: dmltnField, header: { text: '철거연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0' }
];
}).flat(),
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },
], columnLayout: [
{
name: "areaGroup",
header: { text: "행정구역" },
items: [
{ column: "ctpvNm", footerUserSpans: [{ colspan: 3 }, { colspan: 3 }] },
"sggNm",
"swrgPipeNm",
],
},
"displayYear",
{ name: "summaryGroup", header: { text: "총계" }, items: ["pipeByPrstWhol", "lyngPrlg", "dmltnPrlg"] },
...Array.from({ length: totalCount }, (_, i) => {
// const dtoNum = totalCount - i;
const dtoNum = i + 1;
const yearLabel = i === 0 ? "1996년이전" : `${startYear + (i - 1)}년`;
return {
name: `group_${yearLabel}`,
header: { text: yearLabel },
items: [`yy${dtoNum}PrlgWhol`, `yy${dtoNum}LyngPrlg`, `yy${dtoNum}DmltnPrlg`],
};
}),
"rmrk",
],
columns: [],
columnLayout: [],
},
{
@@ -553,8 +439,10 @@ const rawList = [
]
},
// ST00020005 맨홀 현황은 연도 범위가 동적이므로 rawList에 포함하지 않고
// getST00020005RvwConfig() 로 런타임에 생성합니다.
{
tabKey: "ST00020005",
tabKey: "ST00020005_PLACEHOLDER",
tabLabel: "맨홀 현황",
rowStartNum: 3,
fixedColumns: 3,
@@ -858,11 +746,177 @@ const rawList = [
},
];
export const SewagePipeColumnInfoListRvw = rawList.map(tab => ({
...tab,
columns: tab.columns.map(col => ({
...col,
editable: false,
styleName: "rg-cell-readonly"
}))
}));
/** ST00020003 관종별 현황 — 검토 화면용 동적 컬럼 생성
* 필드명: prlg_whol_{year}, lyng_prlg_{year}, dmltn_prlg_{year}
* (total의 getST00020003Config와 동일 필드, oldPipeRt 제외)
*/
export const getST00020003RvwConfig = (statsYr: string | number) => {
const rvwStartYear = 1995;
const rvwEndYear = Number(statsYr) || dayjs().year();
const yearRange = Array.from(
{ length: rvwEndYear - rvwStartYear + 1 },
(_, i) => rvwStartYear + i,
);
return {
tabKey: 'ST00020003',
tabLabel: '관종별 현황',
rowStartNum: 2,
fixedColumns: 2,
columns: [
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
{ name: 'swrgPipeNm', fieldName: 'swrgPipeNm', header: { text: '관종' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true },
{ name: 'pipeByPrstWhol', fieldName: 'pipeByPrstWhol', header: { text: '계' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'lyngPrlg', fieldName: 'lyngPrlg', header: { text: '매설연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'dmltnPrlg', fieldName: 'dmltnPrlg', header: { text: '철거연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
...yearRange.flatMap((yr) => [
{ name: `prlg_whol_${yr}`, fieldName: `prlg_whol_${yr}`, header: { text: '계' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `lyng_prlg_${yr}`, fieldName: `lyng_prlg_${yr}`, header: { text: '매설연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `dmltn_prlg_${yr}`, fieldName: `dmltn_prlg_${yr}`, header: { text: '철거연장(m)' }, footer: footerDecimal1ArrayByDivision, valueType: ValueType.NUMBER, numberFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly' },
]),
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' }, editable: false, styleName: 'rg-cell-readonly' },
],
columnLayout: [
{
name: 'areaGroup',
header: { text: '행정구역' },
items: ['ctpvNm', 'sggNm', 'admdstNm', 'swrgPipeNm'],
},
{ name: 'summaryGroup', header: { text: '총계' }, items: ['pipeByPrstWhol', 'lyngPrlg', 'dmltnPrlg'] },
...yearRange.map((yr) => ({
name: `group_${yr}`,
header: { text: yr === rvwStartYear ? '1996년이전' : `${yr}년` },
items: [`prlg_whol_${yr}`, `lyng_prlg_${yr}`, `dmltn_prlg_${yr}`],
})),
'rmrk',
],
};
};
/** ST00020005 맨홀 현황 — 검토 화면용 동적 컬럼 생성
* 필드명: wholMnhlCnt{year}, step1~5MnhlCnt{year}
* (total의 getST00020005Config와 동일 필드)
*/
export const getST00020005RvwConfig = (statsYr: string | number) => {
const firstYear = 2014;
const lastYear = Number(statsYr) || dayjs().year();
const yearRange = Array.from({ length: lastYear - firstYear + 1 }, (_, i) => firstYear + i);
const makeFooterValueCallback = (divisionIndex: number) => ({
valueCallback: (grid: GridView) => {
const dataSource = grid.getDataSource();
const uniqueValues = new Set<string>();
for (let i = 0; i < dataSource.getRowCount(); i++) {
const val = dataSource.getValue(i, 'displayYear');
if (val) uniqueValues.add(String(val));
}
const divisionValues = Array.from(uniqueValues);
return divisionValues.length > divisionIndex
? divisionValues[divisionIndex] + '합계'
: '합계';
},
});
const mnhlYearCols = (year: number) => [
{ name: `wholMnhlCnt${year}`, fieldName: `wholMnhlCnt${year}`, header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step1MnhlCnt${year}`, fieldName: `step1MnhlCnt${year}`, header: { text: '주철' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step2MnhlCnt${year}`, fieldName: `step2MnhlCnt${year}`, header: { text: '주철(조화)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step3MnhlCnt${year}`, fieldName: `step3MnhlCnt${year}`, header: { text: '콘크리트' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step4MnhlCnt${year}`, fieldName: `step4MnhlCnt${year}`, header: { text: '기타' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step5MnhlCnt${year}`, fieldName: `step5MnhlCnt${year}`, header: { text: '추락방지시설\n설치맨홀(개)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
];
const mnhlYearLayout = (year: number) => ({
name: `yr${year}Group`,
header: { text: year === firstYear ? '2015년 이전' : `${year}년` },
items: [
{
name: `yr${year}MtrlGroup`,
header: { text: '맨홀뚜껑 재질(개)' },
items: [`wholMnhlCnt${year}`, `step1MnhlCnt${year}`, `step2MnhlCnt${year}`, `step3MnhlCnt${year}`, `step4MnhlCnt${year}`],
},
`step5MnhlCnt${year}`,
],
});
return {
tabKey: 'ST00020005',
tabLabel: '맨홀 현황',
rowStartNum: 3,
fixedColumns: 3,
columns: [
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true, footer: [makeFooterValueCallback(0), makeFooterValueCallback(1)] },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
{ name: 'emphsMngSeNm', fieldName: 'emphsMngSeNm', header: { text: '집중강우\n중점관리\n구역구분' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true },
{ name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, editable: false, styleName: 'rg-cell-readonly', sortdisable: true, footer: [makeFooterValueCallback(0), makeFooterValueCallback(1)] },
{ name: 'totalWholMnhlCnt', fieldName: 'totalWholMnhlCnt', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totalStep1MnhlCnt', fieldName: 'totalStep1MnhlCnt', header: { text: '주철' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totalStep2MnhlCnt', fieldName: 'totalStep2MnhlCnt', header: { text: '주철(조화)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totalStep3MnhlCnt', fieldName: 'totalStep3MnhlCnt', header: { text: '콘크리트' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totalStep4MnhlCnt', fieldName: 'totalStep4MnhlCnt', header: { text: '기타' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totalStep5MnhlCnt', fieldName: 'totalStep5MnhlCnt', header: { text: '추락방지시설\n설치맨홀(개)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
...yearRange.flatMap(mnhlYearCols),
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' }, editable: false, styleName: 'rg-cell-readonly' },
],
columnLayout: [
{
name: 'addrGroup',
header: { text: '행정구역' },
items: [
{ column: 'ctpvNm', footerUserSpans: [{ colspan: 5 }, { colspan: 5 }] },
'sggNm',
'admdstNm',
],
},
'emphsMngSeNm',
'displayYear',
{
name: 'totGroup',
header: { text: '총계' },
items: [
{ name: 'totMtrlGroup', header: { text: '맨홀뚜껑 재질(개)' }, items: ['totalWholMnhlCnt', 'totalStep1MnhlCnt', 'totalStep2MnhlCnt', 'totalStep3MnhlCnt', 'totalStep4MnhlCnt'] },
'totalStep5MnhlCnt',
],
},
...yearRange.map(mnhlYearLayout),
'rmrk',
],
};
};
export const SewagePipeColumnInfoListRvw = rawList
.filter((tab) => tab.tabKey !== 'ST00020003_PLACEHOLDER' && tab.tabKey !== 'ST00020005_PLACEHOLDER')
.map((tab) => ({
...tab,
columns: tab.columns.map((col) => ({
...col,
editable: false,
styleName: 'rg-cell-readonly',
})),
}));
const st00020003RvwPlaceholder = {
tabKey: 'ST00020003',
tabLabel: '관종별 현황',
rowStartNum: 2,
fixedColumns: 2,
columns: [] as ReturnType<typeof getST00020003RvwConfig>['columns'],
columnLayout: [] as ReturnType<typeof getST00020003RvwConfig>['columnLayout'],
};
const st00020005RvwPlaceholder = {
tabKey: 'ST00020005',
tabLabel: '맨홀 현황',
rowStartNum: 3,
fixedColumns: 3,
columns: [] as ReturnType<typeof getST00020005RvwConfig>['columns'],
columnLayout: [] as ReturnType<typeof getST00020005RvwConfig>['columnLayout'],
};
// ST00020003: index 2, ST00020005: 끝에 추가
SewagePipeColumnInfoListRvw.splice(2, 0, st00020003RvwPlaceholder);
SewagePipeColumnInfoListRvw.push(st00020005RvwPlaceholder);
@@ -700,8 +700,10 @@ const rawSewagePipeTotList = [
],
},
// ST00020005 맨홀 현황은 연도 범위가 동적이므로 rawSewagePipeTotList에 포함하지 않고
// getST00020005Config() 로 런타임에 생성합니다.
{
tabKey: "ST00020005",
tabKey: "ST00020005_PLACEHOLDER",
tabLabel: "맨홀 현황",
rowStartNum: 2,
fixedColumns: 2,
@@ -985,10 +987,80 @@ export const getST00020003Config = (statsYr: string | number) => {
};
};
export const SewagePipeColumnInfoListTot: ColumnInfoList[] = rawSewagePipeTotList.map((tab) => ({
...tab,
columns: tab.columns.map((col) => ({ ...col, editable: false as const })),
}));
/** ST00020005 — 맨홀 현황 동적 컬럼 생성
* 응답 필드: wholMnhlCnt{year}, step1~5MnhlCnt{year}
* firstYear: 데이터 시작 연도(기본 2014), lastYear: 조회 연도(statsYr)
*/
export const getST00020005Config = (statsYr: string | number) => {
const firstYear = 2014;
const lastYear = Number(statsYr) || new Date().getFullYear();
const mnhlYearCols = (year: number) => [
{ name: `wholMnhlCnt${year}`, fieldName: `wholMnhlCnt${year}`, header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step1MnhlCnt${year}`, fieldName: `step1MnhlCnt${year}`, header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step2MnhlCnt${year}`, fieldName: `step2MnhlCnt${year}`, header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step3MnhlCnt${year}`, fieldName: `step3MnhlCnt${year}`, header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step4MnhlCnt${year}`, fieldName: `step4MnhlCnt${year}`, header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step5MnhlCnt${year}`, fieldName: `step5MnhlCnt${year}`, header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
];
const mnhlYearLayout = (year: number) => ({
name: `yr${year}Group`,
header: { text: year === firstYear ? `${firstYear}년 이전` : `${year}년` },
items: [
{
name: `yr${year}MtrlGroup`,
header: { text: "맨홀뚜껑 재질(개)" },
items: [
`wholMnhlCnt${year}`,
`step1MnhlCnt${year}`,
`step2MnhlCnt${year}`,
`step3MnhlCnt${year}`,
`step4MnhlCnt${year}`,
],
},
`step5MnhlCnt${year}`,
],
});
const yearRange = Array.from({ length: lastYear - firstYear + 1 }, (_, i) => firstYear + i);
return {
tabKey: "ST00020005",
tabLabel: "맨홀 현황",
rowStartNum: 2,
fixedColumns: 2,
columns: [
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
{ name: "emphsMngSeNm", fieldName: "emphsMngSeNm", header: { text: "집중강우\n중점관리\n구역구분" }, editable: false },
{ name: "totWholMnhlCnt", fieldName: "totWholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep1MnhlCnt", fieldName: "totStep1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep2MnhlCnt", fieldName: "totStep2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep3MnhlCnt", fieldName: "totStep3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep4MnhlCnt", fieldName: "totStep4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep5MnhlCnt", fieldName: "totStep5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
...yearRange.flatMap(mnhlYearCols),
],
columnLayout: [
{ name: "addrGroup", header: { text: "행정구역" }, items: ["ctpvNm", "sggNm"] },
"emphsMngSeNm",
{
name: "totGroup",
header: { text: "연도별총계" },
items: [
{ name: "totMtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["totWholMnhlCnt", "totStep1MnhlCnt", "totStep2MnhlCnt", "totStep3MnhlCnt", "totStep4MnhlCnt"] },
"totStep5MnhlCnt",
],
},
...yearRange.map(mnhlYearLayout),
],
};
};
export const SewagePipeColumnInfoListTot: ColumnInfoList[] = rawSewagePipeTotList
.filter((tab) => tab.tabKey !== "ST00020005_PLACEHOLDER")
.map((tab) => ({ ...tab, columns: tab.columns.map((col) => ({ ...col, editable: false as const })) }));
const st00020003PlaceholderTab: ColumnInfoList = {
tabKey: "ST00020003",
@@ -999,4 +1071,14 @@ const st00020003PlaceholderTab: ColumnInfoList = {
columnLayout: [],
};
SewagePipeColumnInfoListTot.splice(2, 0, st00020003PlaceholderTab);
const st00020005PlaceholderTab: ColumnInfoList = {
tabKey: "ST00020005",
tabLabel: "맨홀 현황",
rowStartNum: 2,
fixedColumns: 2,
columns: [],
columnLayout: [],
};
SewagePipeColumnInfoListTot.splice(2, 0, st00020003PlaceholderTab);
SewagePipeColumnInfoListTot.push(st00020005PlaceholderTab);
@@ -199,6 +199,108 @@ export function shouldPrepareColumnsForExcelExport(columns: ColumnConfig[]): boo
return columns.some((c) => c.excludeFromExcelExport || c.visible === false);
}
/** 고정(frozen) 컬럼 표시용 — rg-cell-readonly와 동일한 화면 스타일 */
export const FIXED_CELL_STYLE_NAME = "rg-cell-fixed";
const FIXED_EXPORT_STRIP_CLASSES = new Set([
FIXED_CELL_STYLE_NAME,
"rg-cell-readonly",
]);
/** 고정 컬럼 styleName에 rg-cell-fixed 추가 */
export function appendFixedCellStyle(styleName: string | undefined): string {
const parts = (styleName ?? "").split(/\s+/).filter(Boolean);
if (!parts.includes(FIXED_CELL_STYLE_NAME)) {
parts.push(FIXED_CELL_STYLE_NAME);
}
return parts.join(" ");
}
/** 엑셀 export 시 고정/readonly 배경·글자색 클래스 제거 (검증 등 다른 클래스는 유지) */
export function stripFixedStylesForExport(styleName: string | undefined): string {
if (!styleName) return "";
return styleName
.split(/\s+/)
.filter((cls) => cls && !FIXED_EXPORT_STRIP_CLASSES.has(cls))
.join(" ");
}
export function resolveColumnStyleName(
column: { name: string; styleName?: string },
fixedColumnNameSet: Set<string>,
): string {
const base = column.styleName ?? "";
return fixedColumnNameSet.has(column.name)
? appendFixedCellStyle(base)
: base;
}
/** 화면 고정(colCount)과 동일한 앞쪽 visible 리프 컬럼명 */
export function getFixedLeafColumnNames(
columns: ColumnConfig[],
columnLayout: ColumnLayoutItem[] | undefined,
fixedColumns: number | undefined,
): string[] {
if (!fixedColumns || fixedColumns <= 0) return [];
const excludedNames = new Set(
columns.filter((c) => c.excludeFromExcelExport).map((c) => c.name),
);
const hiddenNames = new Set(
columns.filter((c) => c.visible === false).map((c) => c.name),
);
const knownNames = new Set(columns.map((c) => c.name));
const layoutNames =
columnLayout && columnLayout.length > 0
? flattenColumnLayoutNames(columnLayout)
: columns.map((c) => c.name);
const frozen: string[] = [];
const seen = new Set<string>();
for (const name of layoutNames) {
if (seen.has(name) || !knownNames.has(name)) continue;
if (hiddenNames.has(name) || excludedNames.has(name)) continue;
seen.add(name);
frozen.push(name);
if (frozen.length >= fixedColumns) break;
}
return frozen;
}
export function buildFixedColumnNameSet(
columns: ColumnConfig[],
columnLayout: ColumnLayoutItem[] | undefined,
fixedColumns: number | undefined,
): Set<string> {
return new Set(getFixedLeafColumnNames(columns, columnLayout, fixedColumns));
}
type GridViewWithFixedOptions = GridView & {
setFixedOptions?: (options: { colCount?: number }) => void;
getFixedOptions?: () => { colCount?: number };
};
/** 엑셀 export 직전: RealGrid 고정영역 기본 회색이 export에 섞이지 않도록 해제 */
export function releaseFixedOptionsForExcelExport(
gridView: GridView,
fixedColumns: number | undefined,
): number | undefined {
if (!fixedColumns || fixedColumns <= 0) return undefined;
const gv = gridView as GridViewWithFixedOptions;
const colCount = gv.getFixedOptions?.()?.colCount ?? fixedColumns;
gv.setFixedOptions?.({ colCount: 0 });
return colCount;
}
export function restoreFixedOptionsAfterExcelExport(
gridView: GridView,
colCount: number | undefined,
): void {
if (colCount && colCount > 0) {
(gridView as GridViewWithFixedOptions).setFixedOptions?.({ colCount });
}
}
const INVALID_EXCEL_FILE_NAME_CHARS = /[\\/:*?"<>|]/g;
function sanitizeExcelFileNamePart(value: string): string {
@@ -258,91 +258,17 @@ export interface SewagePipeReviewManholeItem {
emphsMngSeCd: string;
emphsMngSeNm?: string;
yy14WholMnhlCnt: number;
yy14Step1MnhlCnt: number;
yy14Step2MnhlCnt: number;
yy14Step3MnhlCnt: number;
yy14Step4MnhlCnt: number;
yy14Step5MnhlCnt: number;
yy15WholMnhlCnt: number;
yy15Step1MnhlCnt: number;
yy15Step2MnhlCnt: number;
yy15Step3MnhlCnt: number;
yy15Step4MnhlCnt: number;
yy15Step5MnhlCnt: number;
yy16WholMnhlCnt: number;
yy16Step1MnhlCnt: number;
yy16Step2MnhlCnt: number;
yy16Step3MnhlCnt: number;
yy16Step4MnhlCnt: number;
yy16Step5MnhlCnt: number;
yy17WholMnhlCnt: number;
yy17Step1MnhlCnt: number;
yy17Step2MnhlCnt: number;
yy17Step3MnhlCnt: number;
yy17Step4MnhlCnt: number;
yy17Step5MnhlCnt: number;
yy18WholMnhlCnt: number;
yy18Step1MnhlCnt: number;
yy18Step2MnhlCnt: number;
yy18Step3MnhlCnt: number;
yy18Step4MnhlCnt: number;
yy18Step5MnhlCnt: number;
yy19WholMnhlCnt: number;
yy19Step1MnhlCnt: number;
yy19Step2MnhlCnt: number;
yy19Step3MnhlCnt: number;
yy19Step4MnhlCnt: number;
yy19Step5MnhlCnt: number;
yy20WholMnhlCnt: number;
yy20Step1MnhlCnt: number;
yy20Step2MnhlCnt: number;
yy20Step3MnhlCnt: number;
yy20Step4MnhlCnt: number;
yy20Step5MnhlCnt: number;
yy21WholMnhlCnt: number;
yy21Step1MnhlCnt: number;
yy21Step2MnhlCnt: number;
yy21Step3MnhlCnt: number;
yy21Step4MnhlCnt: number;
yy21Step5MnhlCnt: number;
yy22WholMnhlCnt: number;
yy22Step1MnhlCnt: number;
yy22Step2MnhlCnt: number;
yy22Step3MnhlCnt: number;
yy22Step4MnhlCnt: number;
yy22Step5MnhlCnt: number;
yy23WholMnhlCnt: number;
yy23Step1MnhlCnt: number;
yy23Step2MnhlCnt: number;
yy23Step3MnhlCnt: number;
yy23Step4MnhlCnt: number;
yy23Step5MnhlCnt: number;
yy24WholMnhlCnt: number;
yy24Step1MnhlCnt: number;
yy24Step2MnhlCnt: number;
yy24Step3MnhlCnt: number;
yy24Step4MnhlCnt: number;
yy24Step5MnhlCnt: number;
yy25WholMnhlCnt: number;
yy25Step1MnhlCnt: number;
yy25Step2MnhlCnt: number;
yy25Step3MnhlCnt: number;
yy25Step4MnhlCnt: number;
yy25Step5MnhlCnt: number;
totalWholMnhlCnt: number;
totalStep1MnhlCnt: number;
totalStep2MnhlCnt: number;
totalStep3MnhlCnt: number;
totalStep4MnhlCnt: number;
totalStep5MnhlCnt: number;
rmrk: string | null;
/** 연도별 동적 필드 (wholMnhlCnt{year} ~ step5MnhlCnt{year}) */
[key: string]: string | number | null | undefined;
}
export interface SewagePipeManholeResponseData {
@@ -162,18 +162,18 @@ export interface SewagePipePipeDiamStatusResponse {
/**
* 맨홀 현황 응답 아이템 인터페이스
* DTO의 yy14(2014년 이전) ~ yy25(2024년) 필드를 모두 명시적으로 선언
* 연도별 동적 필드: wholMnhlCnt{year}, step1~5MnhlCnt{year}
* ex) wholMnhlCnt2014, step1MnhlCnt2025
*/
export interface FcltyManholeSummaryItem {
/* --- 기본 정보 및 행 제어 --- */
ctpvNm: string; // 시도명
sggNm: string; // 시군구명
admdstNm: string; // 행정구역명
admdstCd: string; // 행정구역코드
statsYr: string; // 통계연도
rowType: string; // 데이터 구분 (OLD, CURR, SUMMARY 등)
emphsMngSeCd: string; // 중점관리구역구분코드
emphsMngSeNm: string; // 중점관리구역구분명
ctpvNm: string;
sggNm: string;
admdstNm: string;
admdstCd: string;
statsYr: string;
rowType: string;
emphsMngSeCd: string;
emphsMngSeNm: string;
totWholMnhlCnt: number;
totStep1MnhlCnt: number;
@@ -181,101 +181,9 @@ export interface FcltyManholeSummaryItem {
totStep3MnhlCnt: number;
totStep4MnhlCnt: number;
totStep5MnhlCnt: number;
/* --- yy14 (2014년 이전) --- */
yy14WholMnhlCnt: number; // 합계
yy14Step1MnhlCnt: number; // 1단계
yy14Step2MnhlCnt: number; // 2단계
yy14Step3MnhlCnt: number; // 3단계
yy14Step4MnhlCnt: number; // 4단계
yy14Step5MnhlCnt: number; // 5단계
/* --- yy15 (2014년) --- */
yy15WholMnhlCnt: number;
yy15Step1MnhlCnt: number;
yy15Step2MnhlCnt: number;
yy15Step3MnhlCnt: number;
yy15Step4MnhlCnt: number;
yy15Step5MnhlCnt: number;
/* --- yy16 (2015년) --- */
yy16WholMnhlCnt: number;
yy16Step1MnhlCnt: number;
yy16Step2MnhlCnt: number;
yy16Step3MnhlCnt: number;
yy16Step4MnhlCnt: number;
yy16Step5MnhlCnt: number;
/* --- yy17 (2016년) --- */
yy17WholMnhlCnt: number;
yy17Step1MnhlCnt: number;
yy17Step2MnhlCnt: number;
yy17Step3MnhlCnt: number;
yy17Step4MnhlCnt: number;
yy17Step5MnhlCnt: number;
/* --- yy18 (2017년) --- */
yy18WholMnhlCnt: number;
yy18Step1MnhlCnt: number;
yy18Step2MnhlCnt: number;
yy18Step3MnhlCnt: number;
yy18Step4MnhlCnt: number;
yy18Step5MnhlCnt: number;
/* --- yy19 (2018년) --- */
yy19WholMnhlCnt: number;
yy19Step1MnhlCnt: number;
yy19Step2MnhlCnt: number;
yy19Step3MnhlCnt: number;
yy19Step4MnhlCnt: number;
yy19Step5MnhlCnt: number;
/* --- yy20 (2019년) --- */
yy20WholMnhlCnt: number;
yy20Step1MnhlCnt: number;
yy20Step2MnhlCnt: number;
yy20Step3MnhlCnt: number;
yy20Step4MnhlCnt: number;
yy20Step5MnhlCnt: number;
/* --- yy21 (2020년) --- */
yy21WholMnhlCnt: number;
yy21Step1MnhlCnt: number;
yy21Step2MnhlCnt: number;
yy21Step3MnhlCnt: number;
yy21Step4MnhlCnt: number;
yy21Step5MnhlCnt: number;
/* --- yy22 (2021년) --- */
yy22WholMnhlCnt: number;
yy22Step1MnhlCnt: number;
yy22Step2MnhlCnt: number;
yy22Step3MnhlCnt: number;
yy22Step4MnhlCnt: number;
yy22Step5MnhlCnt: number;
/* --- yy23 (2022년) --- */
yy23WholMnhlCnt: number;
yy23Step1MnhlCnt: number;
yy23Step2MnhlCnt: number;
yy23Step3MnhlCnt: number;
yy23Step4MnhlCnt: number;
yy23Step5MnhlCnt: number;
/* --- yy24 (2023년) --- */
yy24WholMnhlCnt: number;
yy24Step1MnhlCnt: number;
yy24Step2MnhlCnt: number;
yy24Step3MnhlCnt: number;
yy24Step4MnhlCnt: number;
yy24Step5MnhlCnt: number;
/* --- yy25 (2024년) --- */
yy25WholMnhlCnt: number;
yy25Step1MnhlCnt: number;
yy25Step2MnhlCnt: number;
yy25Step3MnhlCnt: number;
yy25Step4MnhlCnt: number;
yy25Step5MnhlCnt: number;
/** 연도별 동적 필드 (wholMnhlCnt{year} ~ step5MnhlCnt{year}) */
[key: string]: string | number;
}
/**