Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev
This commit is contained in:
@@ -2,15 +2,17 @@ import { Button } from "@common/components";
|
||||
|
||||
interface SubmitButtonProps {
|
||||
submitFunc: () => void;
|
||||
/** 버튼 문구 (기본: 제출) */
|
||||
text?: string;
|
||||
}
|
||||
|
||||
const SubmitButton = ({ submitFunc }: SubmitButtonProps) => {
|
||||
const SubmitButton = ({ submitFunc, text = "제출" }: SubmitButtonProps) => {
|
||||
return (
|
||||
<Button className="h-[3.2rem] w-[96px]" onClick={submitFunc}>
|
||||
<Button className="h-[3.2rem] min-w-[96px] px-4" onClick={submitFunc}>
|
||||
<i className="diveicon di-upload" />
|
||||
<span>제출</span>
|
||||
<span>{text}</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubmitButton;
|
||||
export default SubmitButton;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -23,6 +43,15 @@
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-verify-error-imr {
|
||||
@apply !bg-[#fff7bf] !text-[#b45309];
|
||||
}
|
||||
/* 총계 footer 검증 오류 (commonInfo.verificationResult) */
|
||||
.rg-grid .rg-footer .rg-table .rg-footer-cell.rg-cell-verify-error,
|
||||
.rg-grid .rg-footers .rg-table .rg-footer-cell.rg-cell-verify-error {
|
||||
@apply !bg-[#ffe6e6] !text-[#c00];
|
||||
}
|
||||
.rg-grid .rg-footer .rg-table .rg-footer-cell.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-footers .rg-table .rg-footer-cell.rg-cell-verify-error-imr {
|
||||
@apply !bg-[#fff7bf] !text-[#b45309];
|
||||
}
|
||||
/* 검증결과 내역: 처리여부(prcsYn 0·4 미처리 등) */
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-vrf-prcs-bad {
|
||||
@apply !font-semibold !text-[#dc2626];
|
||||
@@ -405,6 +434,13 @@
|
||||
background-color: #ffe6e6 !important;
|
||||
color: #c00 !important;
|
||||
}
|
||||
.rg-grid .rg-footer .rg-footer-cell.rg-cell-verify-error.rg-cell-verify-error,
|
||||
.rg-grid .rg-footers .rg-footer-cell.rg-cell-verify-error.rg-cell-verify-error,
|
||||
.rg-grid .rg-footer .rg-table .rg-footer-cell.rg-cell-verify-error.rg-cell-verify-error,
|
||||
.rg-grid .rg-footers .rg-table .rg-footer-cell.rg-cell-verify-error.rg-cell-verify-error {
|
||||
background-color: #ffe6e6 !important;
|
||||
color: #c00 !important;
|
||||
}
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-fixed-body .rg-data-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
@@ -412,6 +448,13 @@
|
||||
background-color: #fff7bf !important;
|
||||
color: #b45309 !important;
|
||||
}
|
||||
.rg-grid .rg-footer .rg-footer-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-footers .rg-footer-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-footer .rg-table .rg-footer-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr,
|
||||
.rg-grid .rg-footers .rg-table .rg-footer-cell.rg-cell-verify-error-imr.rg-cell-verify-error-imr {
|
||||
background-color: #fff7bf !important;
|
||||
color: #b45309 !important;
|
||||
}
|
||||
|
||||
/* dark theme에서도 검증 오류 색을 유지하고 싶다면 동일 색 유지(가독성 우선) */
|
||||
[data-theme="black"] .rg-grid .rg-body .rg-data-cell.rg-cell-verify-error.rg-cell-verify-error,
|
||||
|
||||
@@ -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";
|
||||
@@ -24,35 +24,100 @@ import type {
|
||||
} from "../type/DataGrid.type";
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks";
|
||||
import {
|
||||
formatVerificationTooltip,
|
||||
getVerificationErrorsForField,
|
||||
getVerificationStyleName,
|
||||
type VerificationResultLikeMap,
|
||||
} 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 */
|
||||
function resolveFooterVerificationStyleName(
|
||||
col: ColumnConfig,
|
||||
footerVerificationResult: VerificationResultLikeMap | null | undefined,
|
||||
): string | undefined {
|
||||
if (col.footer && !Array.isArray(col.footer) && col.footer.styleName) {
|
||||
return col.footer.styleName;
|
||||
}
|
||||
const errors = getVerificationErrorsForField(
|
||||
footerVerificationResult,
|
||||
col.fieldName,
|
||||
col.name !== col.fieldName ? [col.name] : undefined,
|
||||
);
|
||||
return getVerificationStyleName(errors);
|
||||
}
|
||||
|
||||
function resolveFooterVerificationTooltip(
|
||||
col: ColumnConfig,
|
||||
footerVerificationResult: VerificationResultLikeMap | null | undefined,
|
||||
): string | undefined {
|
||||
const errors = getVerificationErrorsForField(
|
||||
footerVerificationResult,
|
||||
col.fieldName,
|
||||
col.name !== col.fieldName ? [col.name] : undefined,
|
||||
);
|
||||
return formatVerificationTooltip(errors);
|
||||
}
|
||||
|
||||
type GridColumnWithStyleCallback = {
|
||||
@@ -125,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;
|
||||
@@ -150,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,
|
||||
);
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -781,8 +868,9 @@ const DataGrid = ({
|
||||
rowStartNum,
|
||||
excelExportFileSuffix,
|
||||
excelExportSearchContext,
|
||||
excelExportApplyStyles = false,
|
||||
isExcelImport = true,
|
||||
excelImportSkipNonEditable = true,
|
||||
excelImportSkipNonEditable = false,
|
||||
excelImportSewerBzmnCd,
|
||||
isExcelExport = true,
|
||||
fixedColumns,
|
||||
@@ -793,6 +881,7 @@ const DataGrid = ({
|
||||
getGridRef,
|
||||
cellStyleCallback,
|
||||
cellTooltipCallback,
|
||||
footerVerificationResult = null,
|
||||
onCellEdited,
|
||||
onRowClick,
|
||||
onDataCellClick,
|
||||
@@ -809,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);
|
||||
@@ -821,9 +914,13 @@ const DataGrid = ({
|
||||
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(
|
||||
cellStyleCallback,
|
||||
);
|
||||
const excelExportApplyStylesRef = useRef(excelExportApplyStyles);
|
||||
const cellTooltipCallbackRef = useRef<CellTooltipCallback | undefined>(
|
||||
cellTooltipCallback,
|
||||
);
|
||||
const footerVerificationResultRef = useRef<
|
||||
VerificationResultLikeMap | null | undefined
|
||||
>(footerVerificationResult);
|
||||
const onCellEditedRef = useRef<typeof onCellEdited>(onCellEdited);
|
||||
const onDataCellClickRef = useRef<typeof onDataCellClick>(onDataCellClick);
|
||||
const onScrollToBottomRef = useRef<(() => void) | undefined>(
|
||||
@@ -847,7 +944,9 @@ const DataGrid = ({
|
||||
columnsRef.current = columns;
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
excelExportApplyStylesRef.current = excelExportApplyStyles;
|
||||
cellTooltipCallbackRef.current = cellTooltipCallback;
|
||||
footerVerificationResultRef.current = footerVerificationResult;
|
||||
onScrollToBottomRef.current = onScrollToBottom;
|
||||
onItemCheckedRef.current = onItemChecked;
|
||||
onItemAllCheckedRef.current = onItemAllChecked;
|
||||
@@ -855,6 +954,7 @@ const DataGrid = ({
|
||||
onDataCellClickRef.current = onDataCellClick;
|
||||
|
||||
const hasVerificationErrors = rowsHaveVerificationErrors(rowData);
|
||||
// 총계 footer(commonInfo.verificationResult)는 행 우선보기 대상이 아님
|
||||
const showVerifyPriorityToggle = hasVerificationErrors;
|
||||
|
||||
// 조회 등으로 rowData가 교체되면 우선보기 해제 (자체 setRowData는 스킵)
|
||||
@@ -999,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(
|
||||
@@ -1007,6 +1108,8 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
(gv as { refresh?: () => void }).refresh?.();
|
||||
};
|
||||
@@ -1018,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);
|
||||
@@ -1116,6 +1219,10 @@ useEffect(() => {
|
||||
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
|
||||
? prepareGridColumnsForExcelExport(gridView, columns)
|
||||
: undefined;
|
||||
const savedFixedColCount =
|
||||
!excelExportApplyStyles && fixedColumns
|
||||
? releaseFixedOptionsForExcelExport(gridView, fixedColumns)
|
||||
: undefined;
|
||||
|
||||
const exportBaseName = buildExcelExportFileBaseName(
|
||||
menuName,
|
||||
@@ -1129,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,
|
||||
@@ -1147,7 +1261,7 @@ useEffect(() => {
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
alert("내보내기가 완료되었습니다.");
|
||||
// alert("내보내기가 완료되었습니다.");
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1338,10 +1452,14 @@ useEffect(() => {
|
||||
const preserveFieldNames = excelImportSkipNonEditable
|
||||
? getNonEditableColumnFieldNames(columns, columnLayout)
|
||||
: [];
|
||||
setRowData((prev) =>
|
||||
mergeExcelImportWithExistingRows(prev, dataRows, {
|
||||
preserveFieldNames,
|
||||
}),
|
||||
// 우선보기 해제 직후 rowDataRef가 원본 순서 — functional prev는 아직 정렬 상태일 수 있음
|
||||
skipVerifyPriorityResetRef.current = true;
|
||||
setRowData(
|
||||
mergeExcelImportWithExistingRows(
|
||||
rowDataRef.current,
|
||||
dataRows,
|
||||
{ preserveFieldNames },
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("엑셀 파일 파싱 오류:", error);
|
||||
@@ -1471,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))) {
|
||||
@@ -1661,6 +1788,8 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
gridRef.current.gridView.refresh?.();
|
||||
} catch (e) {
|
||||
@@ -1703,12 +1832,26 @@ useEffect(() => {
|
||||
|
||||
if (col.footer) {
|
||||
if (Array.isArray(col.footer)) {
|
||||
colInfo.footer = col.footer;
|
||||
colInfo.footer = col.footer.map((item) => ({
|
||||
...item,
|
||||
styleName:
|
||||
item.styleName ??
|
||||
resolveFooterVerificationStyleName(
|
||||
col,
|
||||
footerVerificationResultRef.current,
|
||||
) ??
|
||||
"",
|
||||
}));
|
||||
} else {
|
||||
colInfo.footer = {
|
||||
text: col.footer.text,
|
||||
expression: col.footer.expression,
|
||||
numberFormat: col.footer.numberFormat,
|
||||
styleName:
|
||||
resolveFooterVerificationStyleName(
|
||||
col,
|
||||
footerVerificationResultRef.current,
|
||||
) ?? "",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1736,6 +1879,8 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
|
||||
// setColumns 호출 후 footer 배열의 valueCallback을 다시 설정
|
||||
@@ -1789,6 +1934,8 @@ useEffect(() => {
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
excelExportApplyStylesRef,
|
||||
fixedColumnNameSet,
|
||||
);
|
||||
liveGridView.refresh?.();
|
||||
}, 50);
|
||||
@@ -1797,7 +1944,7 @@ useEffect(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// footer가 단일 객체이고 valueCallback이 있는 경우
|
||||
// footer가 단일 객체이고 valueCallback/styleName이 있는 경우
|
||||
columns.forEach((col) => {
|
||||
if (!col.footer || Array.isArray(col.footer)) return;
|
||||
|
||||
@@ -1812,6 +1959,13 @@ useEffect(() => {
|
||||
}
|
||||
).valueCallback = col.footer.valueCallback;
|
||||
}
|
||||
const styleName =
|
||||
resolveFooterVerificationStyleName(
|
||||
col,
|
||||
footerVerificationResultRef.current,
|
||||
) ?? "";
|
||||
(column.footer as { styleName?: string }).styleName =
|
||||
styleName;
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`Error setting footer valueCallback for column ${col.name}:`,
|
||||
@@ -1865,14 +2019,53 @@ useEffect(() => {
|
||||
columns,
|
||||
columnLayout,
|
||||
fixedColumns,
|
||||
fixedColumnNameSet,
|
||||
excelExportApplyStyles,
|
||||
rowData,
|
||||
showHeaderCheckbox,
|
||||
getGridDataRef,
|
||||
headerHeight,
|
||||
fitStyle,
|
||||
enableSort,
|
||||
footerVerificationResult,
|
||||
]);
|
||||
|
||||
/** footerVerificationResult만 바뀐 경우에도 총계 스타일 즉시 재적용/초기화 */
|
||||
useEffect(() => {
|
||||
const gridView = gridRef.current?.gridView as
|
||||
| {
|
||||
columnByName?: (name: string) => {
|
||||
footer?: { styleName?: string } | Array<{ styleName?: string }>;
|
||||
} | null;
|
||||
refresh?: () => void;
|
||||
}
|
||||
| undefined;
|
||||
if (!gridView?.columnByName) return;
|
||||
|
||||
columns.forEach((col) => {
|
||||
if (!col.footer) return;
|
||||
try {
|
||||
const column = gridView.columnByName?.(col.name);
|
||||
if (!column?.footer) return;
|
||||
const styleName =
|
||||
resolveFooterVerificationStyleName(
|
||||
col,
|
||||
footerVerificationResult,
|
||||
) ?? "";
|
||||
if (Array.isArray(column.footer)) {
|
||||
column.footer.forEach((item) => {
|
||||
item.styleName = styleName;
|
||||
});
|
||||
} else {
|
||||
column.footer.styleName = styleName;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
});
|
||||
gridView.refresh?.();
|
||||
}, [footerVerificationResult, columns]);
|
||||
|
||||
// 행 클릭 이벤트 + 검증 오류 셀 클릭 시 showMessage (공식 문서 방식)
|
||||
useEffect(() => {
|
||||
const gridView = gridRef.current?.gridView;
|
||||
@@ -1960,9 +2153,15 @@ useEffect(() => {
|
||||
};
|
||||
}, [onRowClick, onDataCellClick, cellTooltipCallback, columns]);
|
||||
|
||||
// 빈 셀은 RealGrid hover 툴팁이 안 뜸 → React 오버레이로만 보완 (값 있는 셀은 기존 onShowTooltip)
|
||||
// RealGrid 네이티브 툴팁은 데이터 셀만 지원 → 빈 셀·총계 footer는 React 오버레이로 보완
|
||||
useEffect(() => {
|
||||
if (!cellTooltipCallback) {
|
||||
const needsOverlayTooltip =
|
||||
Boolean(cellTooltipCallback) ||
|
||||
Boolean(
|
||||
footerVerificationResult &&
|
||||
Object.keys(footerVerificationResult).length > 0,
|
||||
);
|
||||
if (!needsOverlayTooltip) {
|
||||
emptyCellHoverKeyRef.current = "";
|
||||
setEmptyCellHoverTooltip(null);
|
||||
return;
|
||||
@@ -1976,6 +2175,28 @@ useEffect(() => {
|
||||
setEmptyCellHoverTooltip(null);
|
||||
};
|
||||
|
||||
const isFooterCellType = (cellType: string | undefined) => {
|
||||
if (!cellType) return false;
|
||||
const normalized = cellType.toLowerCase();
|
||||
return (
|
||||
normalized === "footer" ||
|
||||
normalized === "summary" ||
|
||||
normalized === "groupfooter"
|
||||
);
|
||||
};
|
||||
|
||||
const resolveColumnFromIndex = (index: {
|
||||
column?: string;
|
||||
fieldName?: string;
|
||||
}) =>
|
||||
columnsRef.current.find(
|
||||
(c) =>
|
||||
c.name === index.column ||
|
||||
c.fieldName === index.column ||
|
||||
c.name === index.fieldName ||
|
||||
c.fieldName === index.fieldName,
|
||||
);
|
||||
|
||||
const handleMouseMove = (ev: MouseEvent) => {
|
||||
if (emptyCellHoverRafRef.current != null) {
|
||||
cancelAnimationFrame(emptyCellHoverRafRef.current);
|
||||
@@ -2012,12 +2233,72 @@ useEffect(() => {
|
||||
ev.clientY - rect.top,
|
||||
);
|
||||
} catch {
|
||||
index = null;
|
||||
}
|
||||
|
||||
const elAtPoint = document.elementFromPoint(
|
||||
ev.clientX,
|
||||
ev.clientY,
|
||||
);
|
||||
const footerDom =
|
||||
elAtPoint instanceof Element
|
||||
? elAtPoint.closest(
|
||||
".rg-footer-cell, .rg-footers .rg-cell, .rg-footer .rg-cell",
|
||||
)
|
||||
: null;
|
||||
const isFooter =
|
||||
Boolean(footerDom) ||
|
||||
isFooterCellType(index?.cellType) ||
|
||||
index?.itemIndex === -1;
|
||||
|
||||
if (isFooter) {
|
||||
if (!footerVerificationResultRef.current) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
const col = index
|
||||
? resolveColumnFromIndex(index)
|
||||
: undefined;
|
||||
if (!col) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
const fieldName = col.fieldName;
|
||||
const msg = resolveFooterVerificationTooltip(
|
||||
col,
|
||||
footerVerificationResultRef.current,
|
||||
);
|
||||
if (!msg) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
const normalizedMessage = msg.replace(/\n[ \t]+/g, "\n");
|
||||
const key = `footer:${fieldName}`;
|
||||
emptyCellHoverKeyRef.current = key;
|
||||
setEmptyCellHoverTooltip({
|
||||
message: normalizedMessage,
|
||||
x: ev.clientX + 12,
|
||||
y: ev.clientY + 16,
|
||||
key,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!index) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
const col = resolveColumnFromIndex(index);
|
||||
const fieldName =
|
||||
col?.fieldName ?? index.fieldName ?? index.column ?? "";
|
||||
|
||||
if (!cellTooltipCallbackRef.current) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!index ||
|
||||
(index.cellType != null && index.cellType !== "data") ||
|
||||
index.itemIndex == null ||
|
||||
index.itemIndex < 0
|
||||
@@ -2032,15 +2313,6 @@ useEffect(() => {
|
||||
return;
|
||||
}
|
||||
|
||||
const col = columnsRef.current.find(
|
||||
(c) =>
|
||||
c.name === index.column ||
|
||||
c.fieldName === index.column ||
|
||||
c.name === index.fieldName ||
|
||||
c.fieldName === index.fieldName,
|
||||
);
|
||||
const fieldName =
|
||||
col?.fieldName ?? index.fieldName ?? index.column ?? "";
|
||||
if (!fieldName) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
@@ -2061,8 +2333,6 @@ useEffect(() => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 값 있는 셀 RealGrid 툴팁과 같이: 본문 다음 줄에 (검증 기준값...)이 오도록
|
||||
// 템플릿 리터럴 들여쓰기 공백은 제거
|
||||
const normalizedMessage = msg.replace(/\n[ \t]+/g, "\n");
|
||||
|
||||
const key = `${index.itemIndex}:${fieldName}`;
|
||||
@@ -2096,7 +2366,7 @@ useEffect(() => {
|
||||
}
|
||||
clearEmptyCellHoverTooltip();
|
||||
};
|
||||
}, [cellTooltipCallback]);
|
||||
}, [cellTooltipCallback, footerVerificationResult]);
|
||||
|
||||
useEffect(() => {
|
||||
const gridView = gridRef.current?.gridView;
|
||||
@@ -2433,19 +2703,36 @@ useEffect(() => {
|
||||
index: {
|
||||
itemIndex: number;
|
||||
column: string;
|
||||
cellType?: string;
|
||||
},
|
||||
value: string,
|
||||
) => string | undefined;
|
||||
}
|
||||
).onShowTooltip = (_grid, index, value) => {
|
||||
const row =
|
||||
rowDataRef.current[index?.itemIndex];
|
||||
if (row == null) return value;
|
||||
const column = index.column;
|
||||
const col = columns.find(
|
||||
(c) => c.name === column,
|
||||
);
|
||||
const fieldName = col?.fieldName ?? column;
|
||||
// 총계 footer: cellType footer 또는 itemIndex === -1
|
||||
const isFooter =
|
||||
index?.cellType === "footer" ||
|
||||
index?.itemIndex === -1;
|
||||
if (
|
||||
isFooter &&
|
||||
col &&
|
||||
footerVerificationResultRef.current
|
||||
) {
|
||||
const footerMsg =
|
||||
resolveFooterVerificationTooltip(
|
||||
col,
|
||||
footerVerificationResultRef.current,
|
||||
);
|
||||
if (footerMsg) return footerMsg;
|
||||
}
|
||||
const row =
|
||||
rowDataRef.current[index?.itemIndex];
|
||||
if (row == null) return value;
|
||||
const custom =
|
||||
cellTooltipCallbackRef.current?.({
|
||||
row: row as Record<
|
||||
@@ -2459,6 +2746,33 @@ useEffect(() => {
|
||||
? custom
|
||||
: undefined;
|
||||
};
|
||||
} else if (footerVerificationResultRef.current) {
|
||||
(
|
||||
gv as {
|
||||
onShowTooltip?: (
|
||||
grid: unknown,
|
||||
index: {
|
||||
itemIndex: number;
|
||||
column: string;
|
||||
cellType?: string;
|
||||
},
|
||||
value: string,
|
||||
) => string | undefined;
|
||||
}
|
||||
).onShowTooltip = (_grid, index) => {
|
||||
const isFooter =
|
||||
index?.cellType === "footer" ||
|
||||
index?.itemIndex === -1;
|
||||
if (!isFooter) return undefined;
|
||||
const col = columns.find(
|
||||
(c) => c.name === index.column,
|
||||
);
|
||||
if (!col) return undefined;
|
||||
return resolveFooterVerificationTooltip(
|
||||
col,
|
||||
footerVerificationResultRef.current,
|
||||
);
|
||||
};
|
||||
}
|
||||
columns.forEach((column) => {
|
||||
const gridCol = gv.columnByName(
|
||||
@@ -2487,7 +2801,7 @@ useEffect(() => {
|
||||
gv as GridViewWithSetColumnProperty,
|
||||
column,
|
||||
);
|
||||
if (cellTooltipCallbackRef.current) {
|
||||
if (cellTooltipCallbackRef.current || footerVerificationResultRef.current) {
|
||||
(
|
||||
gv as {
|
||||
setColumnProperty?: (
|
||||
@@ -2509,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
|
||||
@@ -2588,13 +2907,22 @@ useEffect(() => {
|
||||
footer={
|
||||
column.footer &&
|
||||
!Array.isArray(column.footer)
|
||||
? {
|
||||
text: column.footer.text,
|
||||
expression:
|
||||
column.footer.expression,
|
||||
numberFormat:
|
||||
column.footer.numberFormat,
|
||||
}
|
||||
? (() => {
|
||||
const footerStyleName =
|
||||
resolveFooterVerificationStyleName(
|
||||
column,
|
||||
footerVerificationResult,
|
||||
) ?? "";
|
||||
return {
|
||||
text: column.footer.text,
|
||||
expression:
|
||||
column.footer.expression,
|
||||
numberFormat:
|
||||
column.footer
|
||||
.numberFormat,
|
||||
styleName: footerStyleName,
|
||||
};
|
||||
})()
|
||||
: undefined
|
||||
}
|
||||
mergeRule={
|
||||
@@ -2610,10 +2938,11 @@ useEffect(() => {
|
||||
{emptyCellHoverTooltip &&
|
||||
createPortal(
|
||||
<div
|
||||
className="rg-tooltip pointer-events-none !visible fixed z-[9999] w-max=[60rem]"
|
||||
className="rg-tooltip pointer-events-none !visible fixed z-[9999] max-w-[60rem]"
|
||||
style={{
|
||||
left: emptyCellHoverTooltip.x,
|
||||
top: emptyCellHoverTooltip.y,
|
||||
whiteSpace: "pre-line",
|
||||
}}
|
||||
role="tooltip"
|
||||
>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -282,7 +304,7 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string
|
||||
const TabDataGrid = ({
|
||||
columnInfo,
|
||||
isExcelImport = true,
|
||||
excelImportSkipNonEditable = true,
|
||||
excelImportSkipNonEditable = false,
|
||||
excelImportSewerBzmnCd,
|
||||
excelExportSearchContext,
|
||||
isExcelExport = true,
|
||||
@@ -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>
|
||||
|
||||
@@ -40,6 +40,7 @@ import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputExcretaTreatmentFacilityApi } from "@/entities/statistics/api/input/statistics-input-excreta-treatment-facility-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
@@ -109,7 +110,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -339,6 +340,8 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
|
||||
const [statsCd, setStatsCd] = useState("");
|
||||
const [isVerificationFailed, setIsVerificationFailed] = useState(false);
|
||||
const [footerVerificationResult, setFooterVerificationResult] =
|
||||
useState<VerificationResultLikeMap | null>(null);
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
@@ -406,7 +409,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const enterable =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = ExcretaTreatFactInpColumn.columns ?? [];
|
||||
const base = enterable
|
||||
? rawColumns
|
||||
@@ -434,7 +437,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
}, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]);
|
||||
|
||||
const canEditGrid =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
|
||||
const getCurrentRows = (): Record<string, string | number>[] =>
|
||||
gridGetRowsRef.current?.() ?? rowData;
|
||||
@@ -477,6 +480,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
setStatusInfo(nextStatusInfo);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
setFooterVerificationResult(commonInfo.verificationResult ?? null);
|
||||
if (overrides?.mode !== "PRE") {
|
||||
setIsNotApplicable(gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
@@ -536,6 +540,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
setStatusInfo(null);
|
||||
setStatsCd("");
|
||||
setIsVerificationFailed(false);
|
||||
setFooterVerificationResult(null);
|
||||
setIsNotApplicable(false);
|
||||
setInputPeriodEnterable(true);
|
||||
setSggOptions([]);
|
||||
@@ -984,7 +989,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable);
|
||||
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
|
||||
const verificationQueryContext = (() => {
|
||||
@@ -1017,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}
|
||||
@@ -1028,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
|
||||
@@ -1047,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">
|
||||
@@ -1059,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}
|
||||
@@ -1076,6 +1081,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={footerVerificationResult}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,11 @@ import { SewageTreatFactStatusInpColumn } from "@/components/util/column/input/p
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
const PrivateSewageFacilityInpTabList = [
|
||||
@@ -134,7 +138,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -343,6 +347,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -458,6 +465,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -486,6 +496,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -514,6 +527,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -531,6 +547,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -573,6 +590,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -1067,10 +1085,10 @@ const PrivateSewageFacilityInp = () => {
|
||||
for (const tab of PrivateSewageFacilityInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1213,10 +1231,10 @@ const PrivateSewageFacilityInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
@@ -1255,6 +1273,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState, type Dispatch, type SetStateAction } from 'react';
|
||||
import { useEffect, useRef, useState, type Dispatch, type SetStateAction } from 'react';
|
||||
|
||||
import { useConfirm } from '@frontend-opensource/use-react-hooks';
|
||||
|
||||
@@ -51,7 +51,8 @@ import { PublicSewageFactInpColumnList } from '@/components/util/column/input/Pu
|
||||
import { toGridRows } from '@/components/util/grid/gridFunction';
|
||||
import { fetchColumnGuidesRecord } from '@/components/util/grid/columnGuides';
|
||||
import { useTabStore } from '@/store/tab-store';
|
||||
import { patchVerificationFailedByTab } from '@/components/util/input/verification-failed-flag';
|
||||
import { patchVerificationFailedByTab, patchFooterVerificationByTab } from '@/components/util/input/verification-failed-flag';
|
||||
import type { VerificationResultLikeMap } from '@/components/util/input/verification-result-display';
|
||||
import { useAdminStore } from '@/store/admin-store';
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
@@ -94,7 +95,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2';
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -201,6 +202,9 @@ const PublicSewageFacilityInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
/** 탭키별 `engColNm -> columnGuide` 캐시 */
|
||||
const [columnGuidesByTab, setColumnGuidesByTab] = useState<Record<string, Record<string, string>>>({});
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<Map<string, TabToolbarSnapshot>>(() => new Map());
|
||||
@@ -750,6 +754,9 @@ const PublicSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('ST00060001', gridRows);
|
||||
@@ -797,6 +804,9 @@ const PublicSewageFacilityInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, response.data.commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, response.data.commonInfo),
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('MainFacility', leftGridRows);
|
||||
@@ -959,6 +969,7 @@ const PublicSewageFacilityInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -1022,6 +1033,7 @@ const PublicSewageFacilityInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
|
||||
setInputPeriodEnterable(true);
|
||||
@@ -1080,13 +1092,16 @@ const PublicSewageFacilityInp = () => {
|
||||
const getGridColumnInfo = (
|
||||
tab: ColumnInfoList,
|
||||
guideTabKey?: string,
|
||||
canEdit = true,
|
||||
): ColumnInfo => ({
|
||||
columns: (tab.columns ?? []).map((col) => {
|
||||
const guide = columnGuidesByTab[guideTabKey ?? tab.tabKey]?.[col.fieldName]?.trim();
|
||||
return {
|
||||
const withGuide = {
|
||||
...col,
|
||||
tooltip: guide && guide.length > 0 ? guide : undefined,
|
||||
};
|
||||
if (canEdit) return withGuide;
|
||||
return { ...withGuide, editable: false as const };
|
||||
}),
|
||||
columnLayout: tab.columnLayout ?? [],
|
||||
});
|
||||
@@ -1164,7 +1179,7 @@ const PublicSewageFacilityInp = () => {
|
||||
isMainFacilityTab &&
|
||||
tabRowDataExists('ST00060002') &&
|
||||
gugunOkOrNotDoList &&
|
||||
(isAdminInfo || inputPeriodEnterable);
|
||||
inputPeriodEnterable;
|
||||
const showSaveInToolbar =
|
||||
toolbarGroupAllowed && (baseShowSaveInToolbar || showMainFacilitySaveInToolbar);
|
||||
const showEnterableToolbarButtons =
|
||||
@@ -1261,14 +1276,28 @@ const PublicSewageFacilityInp = () => {
|
||||
</TabsList>
|
||||
|
||||
{PublicSewageFactInpColumnList.map((tab) => {
|
||||
const tabStatus = tabStatusInfoMap.get(tab.tabKey) ?? null;
|
||||
const tabEnterableForGrid =
|
||||
(computeTabEnterable(tabStatus, inputPeriodEnterable) ||
|
||||
isAdminInfo) &&
|
||||
inputPeriodEnterable;
|
||||
|
||||
if (tab.isSplitGrid && tab.left && tab.right) {
|
||||
const leftKey = tab.left.tabKey ?? `${tab.tabKey}_left`;
|
||||
const rightKey = tab.right.tabKey ?? `${tab.tabKey}_right`;
|
||||
return (
|
||||
<TabsContent key={tab.tabKey} value={tab.tabKey}>
|
||||
<SplitDataGrid
|
||||
leftColumnInfo={getGridColumnInfo(tab.left, tab.tabKey)}
|
||||
rightColumnInfo={getGridColumnInfo(tab.right, tab.tabKey)}
|
||||
leftColumnInfo={getGridColumnInfo(
|
||||
tab.left,
|
||||
tab.tabKey,
|
||||
tabEnterableForGrid,
|
||||
)}
|
||||
rightColumnInfo={getGridColumnInfo(
|
||||
tab.right,
|
||||
tab.tabKey,
|
||||
tabEnterableForGrid,
|
||||
)}
|
||||
leftFixedColumns={tab.left.fixedColumns}
|
||||
rightFixedColumns={tab.right.fixedColumns}
|
||||
showHeaderCheckbox={true}
|
||||
@@ -1348,11 +1377,10 @@ const PublicSewageFacilityInp = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const gridColumnInfo = getGridColumnInfo(tab);
|
||||
const tabStatus = tabStatusInfoMap.get(tab.tabKey) ?? null;
|
||||
const tabEnterableForGrid = computeTabEnterable(
|
||||
tabStatus,
|
||||
inputPeriodEnterable,
|
||||
const gridColumnInfo = getGridColumnInfo(
|
||||
tab,
|
||||
undefined,
|
||||
tabEnterableForGrid,
|
||||
);
|
||||
const excelToolbarEnabledForTab =
|
||||
tabRowDataExists(tab.tabKey) &&
|
||||
@@ -1362,7 +1390,7 @@ const PublicSewageFacilityInp = () => {
|
||||
return (
|
||||
<TabsContent key={tab.tabKey} value={tab.tabKey}>
|
||||
<DataGrid
|
||||
key={tab.tabKey}
|
||||
key={`${tab.tabKey}-${tabEnterableForGrid ? 'enterable' : 'readonly'}`}
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={tab.rowStartNum}
|
||||
excelExportFileSuffix={tab.tabLabel || tab.tabKey}
|
||||
@@ -1399,6 +1427,9 @@ const PublicSewageFacilityInp = () => {
|
||||
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
|
||||
}
|
||||
}}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tab.tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={({ row, fieldName }) => {
|
||||
const verification = (row as GridRowWithVerification).verificationResult;
|
||||
if (!verification) return;
|
||||
|
||||
@@ -40,6 +40,7 @@ import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputPumpingStationApi } from "@/entities/statistics/api/input/statistics-input-pumping-station-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
@@ -109,7 +110,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -334,6 +335,8 @@ const PumpingStationInp = () => {
|
||||
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
|
||||
const [statsCd, setStatsCd] = useState("");
|
||||
const [isVerificationFailed, setIsVerificationFailed] = useState(false);
|
||||
const [footerVerificationResult, setFooterVerificationResult] =
|
||||
useState<VerificationResultLikeMap | null>(null);
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
@@ -401,7 +404,7 @@ const PumpingStationInp = () => {
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const enterable =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = PumpingStationInpColumn.columns ?? [];
|
||||
const base = enterable
|
||||
? rawColumns
|
||||
@@ -429,7 +432,7 @@ const PumpingStationInp = () => {
|
||||
}, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]);
|
||||
|
||||
const canEditGrid =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
|
||||
const getCurrentRows = (): Record<string, string | number>[] =>
|
||||
gridGetRowsRef.current?.() ?? rowData;
|
||||
@@ -469,6 +472,7 @@ const PumpingStationInp = () => {
|
||||
setStatusInfo(nextStatusInfo);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
setFooterVerificationResult(commonInfo.verificationResult ?? null);
|
||||
if (overrides?.mode !== "PRE") {
|
||||
setIsNotApplicable(gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
@@ -528,6 +532,7 @@ const PumpingStationInp = () => {
|
||||
setStatusInfo(null);
|
||||
setStatsCd("");
|
||||
setIsVerificationFailed(false);
|
||||
setFooterVerificationResult(null);
|
||||
setIsNotApplicable(false);
|
||||
setInputPeriodEnterable(true);
|
||||
setSggOptions([]);
|
||||
@@ -968,7 +973,7 @@ const PumpingStationInp = () => {
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable);
|
||||
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
|
||||
const verificationQueryContext = (() => {
|
||||
@@ -1060,6 +1065,7 @@ const PumpingStationInp = () => {
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={footerVerificationResult}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,11 @@ import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/colum
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { statisticsInputSewageExcretaRelatedApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-related-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -73,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;
|
||||
|
||||
@@ -169,7 +173,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -622,6 +626,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [isNotApplicableByTab, setIsNotApplicableByTab] = useState<Map<string, boolean>>(
|
||||
() => new Map(),
|
||||
);
|
||||
@@ -866,6 +873,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -909,6 +919,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -940,6 +953,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -974,6 +990,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -1005,6 +1024,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
setLoadedTabKeys(new Set());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setIsNotApplicableByTab(new Map());
|
||||
setDeleteListByTab(new Map());
|
||||
clearArticleOgnzSnTracking(
|
||||
@@ -1055,6 +1075,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setIsNotApplicableByTab(new Map());
|
||||
setDeleteListByTab(new Map());
|
||||
@@ -1811,10 +1832,10 @@ const SewageExcretaRelatedInp = () => {
|
||||
for (const tab of SewageExcretaRelatedInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1946,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={
|
||||
@@ -1959,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}
|
||||
@@ -1981,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">
|
||||
@@ -2013,8 +2034,8 @@ const SewageExcretaRelatedInp = () => {
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(tabStatusInfo, inputPeriodEnterable) ||
|
||||
isAdminInfo;
|
||||
(computeTabEnterable(tabStatusInfo, inputPeriodEnterable) ||
|
||||
isAdminInfo) && inputPeriodEnterable;
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
@@ -2055,6 +2076,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
}
|
||||
getGridRef={activeTab === tabKey ? getGridRef : undefined}
|
||||
cellStyleCallback={resolveGridCellStyleCallback(tabKey)}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
</TabsContent>
|
||||
@@ -2102,7 +2126,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
false,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,7 +52,11 @@ import { doList } from "@/components/util/column/input/DoList";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { statisticsInputSewageExcretaSlurgeApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -177,10 +181,10 @@ function mapListToGridRows<T extends { verificationResult?: SewageExcretaSlurgeV
|
||||
});
|
||||
}
|
||||
|
||||
const SewageExcretaSlurgeInpTabList = [
|
||||
SlurgeProcessingFacStatusInpColumn,
|
||||
DigestorOperatingStatusInpColumn,
|
||||
SewageSlurgeProcessingFacStatusInpColumn,
|
||||
const SewageExcretaSlurgeInpTabList = [
|
||||
SlurgeProcessingFacStatusInpColumn,
|
||||
DigestorOperatingStatusInpColumn,
|
||||
SewageSlurgeProcessingFacStatusInpColumn,
|
||||
ExcretaSlurgeProcessingStatusInpColumn,
|
||||
] as const;
|
||||
|
||||
@@ -355,7 +359,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -669,6 +673,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -927,6 +934,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -964,6 +974,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -999,6 +1012,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -1039,6 +1055,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -1060,6 +1079,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -1118,6 +1138,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
setLoadedTabKeys(new Set());
|
||||
@@ -1876,10 +1897,10 @@ const SewageExcretaSlurgeInp = () => {
|
||||
for (const tab of SewageExcretaSlurgeInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -2050,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={
|
||||
@@ -2063,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}
|
||||
@@ -2084,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">
|
||||
@@ -2154,6 +2175,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
activeTab === tabKey ? getGridRef : undefined
|
||||
}
|
||||
cellStyleCallback={resolveGridCellStyleCallback(tabKey)}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
onCellEdited={
|
||||
tabKey === DigestorOperatingStatusInpColumn.tabKey
|
||||
|
||||
@@ -72,8 +72,10 @@ import { SewagePipeInpColumnInfoList } from "@/components/util/column/input/sewa
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -215,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,
|
||||
@@ -225,6 +239,7 @@ function normalizePipeTypeStatusRow(
|
||||
yearlyStatList,
|
||||
lyngPrlgWhol,
|
||||
dmltnPrlgWhol,
|
||||
verificationResult,
|
||||
...rest
|
||||
} = row;
|
||||
const yearFields: Record<string, number> = {};
|
||||
@@ -252,6 +267,7 @@ function normalizePipeTypeStatusRow(
|
||||
lyngPrlg: Number(row.lyngPrlg ?? lyngPrlgWhol ?? 0),
|
||||
dmltnPrlg: Number(row.dmltnPrlg ?? dmltnPrlgWhol ?? 0),
|
||||
...yearFields,
|
||||
verificationResult: remapPipeTypePrlgWholVerification(verificationResult) ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -435,7 +451,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -465,6 +481,9 @@ const SewagePipeInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
|
||||
/** 탭키별 `engColNm -> columnGuide` 캐시 */
|
||||
@@ -566,6 +585,7 @@ const SewagePipeInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setLastSearchedYear(null);
|
||||
setLastSearchedValues(null);
|
||||
setHasSearched(false);
|
||||
@@ -594,7 +614,7 @@ const SewagePipeInp = () => {
|
||||
});
|
||||
return SewagePipeInpColumnInfoList.map((tab: ColumnInfoList) => {
|
||||
const canEdit =
|
||||
computeTabEnterable(tabStatusInfoMap.get(tab.tabKey) ?? null, inputPeriodEnterable) || isAdminInfo;
|
||||
(computeTabEnterable(tabStatusInfoMap.get(tab.tabKey) ?? null, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
||||
const applyEditable = (cols: ColumnConfig[] | undefined) =>
|
||||
tab.tabKey === PIPE_TYPE_STATUS_TAB_KEY
|
||||
? applyPipeTypeStatusEditableColumns(
|
||||
@@ -703,6 +723,9 @@ const SewagePipeInp = () => {
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
@@ -747,6 +770,9 @@ const SewagePipeInp = () => {
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
@@ -773,6 +799,13 @@ const SewagePipeInp = () => {
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, {
|
||||
verificationResult:
|
||||
remapPipeTypePrlgWholVerification(commonInfo.verificationResult) ??
|
||||
null,
|
||||
}),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
@@ -800,6 +833,9 @@ const SewagePipeInp = () => {
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
@@ -826,6 +862,9 @@ const SewagePipeInp = () => {
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
@@ -859,6 +898,9 @@ const SewagePipeInp = () => {
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
@@ -912,6 +954,7 @@ const SewagePipeInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setStatsCd("");
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
}
|
||||
@@ -2193,7 +2236,9 @@ const SewagePipeInp = () => {
|
||||
}}
|
||||
isExcelImport={excelToolbarEnabledForTab}
|
||||
excelImportSkipNonEditable={
|
||||
tabKey === PIPE_TYPE_STATUS_TAB_KEY ? !isAdminInfo : true
|
||||
tabKey === PIPE_TYPE_STATUS_TAB_KEY
|
||||
? !isAdminInfo
|
||||
: false
|
||||
}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
@@ -2224,6 +2269,9 @@ const SewagePipeInp = () => {
|
||||
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
|
||||
}
|
||||
}}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={({ row, fieldName }) => {
|
||||
const verification = (row as GridRowWithVerification).verificationResult;
|
||||
if (!verification) return;
|
||||
|
||||
@@ -58,7 +58,11 @@ import { PipeDredgingStatusInpColumn } from "@/components/util/column/input/sewa
|
||||
import { RepairStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/RepairStatusInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -128,7 +132,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -291,6 +295,9 @@ const SewagePipeMaintainInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -435,6 +442,9 @@ const SewagePipeMaintainInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(searchContext);
|
||||
}
|
||||
@@ -471,6 +481,9 @@ const SewagePipeMaintainInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(searchContext);
|
||||
}
|
||||
@@ -866,6 +879,7 @@ const SewagePipeMaintainInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setLastSearchedValues(null);
|
||||
@@ -911,6 +925,7 @@ const SewagePipeMaintainInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -986,10 +1001,10 @@ const SewagePipeMaintainInp = () => {
|
||||
for (const tab of SewagePipeMaintainInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = tab.columns ?? [];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1152,10 +1167,10 @@ const SewagePipeMaintainInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
@@ -1168,6 +1183,7 @@ const SewagePipeMaintainInp = () => {
|
||||
toGridColumnInfo(info)
|
||||
}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
fixedColumns={info.fixedColumns}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
@@ -1192,6 +1208,9 @@ const SewagePipeMaintainInp = () => {
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -45,7 +45,11 @@ import { SewageReservoirInpColumn } from "@/components/util/column/input/sewageR
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import {
|
||||
formatCompactYmdForGrid,
|
||||
normalizeOxYnValue,
|
||||
@@ -138,7 +142,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && periodEnterable && gugunOk;
|
||||
@@ -937,6 +941,9 @@ const SewageReservoirPumpInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [verificationResultDialogOpen, setVerificationResultDialogOpen] =
|
||||
useState(false);
|
||||
@@ -1079,6 +1086,9 @@ const SewageReservoirPumpInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(values);
|
||||
}
|
||||
@@ -1124,6 +1134,9 @@ const SewageReservoirPumpInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(values);
|
||||
}
|
||||
@@ -1191,6 +1204,7 @@ const SewageReservoirPumpInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setLastSearchedValues(null);
|
||||
setSggOptions([]);
|
||||
@@ -1241,6 +1255,7 @@ const SewageReservoirPumpInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -1428,10 +1443,10 @@ const SewageReservoirPumpInp = () => {
|
||||
for (const tab of SewageReservoirPumpInpTabList) {
|
||||
const tabKey = tab.tabKey ?? "";
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
|
||||
const isRainfallPumpTab = tabKey === rainfallPumpTabKey;
|
||||
const rawColumns = tab.columns ?? [];
|
||||
@@ -1596,10 +1611,10 @@ const SewageReservoirPumpInp = () => {
|
||||
{SewageReservoirPumpInpTabList.map((tab) => {
|
||||
const tabKey = tab.tabKey ?? "";
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD;
|
||||
@@ -1658,6 +1673,9 @@ const SewageReservoirPumpInp = () => {
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -63,7 +63,11 @@ import { SewerageUsageFeeInpColumn } from "@/components/util/column/input/sewera
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
const SewerageFinanceInpTabList = [
|
||||
@@ -133,7 +137,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -372,6 +376,9 @@ const SewerageFinanceInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -484,6 +491,9 @@ const SewerageFinanceInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -510,6 +520,9 @@ const SewerageFinanceInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -536,6 +549,9 @@ const SewerageFinanceInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -562,6 +578,9 @@ const SewerageFinanceInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -579,6 +598,7 @@ const SewerageFinanceInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -621,6 +641,7 @@ const SewerageFinanceInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -1139,10 +1160,10 @@ const SewerageFinanceInp = () => {
|
||||
for (const tab of SewerageFinanceInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1237,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={
|
||||
@@ -1250,7 +1271,7 @@ const SewerageFinanceInp = () => {
|
||||
<LastYearDataButton
|
||||
getLastYearDataFunc={() => void lastYearDataFunc()}
|
||||
/>
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
<SubmitButton submitFunc={submitFunc} />
|
||||
</>
|
||||
)}
|
||||
{activeTabStatus?.laeSbmsnSttsCd === "2" && (
|
||||
@@ -1285,10 +1306,10 @@ const SewerageFinanceInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
@@ -1327,6 +1348,9 @@ const SewerageFinanceInp = () => {
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -51,6 +51,7 @@ import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -60,6 +61,28 @@ type GridRowWithVerification = Record<string, string | number> & {
|
||||
verificationResult?: SewerageSupplyRateVerificationResult;
|
||||
};
|
||||
|
||||
/** commonInfo 푸터 필드명 → 그리드 fieldName */
|
||||
const FOOTER_VERIFY_FIELD_ALIASES: Record<string, string> = {
|
||||
sewerBzmnWholPpltnCnt: "pbadmsWholPpltnCnt",
|
||||
sewerBzmnDomplWholPpltnCnt: "domplWholPpltnCnt",
|
||||
sewerBzmnFrgnrWholPpltnCnt: "frgnrWholPpltnCnt",
|
||||
sewerBzmnArea: "refArea",
|
||||
};
|
||||
|
||||
const remapCommonInfoVerificationToGridFields = (
|
||||
result: SewerageSupplyRateVerificationResult | null | undefined,
|
||||
): VerificationResultLikeMap | null => {
|
||||
if (!result) return null;
|
||||
const remapped: VerificationResultLikeMap = {};
|
||||
for (const [key, errors] of Object.entries(result)) {
|
||||
const gridField = FOOTER_VERIFY_FIELD_ALIASES[key] ?? key;
|
||||
remapped[gridField] = errors;
|
||||
// API 키로도 조회 가능하도록 유지
|
||||
if (gridField !== key) remapped[key] = errors;
|
||||
}
|
||||
return remapped;
|
||||
};
|
||||
|
||||
/** 컬럼 가이드 API 조회용 통계코드 (하수도 보급률) */
|
||||
const SEWERAGE_PENETRATION_COLUMN_GUIDE_STATS_CD = "ST00010001";
|
||||
|
||||
@@ -137,6 +160,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
/** `engColNm` → `columnGuide` (헤더 hover) */
|
||||
const [columnGuidesByEngCol, setColumnGuidesByEngCol] = useState<Record<string, string>>({});
|
||||
const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null);
|
||||
const [footerVerificationResult, setFooterVerificationResult] =
|
||||
useState<VerificationResultLikeMap | null>(null);
|
||||
const { confirm } = useConfirm();
|
||||
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
|
||||
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
|
||||
@@ -208,11 +233,14 @@ const SeweragePenetrationRateInp = () => {
|
||||
(footerFromApi && footerFromApi[fieldName] != null ? footerFromApi[fieldName] : null) ?? getFooterSummary(grid, column),
|
||||
});
|
||||
|
||||
// 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요.
|
||||
|
||||
// 관리자는 제출/승인 상태에서도 편집 가능. 단, 입력기간 외에는 불가.
|
||||
const columnInfo = useMemo(() => {
|
||||
const { columns, columnLayout } = SeweragePenetrationRateInpColumn;
|
||||
const base = (isEnterable || isAdminInfo) ? columns : columns.map((col) => ({ ...col, editable: false as const }));
|
||||
const canEdit =
|
||||
(isEnterable || isAdminInfo) && isInputPeriodCurrent(periodCheckList);
|
||||
const base = canEdit
|
||||
? columns
|
||||
: columns.map((col) => ({ ...col, editable: false as const }));
|
||||
|
||||
const withHeaderTooltips = base.map((col) => {
|
||||
const guide = columnGuidesByEngCol[col.fieldName]?.trim();
|
||||
@@ -233,7 +261,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
return col;
|
||||
}),
|
||||
};
|
||||
}, [isEnterable, isAdminInfo, footerFromApi, columnGuidesByEngCol]);
|
||||
}, [isEnterable, isAdminInfo, periodCheckList, footerFromApi, columnGuidesByEngCol]);
|
||||
|
||||
const handleReset = () => {
|
||||
setRowData([]);
|
||||
@@ -242,6 +270,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
setLastSearchedYear(null);
|
||||
setLastSearchedValues(null);
|
||||
setFooterFromApi(null);
|
||||
setFooterVerificationResult(null);
|
||||
setVerificationSearchDefaults(null);
|
||||
setIsVerificationFailed(false);
|
||||
setSearchFormRemountKey((k) => k + 1);
|
||||
@@ -278,6 +307,11 @@ const SeweragePenetrationRateInp = () => {
|
||||
}
|
||||
: null
|
||||
);
|
||||
setFooterVerificationResult(
|
||||
remapCommonInfoVerificationToGridFields(
|
||||
commonInfo.verificationResult,
|
||||
),
|
||||
);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
if (statusInfo && statusInfo.laeSbmsnSttsCd) {
|
||||
@@ -299,6 +333,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
setRowData([]);
|
||||
setStatusInfo(null);
|
||||
setFooterFromApi(null);
|
||||
setFooterVerificationResult(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -755,7 +790,8 @@ const SeweragePenetrationRateInp = () => {
|
||||
|
||||
const inputPeriodCurrent = isInputPeriodCurrent(periodCheckList);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2';
|
||||
const baseShowSaveInToolbar = (isEnterable || isAdminInfo) && gugunOkOrNotDoList;
|
||||
const baseShowSaveInToolbar =
|
||||
(isEnterable || isAdminInfo) && inputPeriodCurrent && gugunOkOrNotDoList;
|
||||
const baseShowEnterableToolbarButtons = isEnterable && gugunOkOrNotDoList;
|
||||
const showSubmittedInInputPeriodToolbar =
|
||||
isSubmitted && inputPeriodCurrent && gugunOkOrNotDoList;
|
||||
@@ -805,7 +841,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
</div>
|
||||
}
|
||||
<DataGrid
|
||||
key={(isEnterable || isAdminInfo) ? 'enterable' : 'readonly'}
|
||||
key={((isEnterable || isAdminInfo) && inputPeriodCurrent) ? 'enterable' : 'readonly'}
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={(rowData.length > 0) && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true)}
|
||||
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
||||
@@ -826,6 +862,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
return hasImr ? 'rg-cell-verify-error-imr' : 'rg-cell-verify-error';
|
||||
}
|
||||
}}
|
||||
footerVerificationResult={footerVerificationResult}
|
||||
cellTooltipCallback={({ row, fieldName }) => {
|
||||
const verification = (row as GridRowWithVerification).verificationResult;
|
||||
if (!verification) return;
|
||||
|
||||
@@ -26,7 +26,11 @@ import { ResidentFriendlyFactInpColumn } from "@/components/util/column/input/se
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { statisticsInputSewerageServiceLevelApi } from "@/entities/statistics/api/input/statistics-input-sewerage-service-level-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -132,7 +136,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -304,6 +308,9 @@ const SewerageServiceLevelInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -420,6 +427,9 @@ const SewerageServiceLevelInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
setSearchLoaded(true);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
@@ -455,6 +465,9 @@ const SewerageServiceLevelInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
setSearchLoaded(true);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
@@ -478,6 +491,7 @@ const SewerageServiceLevelInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -523,6 +537,7 @@ const SewerageServiceLevelInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setIsNotApplicable(false);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -1001,10 +1016,10 @@ const SewerageServiceLevelInp = () => {
|
||||
for (const tab of SewerageServiceLevelInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -1128,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={
|
||||
@@ -1141,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}
|
||||
@@ -1188,10 +1203,10 @@ const SewerageServiceLevelInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
@@ -1230,6 +1245,9 @@ const SewerageServiceLevelInp = () => {
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -42,8 +42,10 @@ import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
import {
|
||||
patchFooterVerificationByTab,
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
||||
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputWaterReuseApi } from "@/entities/statistics/api/input/statistics-input-water-reuse-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
@@ -86,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;
|
||||
|
||||
@@ -173,7 +175,7 @@ function computeTabToolbarSnapshot(
|
||||
const gugunOk = buildGugunOkForToolbar(searchContext);
|
||||
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
||||
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
||||
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
||||
const showEnterableToolbarButtons = enterable && gugunOk;
|
||||
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
||||
const showActionToolbarCard =
|
||||
@@ -539,6 +541,9 @@ const WaterReuseInp = () => {
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
||||
Map<string, VerificationResultLikeMap | null>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -713,6 +718,9 @@ const WaterReuseInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
@@ -741,6 +749,9 @@ const WaterReuseInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
@@ -769,6 +780,9 @@ const WaterReuseInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
@@ -799,6 +813,9 @@ const WaterReuseInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
@@ -830,6 +847,9 @@ const WaterReuseInp = () => {
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setFooterVerificationByTab((prev) =>
|
||||
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
@@ -887,6 +907,7 @@ const WaterReuseInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -934,6 +955,7 @@ const WaterReuseInp = () => {
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setFooterVerificationByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setDeleteListByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
@@ -1905,10 +1927,10 @@ const WaterReuseInp = () => {
|
||||
for (const tab of WaterReuseInpTabList) {
|
||||
const tabKey = tab.tabKey;
|
||||
const canEdit =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const rawColumns = (tab.columns ?? []) as ColumnConfig[];
|
||||
const base = canEdit
|
||||
? rawColumns
|
||||
@@ -2067,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={
|
||||
@@ -2080,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}
|
||||
@@ -2106,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">
|
||||
@@ -2138,10 +2160,10 @@ const WaterReuseInp = () => {
|
||||
const tabKey = info.tabKey;
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const tabEnterableForGrid =
|
||||
computeTabEnterable(
|
||||
(computeTabEnterable(
|
||||
tabStatusInfoMap.get(tabKey) ?? null,
|
||||
inputPeriodEnterable,
|
||||
) || isAdminInfo;
|
||||
) || isAdminInfo) && inputPeriodEnterable;
|
||||
const gugunOk = buildGugunOkForToolbar(lastSearchedValues);
|
||||
const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null;
|
||||
|
||||
@@ -2183,6 +2205,9 @@ const WaterReuseInp = () => {
|
||||
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
||||
isExcelExport
|
||||
cellStyleCallback={verificationCellStyle}
|
||||
footerVerificationResult={
|
||||
footerVerificationByTab.get(tabKey) ?? null
|
||||
}
|
||||
cellTooltipCallback={verificationCellTooltip}
|
||||
getGridDataRef={
|
||||
activeTab === tabKey ? getGridDataRef : undefined
|
||||
|
||||
@@ -29,6 +29,11 @@ export interface ReviewSubmitDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
context: ReviewerInfoRequest | null;
|
||||
onSubmitted?: () => void | Promise<void>;
|
||||
/**
|
||||
* true면 검토의견·처리내역 UI를 숨기고
|
||||
* submit body에 statsCd / splmntOpnnCn / wrterOpnnCn을 넣지 않음
|
||||
*/
|
||||
hideOpinionFields?: boolean;
|
||||
}
|
||||
|
||||
export function ReviewSubmitDialog({
|
||||
@@ -36,6 +41,7 @@ export function ReviewSubmitDialog({
|
||||
onOpenChange,
|
||||
context,
|
||||
onSubmitted,
|
||||
hideOpinionFields = false,
|
||||
}: ReviewSubmitDialogProps) {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [reviewerInfo, setReviewerInfo] =
|
||||
@@ -98,14 +104,20 @@ export function ReviewSubmitDialog({
|
||||
setIsSubmitting(true);
|
||||
|
||||
const userInfo = await getUserInfo();
|
||||
const submitParam: ReviewSubmitRequest = {
|
||||
statsYr: context.statsYr,
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
splmntOpnnCn: reviewerInfo?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: reviewerInfo?.wrterOpnnCn ?? "",
|
||||
};
|
||||
const submitParam: ReviewSubmitRequest = hideOpinionFields
|
||||
? {
|
||||
statsYr: context.statsYr,
|
||||
sewerBzmnCd: [context.sewerBzmnCd],
|
||||
mdfrId: userInfo.userUnqId,
|
||||
}
|
||||
: {
|
||||
statsYr: context.statsYr,
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
splmntOpnnCn: reviewerInfo?.splmntOpnnCn ?? "",
|
||||
wrterOpnnCn: reviewerInfo?.wrterOpnnCn ?? "",
|
||||
};
|
||||
|
||||
const response = await statisticsApi.submitReview(submitParam);
|
||||
if (response.status !== "success") {
|
||||
@@ -153,22 +165,26 @@ export function ReviewSubmitDialog({
|
||||
<Label className="text-label">직책</Label>
|
||||
<Input value={reviewerInfo?.cps ?? "-"} readOnly />
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-2">
|
||||
<Label className="text-label">검토의견</Label>
|
||||
<Textarea
|
||||
value={reviewerInfo?.splmntOpnnCn ?? "-"}
|
||||
readOnly
|
||||
className="min-h-[10rem] p-2 resize-none !text-xl"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-2">
|
||||
<Label className="text-label">처리내역</Label>
|
||||
<Textarea
|
||||
value={reviewerInfo?.wrterOpnnCn ?? "-"}
|
||||
readOnly
|
||||
className="min-h-[10rem] p-2 resize-none !text-xl"
|
||||
/>
|
||||
</div>
|
||||
{!hideOpinionFields && (
|
||||
<>
|
||||
<div className="col-span-2 flex flex-col gap-2">
|
||||
<Label className="text-label">검토의견</Label>
|
||||
<Textarea
|
||||
value={reviewerInfo?.splmntOpnnCn ?? "-"}
|
||||
readOnly
|
||||
className="min-h-[10rem] p-2 resize-none !text-xl"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2 flex flex-col gap-2">
|
||||
<Label className="text-label">처리내역</Label>
|
||||
<Textarea
|
||||
value={reviewerInfo?.wrterOpnnCn ?? "-"}
|
||||
readOnly
|
||||
className="min-h-[10rem] p-2 resize-none !text-xl"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -6,6 +6,8 @@ import DataGrid from "@/components/module/DataGrid";
|
||||
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import {
|
||||
Button,
|
||||
@@ -18,9 +20,12 @@ import {
|
||||
DataTable
|
||||
} from "@common/components";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { StatsInputStatusApproveItem } from "@/entities/statistics/model/statistics-interface";
|
||||
import type {
|
||||
ReviewerInfoRequest,
|
||||
StatsInputStatusApproveItem,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { StatsInputStatusInfoList } from "@/components/util/column/review/StatsInputStatusRvwColumn";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type {
|
||||
@@ -55,6 +60,7 @@ const mapSewerageListToGridRows = (rawList: StatsInputStatusReviewItem[]) =>
|
||||
// 하수도 통계 검토 00. 통계 입력 현황
|
||||
const StatsInputStatusRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const { confirm } = useConfirm();
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [historyRowData, setHistoryRowData] = useState<ReviewHistoryRow[]>([]);
|
||||
@@ -63,6 +69,7 @@ const StatsInputStatusRvw = () => {
|
||||
const [searchedYear, setSearchedYear] = useState<string>("");
|
||||
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
|
||||
const [hasCompletedSearch, setHasCompletedSearch] = useState(false);
|
||||
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
|
||||
const [reviewHistoryOpen, setReviewHistoryOpen] = useState<boolean>(false);
|
||||
const [historySearchInfo, setHistorySearchInfo] = useState<{
|
||||
ctpvNm: string;
|
||||
@@ -112,6 +119,7 @@ const StatsInputStatusRvw = () => {
|
||||
setLastSearchedValues(null);
|
||||
setHasCompletedSearch(false);
|
||||
setIsApproveLoading(false);
|
||||
setSubmitDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
@@ -242,13 +250,74 @@ const StatsInputStatusRvw = () => {
|
||||
[rowData],
|
||||
);
|
||||
|
||||
const showActionToolbarCard =
|
||||
/** 진행상태(prvn) 코드가 모두 제출완료(2) */
|
||||
const allPrvnStatusSubmitted = useMemo(
|
||||
() =>
|
||||
rowData.length > 0 &&
|
||||
rowData.every((row) => String(row.prvnStatusCd ?? "") === "2"),
|
||||
[rowData],
|
||||
);
|
||||
|
||||
const showSubmitButton =
|
||||
hasCompletedSearch &&
|
||||
rowData.length > 0 &&
|
||||
gugunOkOrNotDoList &&
|
||||
!allPrvnStatusApproved &&
|
||||
!allPrvnStatusSubmitted;
|
||||
|
||||
const showApproveButton =
|
||||
hasCompletedSearch &&
|
||||
rowData.length > 0 &&
|
||||
gugunOkOrNotDoList &&
|
||||
allSubmitStatusApproved &&
|
||||
!allPrvnStatusApproved;
|
||||
|
||||
const showActionToolbarCard = showSubmitButton || showApproveButton;
|
||||
|
||||
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
|
||||
if (lastSearchedValues == null) return null;
|
||||
|
||||
const statsYr = lastSearchedValues.year ?? "";
|
||||
const sewerBzmnCd = lastSearchedValues.bsnmCd ?? "";
|
||||
const instSeCd = userInfo?.instSeCd ?? "";
|
||||
|
||||
if (!statsYr || !sewerBzmnCd || !instSeCd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
statsYr,
|
||||
sewerBzmnCd,
|
||||
instSeCd,
|
||||
};
|
||||
}, [lastSearchedValues, userInfo?.instSeCd]);
|
||||
|
||||
const submitFunc = () => {
|
||||
if (submitDialogContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
setSubmitDialogOpen(true);
|
||||
};
|
||||
|
||||
const refreshListAfterSubmit = async () => {
|
||||
if (!lastSearchedValues) return;
|
||||
try {
|
||||
const listResponse = await statisticsManagementApi.getStatsInputStatusList({
|
||||
ctpvNm: lastSearchedValues.sido,
|
||||
sggNm: lastSearchedValues.gugun === "ALL" ? "" : lastSearchedValues.gugun,
|
||||
statsYr: lastSearchedValues.year ?? "",
|
||||
sewerBzmnCd: lastSearchedValues.bsnmCd,
|
||||
});
|
||||
if (listResponse.status === "success" && listResponse.data) {
|
||||
const rawList = listResponse.data.sewerageInputList ?? [];
|
||||
setRowData(toGridRows(mapSewerageListToGridRows(rawList)));
|
||||
}
|
||||
} catch {
|
||||
/* 목록 갱신 실패는 별도 안내 없이 생략 */
|
||||
}
|
||||
};
|
||||
|
||||
const openReviewHistoryForRow = useCallback(
|
||||
async (row: Record<string, string | number>) => {
|
||||
if (!searchedYear) {
|
||||
@@ -297,7 +366,10 @@ const StatsInputStatusRvw = () => {
|
||||
{showActionToolbarCard && (
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<ApproveButton approveFunc={approveFunc} />
|
||||
{showSubmitButton && (
|
||||
<SubmitButton submitFunc={submitFunc} text="일괄 제출" />
|
||||
)}
|
||||
{showApproveButton && <ApproveButton approveFunc={approveFunc} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -357,6 +429,13 @@ const StatsInputStatusRvw = () => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReviewSubmitDialog
|
||||
open={submitDialogOpen}
|
||||
onOpenChange={setSubmitDialogOpen}
|
||||
context={submitDialogContext}
|
||||
hideOpinionFields
|
||||
onSubmitted={() => void refreshListAfterSubmit()}
|
||||
/>
|
||||
<Dialog open={reviewHistoryOpen} onOpenChange={setReviewHistoryOpen}>
|
||||
<DialogContent
|
||||
className="flex max-h-[85vh] max-w-[48rem] flex-col gap-4"
|
||||
|
||||
@@ -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}
|
||||
|
||||
+792
-303
File diff suppressed because it is too large
Load Diff
+110
-15
@@ -11,6 +11,7 @@ import {
|
||||
Textarea,
|
||||
Skeleton,
|
||||
Input,
|
||||
Checkbox,
|
||||
} from "@common/components";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
|
||||
@@ -32,11 +33,13 @@ interface FomulaDialogProps {
|
||||
data?: VerifyDetailResponseDto | null;
|
||||
trgtStatsCd?: string;
|
||||
trgtStatsNm?: string;
|
||||
/** 바로 앞 수식 연산자가 like / !like 일 때 '사용자 직접 입력' 체크박스 표시 */
|
||||
allowLikeDirectInput?: boolean;
|
||||
/** 확인 클릭 시 검사 통과 후 호출. 해당 행에 반영 후 다이얼로그 닫기는 부모에서 처리 */
|
||||
onConfirm?: (payload: FomulaDialogConfirmPayload) => void;
|
||||
}
|
||||
|
||||
const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onConfirm }: FomulaDialogProps) => {
|
||||
const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, allowLikeDirectInput = false, onConfirm }: FomulaDialogProps) => {
|
||||
const [columnInfo, setColumnInfo] = useState<VerifyColumnListResponseData | null>(null);
|
||||
const [selectedColumnInfo, setSelectedColumnInfo] = useState<SelectedVerifyColumnListResponseData | null>(null);
|
||||
const [showCommonColumns, setShowCommonColumns] = useState<boolean>(true);
|
||||
@@ -50,6 +53,8 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
/** 수식 = 토큰 배열. 한글명 중복 시 출처(공통/대상/참조)별 engColNm 보존 */
|
||||
const [formulaDisplaySegments, setFormulaDisplaySegments] = useState<string[]>([]);
|
||||
const [formulaValueSegments, setFormulaValueSegments] = useState<string[]>([]);
|
||||
/** like / !like 수식에서 '사용자 직접 입력' 체크 여부 */
|
||||
const [userDirectInput, setUserDirectInput] = useState(false);
|
||||
/** 확인 클릭 시 검사 실패 메시지 (Textarea 아래 빨간 글씨) */
|
||||
const [formulaSaveError, setFormulaSaveError] = useState<string | null>(null);
|
||||
/** 참조 통계 변경 시 안내 메시지 */
|
||||
@@ -57,6 +62,8 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
|
||||
const formulaTextareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const isUserDirectInputMode = allowLikeDirectInput && userDirectInput;
|
||||
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
/** 다이얼로그에서 쓰는 기호 → 제출용 value. 백엔드 규격에 맞게 수정 가능 */
|
||||
@@ -112,6 +119,36 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
return { display, value };
|
||||
};
|
||||
|
||||
/** 직접 입력 저장값의 양쪽 백틱 제거 (편집용) */
|
||||
const stripBacktickWrap = (value: string) => {
|
||||
if (value.length >= 2 && value.startsWith("`") && value.endsWith("`")) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/** 직접 입력 저장 시 백틱으로 감싸기 (이미 감싸진 경우 유지) */
|
||||
const wrapWithBacktick = (value: string) => {
|
||||
if (!value) return value;
|
||||
if (value.startsWith("`") && value.endsWith("`")) return value;
|
||||
return `\`${value}\``;
|
||||
};
|
||||
|
||||
const handleUserDirectInputChange = (checked: boolean) => {
|
||||
setFormulaSaveError(null);
|
||||
setUserDirectInput(checked);
|
||||
const current = formulaDisplaySegments.join("");
|
||||
if (checked) {
|
||||
const editable = stripBacktickWrap(current);
|
||||
setFormulaDisplaySegments(editable ? [editable] : []);
|
||||
setFormulaValueSegments(editable ? [editable] : []);
|
||||
return;
|
||||
}
|
||||
const { display, value } = parseDisplayToSegments(current);
|
||||
setFormulaDisplaySegments(display);
|
||||
setFormulaValueSegments(value);
|
||||
};
|
||||
|
||||
const handleFormulaKey = (val: string) => {
|
||||
setFormulaSaveError(null);
|
||||
if (val === 'backspace') {
|
||||
@@ -257,6 +294,7 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
setRefStatsCdLocal('');
|
||||
setFormulaDisplaySegments([]);
|
||||
setFormulaValueSegments([]);
|
||||
setUserDirectInput(false);
|
||||
setFormulaSaveError(null);
|
||||
setRefStatsChangeNotice(null);
|
||||
}
|
||||
@@ -264,12 +302,22 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const str = data?.kornArtopnEqtnCn ?? '';
|
||||
const { display, value } = parseDisplayToSegments(str);
|
||||
setFormulaDisplaySegments(display);
|
||||
setFormulaValueSegments(value);
|
||||
const raw = data?.kornArtopnEqtnCn ?? '';
|
||||
const wasBacktickWrapped =
|
||||
raw.length >= 2 && raw.startsWith("`") && raw.endsWith("`");
|
||||
const str = stripBacktickWrap(raw);
|
||||
if (wasBacktickWrapped && allowLikeDirectInput) {
|
||||
setFormulaDisplaySegments(str ? [str] : []);
|
||||
setFormulaValueSegments(str ? [str] : []);
|
||||
setUserDirectInput(true);
|
||||
} else {
|
||||
const { display, value } = parseDisplayToSegments(str);
|
||||
setFormulaDisplaySegments(display);
|
||||
setFormulaValueSegments(value);
|
||||
setUserDirectInput(false);
|
||||
}
|
||||
}
|
||||
}, [open, data?.kornArtopnEqtnCn, data?.vrfcDtlSn]);
|
||||
}, [open, data?.kornArtopnEqtnCn, data?.vrfcDtlSn, allowLikeDirectInput]);
|
||||
|
||||
/**
|
||||
* 재오픈 직후에는 columnInfo가 아직 없어서 parseDisplayToSegments가 컬럼명을 매칭 못하고
|
||||
@@ -278,8 +326,8 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
* engColNm 치환이 반영되도록 보정한다.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const src = data?.kornArtopnEqtnCn ?? '';
|
||||
if (!open || isUserDirectInputMode) return;
|
||||
const src = stripBacktickWrap(data?.kornArtopnEqtnCn ?? '');
|
||||
if (!src) return;
|
||||
|
||||
const displayJoined = formulaDisplaySegments.join('');
|
||||
@@ -318,12 +366,14 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || columnInfo?.kornArtopnEqtnCn == null) return;
|
||||
if (data?.kornArtopnEqtnCn !== columnInfo.kornArtopnEqtnCn) return;
|
||||
const { display, value } = parseDisplayToSegments(columnInfo.kornArtopnEqtnCn);
|
||||
if (!open || isUserDirectInputMode || columnInfo?.kornArtopnEqtnCn == null) return;
|
||||
const strippedColumn = stripBacktickWrap(columnInfo.kornArtopnEqtnCn);
|
||||
const strippedData = stripBacktickWrap(data?.kornArtopnEqtnCn ?? "");
|
||||
if (strippedData !== strippedColumn) return;
|
||||
const { display, value } = parseDisplayToSegments(strippedColumn);
|
||||
setFormulaDisplaySegments(display);
|
||||
setFormulaValueSegments(value);
|
||||
}, [open, data?.kornArtopnEqtnCn, columnInfo?.kornArtopnEqtnCn]);
|
||||
}, [open, data?.kornArtopnEqtnCn, columnInfo?.kornArtopnEqtnCn, isUserDirectInputMode]);
|
||||
|
||||
const handleChangeRefStatsCd = (next: string) => {
|
||||
if (!open) return;
|
||||
@@ -360,6 +410,7 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
if (!nextOpen) {
|
||||
setFormulaDisplaySegments([]);
|
||||
setFormulaValueSegments([]);
|
||||
setUserDirectInput(false);
|
||||
setFormulaSaveError(null);
|
||||
setSelectedColumnInfo(null);
|
||||
setRefStatsCdLocal('');
|
||||
@@ -463,20 +514,44 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex flex-col gap-2">
|
||||
<h3 className="font-bold text-title text-[14px]">수식 미리보기</h3>
|
||||
<div className="flex items-center gap-3">
|
||||
<h3 className="font-bold text-title text-[14px]">수식 미리보기</h3>
|
||||
{allowLikeDirectInput && (
|
||||
<label className="flex items-center gap-2 text-[13px] text-gray-700 cursor-pointer select-none">
|
||||
<Checkbox
|
||||
id="userDirectInput"
|
||||
checked={userDirectInput}
|
||||
onCheckedChange={(checked) =>
|
||||
handleUserDirectInputChange(checked === true)
|
||||
}
|
||||
/>
|
||||
사용자 직접 입력
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<Textarea
|
||||
className="w-full flex-1 bg-gray-100 resize-none !text-3xl p-4 !leading-loose text-gray-600 font-bold"
|
||||
className={`w-full flex-1 resize-none !text-3xl p-4 !leading-loose font-bold ${
|
||||
isUserDirectInputMode
|
||||
? "bg-white text-gray-900"
|
||||
: "bg-gray-100 text-gray-600"
|
||||
}`}
|
||||
id="formulaDisplayInput"
|
||||
ref={formulaTextareaRef}
|
||||
value={formulaDisplaySegments.join('')}
|
||||
onChange={(e) => {
|
||||
setFormulaSaveError(null);
|
||||
if (isUserDirectInputMode) {
|
||||
const raw = e.target.value;
|
||||
setFormulaDisplaySegments(raw ? [raw] : []);
|
||||
setFormulaValueSegments(raw ? [raw] : []);
|
||||
return;
|
||||
}
|
||||
const { display, value } = parseDisplayToSegments(e.target.value);
|
||||
setFormulaDisplaySegments(display);
|
||||
setFormulaValueSegments(value);
|
||||
}}
|
||||
rows={4}
|
||||
readOnly
|
||||
readOnly={!isUserDirectInputMode}
|
||||
/>
|
||||
{formulaSaveError && (
|
||||
<p className="text-sm text-red-600 mt-1" role="alert">
|
||||
@@ -516,6 +591,7 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
value={c.engColNm}
|
||||
size="sm"
|
||||
color="lightGray"
|
||||
disabled={isUserDirectInputMode}
|
||||
onClick={() => handleFormulaColumn(c.kornColNm, c.engColNm)}
|
||||
>
|
||||
{c.kornColNm}
|
||||
@@ -558,6 +634,7 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
type="button"
|
||||
value={t.engColNm}
|
||||
size="sm"
|
||||
disabled={isUserDirectInputMode}
|
||||
onClick={() => handleFormulaColumn(t.kornColNm, t.engColNm)}
|
||||
>
|
||||
{t.kornColNm}
|
||||
@@ -619,6 +696,7 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
value={r.engColNm}
|
||||
size="sm"
|
||||
color="sky"
|
||||
disabled={isUserDirectInputMode}
|
||||
onClick={() => handleFormulaColumn(r.kornColNm, r.engColNm)}
|
||||
>
|
||||
{r.kornColNm}
|
||||
@@ -636,6 +714,23 @@ const FomulaDialog = ({ open, onOpenChange, data, trgtStatsCd, trgtStatsNm, onCo
|
||||
size="lg"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (isUserDirectInputMode) {
|
||||
setFormulaSaveError(null);
|
||||
const raw = formulaDisplaySegments.join("");
|
||||
const wrapped = wrapWithBacktick(raw);
|
||||
const isEmptyFormula = raw.trim().length === 0;
|
||||
onConfirm?.({
|
||||
kornArtopnEqtnCn: isEmptyFormula ? raw : wrapped,
|
||||
engArtopnEqtnCn: isEmptyFormula ? raw : wrapped,
|
||||
refStatsCd: isEmptyFormula
|
||||
? null
|
||||
: refStatsCdLocal
|
||||
? refStatsCdLocal
|
||||
: null,
|
||||
});
|
||||
onOpenChange?.(false);
|
||||
return;
|
||||
}
|
||||
const err = validateFormulaForSave(formulaValueSegments);
|
||||
if (err) {
|
||||
setFormulaSaveError(err);
|
||||
|
||||
+13
-2
@@ -27,6 +27,14 @@ const zoneClassName: Record<ClauseZone, string> = {
|
||||
"zone-result": "bg-[#fff4ea]",
|
||||
};
|
||||
|
||||
/** 저장용 백틱 래핑은 숨기고 사용자에게만 표시 */
|
||||
const stripBacktickWrap = (value: string) => {
|
||||
if (value.length >= 2 && value.startsWith("`") && value.endsWith("`")) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const FormulaComponents = ({
|
||||
data,
|
||||
index,
|
||||
@@ -38,6 +46,9 @@ const FormulaComponents = ({
|
||||
handleFomulaDialog,
|
||||
}: FormulaComponentsProps) => {
|
||||
const cmprOpert = data?.cmprOpert;
|
||||
const displayFormula = data
|
||||
? stripBacktickWrap(data.kornArtopnEqtnCn ?? "")
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -56,8 +67,8 @@ const FormulaComponents = ({
|
||||
className="h-[3.6rem] flex-1 !cursor-pointer rounded-md border-[#d8dce3] text-[13px]"
|
||||
placeholder="클릭하여 수식을 등록해주세요."
|
||||
id={`formulaInput-${index ?? 0}`}
|
||||
value={data ? data.kornArtopnEqtnCn : ""}
|
||||
title={data ? data.kornArtopnEqtnCn : ""}
|
||||
value={displayFormula}
|
||||
title={displayFormula}
|
||||
onClick={() => handleFomulaDialog?.(true, data, index)}
|
||||
readOnly
|
||||
/>
|
||||
|
||||
+34
-9
@@ -16,16 +16,24 @@ type OperatorItem = {
|
||||
opertKornNm: string;
|
||||
};
|
||||
|
||||
// opertSeCd 별 operators 목록을 전역 캐싱해서, 수식 블록이 여러 개여도 중복 요청이 발생하지 않게 한다.
|
||||
const operatorsCache: Map<
|
||||
string,
|
||||
OperatorItem[] | Promise<OperatorItem[]>
|
||||
> = new Map();
|
||||
|
||||
interface OperatorsComponentsProps {
|
||||
cmprOpert?: string | null;
|
||||
onValueChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
opertSeCd?: string;
|
||||
}
|
||||
|
||||
const OperatorsComponents = ({
|
||||
cmprOpert,
|
||||
onValueChange,
|
||||
placeholder = "연산자 없음",
|
||||
opertSeCd = "opertSeCd=CMPR"
|
||||
}: OperatorsComponentsProps) => {
|
||||
const [operators, setOperators] = useState<OperatorItem[]>([]);
|
||||
const [localValue, setLocalValue] = useState<string>(cmprOpert ?? "");
|
||||
@@ -35,14 +43,31 @@ const OperatorsComponents = ({
|
||||
}, [cmprOpert]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOperators = async () => {
|
||||
const response = await generalverificationApiService.getVrfcOperators();
|
||||
if (response.status === 'success' && response.data) {
|
||||
setOperators(response.data);
|
||||
}
|
||||
};
|
||||
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로 표시
|
||||
const isControlled = onValueChange !== undefined;
|
||||
@@ -73,4 +98,4 @@ const OperatorsComponents = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default OperatorsComponents;
|
||||
export default OperatorsComponents;
|
||||
|
||||
@@ -30,6 +30,7 @@ export type RealGridColumnInfo = {
|
||||
text?: string;
|
||||
expression?: "sum" | "avg" | "count" | "max" | "min";
|
||||
numberFormat?: string;
|
||||
styleName?: string;
|
||||
valueCallback?: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
@@ -42,6 +43,7 @@ export type RealGridColumnInfo = {
|
||||
text?: string;
|
||||
expression?: "sum" | "avg" | "count" | "max" | "min";
|
||||
numberFormat?: string;
|
||||
styleName?: string;
|
||||
valueCallback?: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
@@ -262,6 +264,7 @@ export type ColumnConfig = {
|
||||
text?: string;
|
||||
expression?: "sum" | "avg" | "count" | "max" | "min";
|
||||
numberFormat?: string;
|
||||
styleName?: string;
|
||||
valueCallback?: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
@@ -275,6 +278,7 @@ export type ColumnConfig = {
|
||||
text?: string;
|
||||
expression?: "sum" | "avg" | "count" | "max" | "min";
|
||||
numberFormat?: string;
|
||||
styleName?: string;
|
||||
valueCallback?: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
@@ -350,6 +354,8 @@ export type CellStyleCallbackResult =
|
||||
| string
|
||||
| {
|
||||
styleName?: string;
|
||||
/** RealGrid 엑셀 export 전용 스타일 (applyDynamicStyles 시 사용) */
|
||||
exportStyleName?: string;
|
||||
editable?: boolean;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
@@ -400,12 +406,14 @@ export type DataGridProps = {
|
||||
/** 있으면보내기 파일명: `{메뉴명}_{suffix}.xlsx` (TabDataGrid의 탭 라벨 접미사와 동일) */
|
||||
excelExportFileSuffix?: string;
|
||||
isExcelImport?: boolean; // 엑셀 가져오기 유무
|
||||
/** 엑셀 가져오기 시 editable:false 컬럼 값 무시 (기본 true) */
|
||||
/** 엑셀 가져오기 시 editable:false 컬럼 값 무시 (기본 false — 보내기한 값을 그대로 반영) */
|
||||
excelImportSkipNonEditable?: boolean;
|
||||
/** 엑셀 import 시 admdstCd 앞 2자리와 비교할 조회 sewerBzmnCd(SearchForm bsnmCd) */
|
||||
excelImportSewerBzmnCd?: string;
|
||||
/** 엑셀보내기 파일명에 조회 시도·시군구·연도 접미사 추가 */
|
||||
excelExportSearchContext?: ExcelExportSearchContext;
|
||||
/** true면 엑셀 export 시 cellStyleCallback 색상·고정 컬럼 배경을 모두 반영 (집계 total 화면용) */
|
||||
excelExportApplyStyles?: boolean;
|
||||
isExcelExport?: boolean;
|
||||
onRowsChange?: (rows: Record<string, string | number>[]) => void;
|
||||
fixedColumns?: number; // 고정할 컬럼 개수 (좌측부터)
|
||||
@@ -418,6 +426,20 @@ export type DataGridProps = {
|
||||
cellStyleCallback?: CellStyleCallback;
|
||||
/** 셀 hover 시 툴팁 문구 (예: verificationResult의 rsnExpln) */
|
||||
cellTooltipCallback?: CellTooltipCallback;
|
||||
/**
|
||||
* commonInfo.verificationResult — 컬럼 fieldName(또는 name) 키로 총계 footer 강조/툴팁.
|
||||
* 컬럼 footer.styleName이 있으면 그쪽이 우선.
|
||||
* ※ 행 단위 「검증 오류 항목 우선보기」에는 포함되지 않음.
|
||||
*/
|
||||
footerVerificationResult?: Record<
|
||||
string,
|
||||
Array<{
|
||||
vrfcCd: string;
|
||||
rsnExpln: string;
|
||||
nowVlCn?: string;
|
||||
compVlCn?: string;
|
||||
}>
|
||||
> | null;
|
||||
/** 셀 편집 완료 시 호출 (RealGrid onEditRowChanged) */
|
||||
onCellEdited?: CellEditedCallback;
|
||||
/** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */
|
||||
@@ -458,7 +480,7 @@ export type TabDataGridProps = {
|
||||
columnInfo: ColumnInfoList[];
|
||||
rows?: Record<string, string | number>[];
|
||||
isExcelImport?: boolean; // 엑셀 가져오기 유무
|
||||
/** 엑셀 가져오기 시 editable:false 컬럼 값 무시 (기본 true) */
|
||||
/** 엑셀 가져오기 시 editable:false 컬럼 값 무시 (기본 false — 보내기한 값을 그대로 반영) */
|
||||
excelImportSkipNonEditable?: boolean;
|
||||
/** 엑셀 import 시 admdstCd 앞 2자리와 비교할 조회 sewerBzmnCd(SearchForm bsnmCd) */
|
||||
excelImportSewerBzmnCd?: string;
|
||||
|
||||
@@ -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: '전화번호' } },
|
||||
|
||||
|
||||
+3
-3
@@ -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' },
|
||||
|
||||
+2
-2
@@ -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' },
|
||||
|
||||
+2
-2
@@ -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
|
||||
{
|
||||
|
||||
+2
-2
@@ -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' },
|
||||
|
||||
// 총계
|
||||
{
|
||||
|
||||
+2
-2
@@ -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' },
|
||||
|
||||
+2
-2
@@ -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' },
|
||||
|
||||
+2
-2
@@ -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: '직 제' } },
|
||||
|
||||
+2
-2
@@ -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: '직 제' } },
|
||||
|
||||
+3
-3
@@ -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(
|
||||
|
||||
+3
-3
@@ -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' },
|
||||
|
||||
// 발생량
|
||||
{
|
||||
|
||||
+2
-2
@@ -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: '찌꺼기처리시설명' } },
|
||||
|
||||
+3
-3
@@ -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: '하수정비기본계획목표년도' } },
|
||||
@@ -190,7 +190,7 @@ export const RegionalFactStatusInpColumn = {
|
||||
{ name: 'exlnOtflCnt', fieldName: 'exlnOtflCnt', header: { text: '우수토구(개소)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
|
||||
// 하수도대장 전산화
|
||||
{ name: 'sewerLdgrEdpPrlg', fieldName: 'sewerLdgrEdpPrlg', header: { text: '하수도대장전산화연장(m)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'sewerLdgrEdpPrlg', fieldName: 'sewerLdgrEdpPrlg', header: { text: '하수도대장전산화연장(m)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' },
|
||||
{
|
||||
name: 'sewerLdgrEdpRt',
|
||||
fieldName: 'sewerLdgrEdpRt',
|
||||
@@ -199,7 +199,7 @@ export const RegionalFactStatusInpColumn = {
|
||||
editable: false,
|
||||
styleName: 'rg-cell-total',
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: '#,##0.00',
|
||||
dataFormat: '#,##0.0',
|
||||
computed: {
|
||||
numeratorFields: ['sewerLdgrEdpPrlg'],
|
||||
denominatorFields: ['cbsysSqrePrlg', 'cbsysCiclPrlg', 'cbsysOpnPrlg', 'cbsysGttrPrlg', 'drtwPplSqrePrlg', 'drtwPplCiclPrlg', 'exlnPplSqrePrlg', 'exlnPplCiclPrlg', 'exlnPplOpnPrlg', 'exlnPplGttrPrlg'],
|
||||
|
||||
+2
-2
@@ -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' },
|
||||
|
||||
+2
-2
@@ -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)
|
||||
{
|
||||
|
||||
+3
-3
@@ -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(
|
||||
|
||||
+3
-3
@@ -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' },
|
||||
|
||||
+2
-2
@@ -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' },
|
||||
|
||||
// 재정별 사업비
|
||||
{
|
||||
|
||||
+2
-2
@@ -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' },
|
||||
|
||||
+4
-4
@@ -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' },
|
||||
|
||||
+2
-2
@@ -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' },
|
||||
|
||||
+2
-2
@@ -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
-2
@@ -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
-2
@@ -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);
|
||||
@@ -14,42 +14,58 @@ function truncateToFirstDecimal(value: number): number {
|
||||
return Math.floor(value * 10) / 10;
|
||||
}
|
||||
|
||||
/** computed sum용 스케일 (표시 최대 소수 3자리 + 여유) */
|
||||
const SUM_DECIMAL_SCALE = 1e6;
|
||||
|
||||
/**
|
||||
* IEEE 754 가산 잡음 제거.
|
||||
* 통계 표시 자릿수(대개 0~3)보다 충분히 큰 자리로 반올림해
|
||||
* 112.229999… → 112.23 처럼 만들어 RealGrid `#,##0.000` 표시 오차를 막는다.
|
||||
* 스케일 정수 합을 Number로 되돌릴 때 생기는 IEEE 오차 보정.
|
||||
* 예: 9003630000/1e6 → 9003.62999… (RealGrid trunc 시 9003.62)
|
||||
* 아주 작은 양수를 더해 표시가 9003.63이 되게 한다.
|
||||
*/
|
||||
function sanitizeFloatNoise(value: number): number {
|
||||
if (!Number.isFinite(value)) return 0;
|
||||
return Math.round(value * 1e6) / 1e6;
|
||||
function numberFromScaledSum(scaledSum: number): number {
|
||||
if (scaledSum === 0) return 0;
|
||||
const raw = scaledSum / SUM_DECIMAL_SCALE;
|
||||
if (scaledSum % SUM_DECIMAL_SCALE === 0) return raw;
|
||||
return raw + Math.sign(raw) * 1e-12;
|
||||
}
|
||||
|
||||
/**
|
||||
* 각 값을 소수 6자리 정수로 올린 뒤 더한 다음 다시 나눈다.
|
||||
* 예: 2712.5 + 6291.13 → (2712500000 + 6291130000) / 1e6 = 9003.63
|
||||
*/
|
||||
function sumViaScaledIntegers(nums: number[]): number {
|
||||
let scaledSum = 0;
|
||||
for (const n of nums) {
|
||||
if (!Number.isFinite(n)) continue;
|
||||
scaledSum += Math.round(n * SUM_DECIMAL_SCALE);
|
||||
}
|
||||
return numberFromScaledSum(scaledSum);
|
||||
}
|
||||
|
||||
/** 합계용 valueCallback 생성 */
|
||||
export function createSumValueCallback(fieldNames: string[]): DataFieldValueCallback {
|
||||
return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => {
|
||||
let sum = 0;
|
||||
const nums: number[] = [];
|
||||
for (const f of fieldNames) {
|
||||
const idx = fieldNamesArr.indexOf(f);
|
||||
if (idx >= 0) sum += toNum(values[idx]);
|
||||
if (idx >= 0) nums.push(toNum(values[idx]));
|
||||
}
|
||||
return sanitizeFloatNoise(sum);
|
||||
return sumViaScaledIntegers(nums);
|
||||
};
|
||||
}
|
||||
|
||||
/** 평균용 valueCallback 생성 */
|
||||
export function createAvgValueCallback(fieldNames: string[]): DataFieldValueCallback {
|
||||
return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => {
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
const nums: number[] = [];
|
||||
for (const f of fieldNames) {
|
||||
const idx = fieldNamesArr.indexOf(f);
|
||||
if (idx >= 0) {
|
||||
const val = toNum(values[idx]);
|
||||
sum += val;
|
||||
count += 1;
|
||||
nums.push(toNum(values[idx]));
|
||||
}
|
||||
}
|
||||
return count > 0 ? sum / count : 0;
|
||||
if (nums.length === 0) return 0;
|
||||
return sumViaScaledIntegers(nums) / nums.length;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,12 +74,12 @@ function sumFieldValues(
|
||||
fieldNamesArr: string[],
|
||||
values: unknown[],
|
||||
): number {
|
||||
let sum = 0;
|
||||
const nums: number[] = [];
|
||||
for (const f of fields) {
|
||||
const idx = fieldNamesArr.indexOf(f);
|
||||
if (idx >= 0) sum += toNum(values[idx]);
|
||||
if (idx >= 0) nums.push(toNum(values[idx]));
|
||||
}
|
||||
return sum;
|
||||
return sumViaScaledIntegers(nums);
|
||||
}
|
||||
|
||||
/** halfSumOfSums 한 건 — (sum(first) + sum(second)) / 2, 소수 내림 */
|
||||
|
||||
@@ -30,7 +30,7 @@ function flattenColumnLayoutNames(layout: ColumnLayoutItem[]): string[] {
|
||||
}
|
||||
|
||||
export type ExcelImportColumnFieldNamesOptions = {
|
||||
/** false면 editable:false 컬럼도 import (기본 true — 읽기전용·계산 컬럼은 엑셀 값 무시) */
|
||||
/** false면 editable:false 컬럼도 import (기본 false — 보내기한 값을 그대로 반영) */
|
||||
skipNonEditable?: boolean;
|
||||
};
|
||||
|
||||
@@ -61,7 +61,8 @@ export function getExcelImportColumnFieldNames(
|
||||
columnLayout?: ColumnLayoutItem[],
|
||||
options?: ExcelImportColumnFieldNamesOptions,
|
||||
): string[] {
|
||||
const skipNonEditable = options?.skipNonEditable !== false;
|
||||
/** true일 때만 읽기전용 컬럼 스킵. 기본(미지정/false)은 보내기 값 그대로 반영 */
|
||||
const skipNonEditable = options?.skipNonEditable === true;
|
||||
const excludedNames = new Set(
|
||||
columns.filter((c) => c.excludeFromExcelExport).map((c) => c.name),
|
||||
);
|
||||
@@ -137,7 +138,10 @@ export function mergeExcelImportWithExistingRows(
|
||||
const existingRow = existingRows[index];
|
||||
if (!existingRow) return { ...importedRow };
|
||||
|
||||
const merged: Record<string, string | number> = { ...existingRow, ...importedRow };
|
||||
const merged: Record<string, string | number> = {
|
||||
...existingRow,
|
||||
...importedRow,
|
||||
};
|
||||
if (preserveSet) {
|
||||
for (const fieldName of preserveSet) {
|
||||
const preserved = existingRow[fieldName];
|
||||
@@ -146,6 +150,8 @@ export function mergeExcelImportWithExistingRows(
|
||||
}
|
||||
}
|
||||
}
|
||||
// 엑셀 반영 후에는 이전 검증오류 메타를 제거 (cellStyleCallback 스타일/툴팁 초기화)
|
||||
delete (merged as { verificationResult?: unknown }).verificationResult;
|
||||
return merged;
|
||||
});
|
||||
}
|
||||
@@ -193,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 {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { StatisticsInputCommonInfoBase } from "@/entities/statistics/model/statistics-interface";
|
||||
|
||||
import type { VerificationResultLikeMap } from "./verification-result-display";
|
||||
|
||||
export function isVerificationFailedFromCommonInfo(
|
||||
commonInfo: Pick<StatisticsInputCommonInfoBase, "isVerificationFailed"> | null | undefined,
|
||||
): boolean {
|
||||
@@ -15,3 +17,17 @@ export function patchVerificationFailedByTab(
|
||||
next.set(tabKey, isVerificationFailedFromCommonInfo(commonInfo));
|
||||
return next;
|
||||
}
|
||||
|
||||
/** commonInfo.verificationResult → 탭별 총계 footer 검증 (우선보기 대상 아님) */
|
||||
export function patchFooterVerificationByTab(
|
||||
prev: Map<string, VerificationResultLikeMap | null>,
|
||||
tabKey: string,
|
||||
commonInfo:
|
||||
| { verificationResult?: VerificationResultLikeMap | null }
|
||||
| null
|
||||
| undefined,
|
||||
): Map<string, VerificationResultLikeMap | null> {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, commonInfo?.verificationResult ?? null);
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/** 셀/푸터 검증 오류 한 건 (입력 조회 verificationResult 항목) */
|
||||
export type VerificationResultLikeItem = {
|
||||
vrfcCd: string;
|
||||
rsnExpln: string;
|
||||
nowVlCn?: string;
|
||||
compVlCn?: string;
|
||||
};
|
||||
|
||||
export type VerificationResultLikeMap = Record<
|
||||
string,
|
||||
VerificationResultLikeItem[]
|
||||
>;
|
||||
|
||||
/** 검증 오류 배열 → RealGrid styleName */
|
||||
export function getVerificationStyleName(
|
||||
errors: VerificationResultLikeItem[] | null | undefined,
|
||||
): string | undefined {
|
||||
if (!Array.isArray(errors) || errors.length === 0) return undefined;
|
||||
const hasImr = errors.some(
|
||||
(e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB",
|
||||
);
|
||||
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
|
||||
}
|
||||
|
||||
/** 검증 오류 배열 → 툴팁/메시지 문구 */
|
||||
export function formatVerificationTooltip(
|
||||
errors: VerificationResultLikeItem[] | null | undefined,
|
||||
): string | undefined {
|
||||
if (!Array.isArray(errors) || errors.length === 0) return undefined;
|
||||
const text = errors
|
||||
.map((e) => {
|
||||
const prefix =
|
||||
e.vrfcCd === "IMR"
|
||||
? "시계열 검증 - "
|
||||
: e.vrfcCd === "NV"
|
||||
? "일반검증 - "
|
||||
: "모집단 검증 - ";
|
||||
const comp = e.compVlCn ?? "";
|
||||
const now = e.nowVlCn ?? "";
|
||||
return `${prefix}${e.rsnExpln}\n(검증 기준값: ${comp} / 현재 입력값: ${now})`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
return text.length > 0 ? text : undefined;
|
||||
}
|
||||
|
||||
/** fieldName 및 alias 중 첫 매칭 오류 배열 */
|
||||
export function getVerificationErrorsForField(
|
||||
result: VerificationResultLikeMap | null | undefined,
|
||||
fieldName: string,
|
||||
aliases?: readonly string[],
|
||||
): VerificationResultLikeItem[] | undefined {
|
||||
if (!result) return undefined;
|
||||
const direct = result[fieldName];
|
||||
if (Array.isArray(direct) && direct.length > 0) return direct;
|
||||
if (aliases) {
|
||||
for (const alias of aliases) {
|
||||
const found = result[alias];
|
||||
if (Array.isArray(found) && found.length > 0) return found;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -89,9 +89,9 @@ export class GeneralverificationApiService implements GeneralverificationService
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getVrfcOperators(): Promise<VrfcOperatorsResponse> {
|
||||
public async getVrfcOperators(param: string): Promise<VrfcOperatorsResponse> {
|
||||
const response = await stClient.get<VrfcOperatorsResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/operators`,
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/operators?${param}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ export class StatisticsApiService implements StatisticsService {
|
||||
const response = await stClient.post<StatsInputStatusApproveResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.STATS}/approve`,
|
||||
param.list,
|
||||
{ params: { type } },
|
||||
withAuthHeader({ params: { type } }),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
@@ -290,6 +290,7 @@ export class StatisticsApiService implements StatisticsService {
|
||||
const response = await stClient.post<ReviewSubmitResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/submit`,
|
||||
param,
|
||||
withAuthHeader(),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ export interface VerifyDetailResponseDto {
|
||||
kornArtopnEqtnCn: string;
|
||||
engArtopnEqtnCn: string;
|
||||
cmprOpert: string | null;
|
||||
/** group by 여부 (Y/N) */
|
||||
grpByYn: string | null;
|
||||
}
|
||||
|
||||
export interface VerifyDetailData {
|
||||
@@ -175,6 +177,8 @@ export interface VerifyDetailRegistData {
|
||||
engArtopnEqtnCn: string;
|
||||
cmprOpert: string | null;
|
||||
eqtnCombSeq: number;
|
||||
/** group by 여부 (Y/N) */
|
||||
grpByYn: string;
|
||||
}
|
||||
|
||||
export interface VerifyDetailRegistRequest {
|
||||
@@ -280,7 +284,7 @@ export interface GeneralverificationService {
|
||||
vrfcSn: number,
|
||||
userUnqId: string,
|
||||
): Promise<DeleteVrfcMasterResponse>;
|
||||
getVrfcOperators(): Promise<VrfcOperatorsResponse>;
|
||||
getVrfcOperators(param: string): Promise<VrfcOperatorsResponse>;
|
||||
getVrfcDetail(param: number): Promise<GeneralverificationDetailResponse>;
|
||||
getVerifyColumnList(
|
||||
param: VerifyColumnListRequest,
|
||||
|
||||
+13
-11
@@ -1,5 +1,16 @@
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 검증 결과 한 건 (필드별 메시지) */
|
||||
export interface VerificationResultItem {
|
||||
vrfcCd: string;
|
||||
rsnExpln: string;
|
||||
nowVlCn: string;
|
||||
compVlCn: string;
|
||||
}
|
||||
|
||||
/** 필드명 → 검증 결과 배열. 예: { popltnSm: [{ type, rsn }], wstrPrmTrt: [...] } */
|
||||
export type SewerageSupplyRateVerificationResult = Record<string, VerificationResultItem[]>;
|
||||
|
||||
// 하수도 보급률 데이터 항목
|
||||
export interface SewerageSupplyRateCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
sewerBzmnArea: number;
|
||||
@@ -12,19 +23,10 @@ export interface SewerageSupplyRateCommonInfo extends StatisticsInputCommonInfoB
|
||||
ctpvNm: string;
|
||||
statsYr: string;
|
||||
statsCd: string;
|
||||
/** 푸터(합계) 등 commonInfo 필드 검증 결과 */
|
||||
verificationResult?: SewerageSupplyRateVerificationResult | null;
|
||||
}
|
||||
|
||||
/** 검증 결과 한 건 (필드별 메시지) */
|
||||
export interface VerificationResultItem {
|
||||
vrfcCd: string;
|
||||
rsnExpln: string;
|
||||
nowVlCn: string;
|
||||
compVlCn: string;
|
||||
}
|
||||
|
||||
/** 필드명 → 검증 결과 배열. 예: { popltnSm: [{ type, rsn }], wstrPrmTrt: [...] } */
|
||||
export type SewerageSupplyRateVerificationResult = Record<string, VerificationResultItem[]>;
|
||||
|
||||
/** 하수도 보급률 목록 API 응답 한 행 (실제 응답 필드만) */
|
||||
export interface SewerageSupplyRateItem {
|
||||
admdstCd: string;
|
||||
|
||||
@@ -378,11 +378,13 @@ export interface ReviewInputSaveRequest {
|
||||
|
||||
export interface ReviewSubmitRequest {
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
/** 항목 단위 제출: string / 통계 입력 현황 일괄 제출: string[] */
|
||||
sewerBzmnCd: string | string[];
|
||||
mdfrId: string;
|
||||
splmntOpnnCn: string;
|
||||
wrterOpnnCn: string;
|
||||
/** 통계항목 단위 제출 시 사용 (통계 입력 현황 일괄 제출에는 미사용) */
|
||||
statsCd?: string;
|
||||
splmntOpnnCn?: string;
|
||||
wrterOpnnCn?: string;
|
||||
}
|
||||
|
||||
export interface ReviewSubmitResponse {
|
||||
@@ -435,7 +437,8 @@ export interface SubmitVerifyExceptionResponse {
|
||||
export interface ReviewerInfoRequest {
|
||||
sewerBzmnCd: string;
|
||||
statsYr: string;
|
||||
statsCd: string;
|
||||
/** 통계항목 단위 조회 시 사용 (통계 입력 현황 일괄 제출에는 미사용) */
|
||||
statsCd?: string;
|
||||
instSeCd : 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 {
|
||||
|
||||
+12
-104
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user