From 3b5fee047b5ce5a740fc26579680789495750d50 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Thu, 13 Aug 2026 15:30:00 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[ST]=20-=20fix:=20qa=20=EC=A1=B0=EC=B9=98,?= =?UTF-8?q?=20=EC=9D=BC=EB=B0=98=20=EA=B2=80=EC=A6=9D=20=EA=B0=9C=EB=B0=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/button/SumitButton.tsx | 10 +- apps/st/src/components/css/DataGrid.css | 23 + apps/st/src/components/module/DataGrid.tsx | 301 ++++- apps/st/src/components/module/TabDataGrid.tsx | 2 +- .../input/ExcretaTreatmentFacilityInp.tsx | 14 +- .../input/PrivateSewageFacilityInp.tsx | 33 +- .../input/PublicSewageFacilityInp.tsx | 57 +- .../statistics/input/PumpingStationInp.tsx | 14 +- .../input/SewageExcretaRelatedInp.tsx | 36 +- .../input/SewageExcretaSlurgeInp.tsx | 32 +- .../statistics/input/SewagePipeInp.tsx | 36 +- .../input/SewagePipeMaintainInp.tsx | 30 +- .../input/SewageReservoirPumpInp.tsx | 30 +- .../statistics/input/SewerageFinanceInp.tsx | 36 +- .../input/SeweragePenetrationRateInp.tsx | 49 +- .../input/SewerageServiceLevelInp.tsx | 30 +- .../statistics/input/WaterReuseInp.tsx | 35 +- .../statistics/review/ReviewSubmitDialog.tsx | 64 +- .../statistics/review/StatsInputStatusRvw.tsx | 87 +- .../generalVerification/DetailComponent.tsx | 1098 ++++++++++++----- .../generalVerification/FomulaDialog.tsx | 125 +- .../generalVerification/FormulaComponents.tsx | 15 +- .../OperatorsComponents.tsx | 10 +- apps/st/src/components/type/DataGrid.type.ts | 22 +- .../sewagePipe/RegionalFactStatusInpColumn.ts | 4 +- .../util/grid/computedFieldCallbacks.ts | 52 +- .../st/src/components/util/gridExcelExport.ts | 12 +- .../util/input/verification-failed-flag.ts | 16 + .../util/input/verification-result-display.ts | 63 + .../api/general-verification-service.ts | 4 +- .../statistics/api/statistics-service.ts | 3 +- .../model/general-verification-interface.ts | 6 +- ...tics-sewage-supply-rate-input-interface.ts | 24 +- .../statistics/model/statistics-interface.ts | 13 +- 34 files changed, 1875 insertions(+), 511 deletions(-) create mode 100644 apps/st/src/components/util/input/verification-result-display.ts diff --git a/apps/st/src/components/button/SumitButton.tsx b/apps/st/src/components/button/SumitButton.tsx index 0443889b7..0c0875ed2 100644 --- a/apps/st/src/components/button/SumitButton.tsx +++ b/apps/st/src/components/button/SumitButton.tsx @@ -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 ( - ); }; -export default SubmitButton; \ No newline at end of file +export default SubmitButton; diff --git a/apps/st/src/components/css/DataGrid.css b/apps/st/src/components/css/DataGrid.css index f4a2020f0..228c16703 100644 --- a/apps/st/src/components/css/DataGrid.css +++ b/apps/st/src/components/css/DataGrid.css @@ -23,6 +23,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 +414,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 +428,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, diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index 896659c00..cdc2e570b 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -24,6 +24,12 @@ 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, getExcelImportColumnFieldNames, @@ -55,6 +61,34 @@ function mergeCellStyleCallbackResult( }; } +/** 컬럼 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 = { styleCallback?: ( grid: unknown, @@ -782,7 +816,7 @@ const DataGrid = ({ excelExportFileSuffix, excelExportSearchContext, isExcelImport = true, - excelImportSkipNonEditable = true, + excelImportSkipNonEditable = false, excelImportSewerBzmnCd, isExcelExport = true, fixedColumns, @@ -793,6 +827,7 @@ const DataGrid = ({ getGridRef, cellStyleCallback, cellTooltipCallback, + footerVerificationResult = null, onCellEdited, onRowClick, onDataCellClick, @@ -824,6 +859,9 @@ const DataGrid = ({ const cellTooltipCallbackRef = useRef( cellTooltipCallback, ); + const footerVerificationResultRef = useRef< + VerificationResultLikeMap | null | undefined + >(footerVerificationResult); const onCellEditedRef = useRef(onCellEdited); const onDataCellClickRef = useRef(onDataCellClick); const onScrollToBottomRef = useRef<(() => void) | undefined>( @@ -848,6 +886,7 @@ const DataGrid = ({ rowDataRef.current = rowData; cellStyleCallbackRef.current = cellStyleCallback; cellTooltipCallbackRef.current = cellTooltipCallback; + footerVerificationResultRef.current = footerVerificationResult; onScrollToBottomRef.current = onScrollToBottom; onItemCheckedRef.current = onItemChecked; onItemAllCheckedRef.current = onItemAllChecked; @@ -855,6 +894,7 @@ const DataGrid = ({ onDataCellClickRef.current = onDataCellClick; const hasVerificationErrors = rowsHaveVerificationErrors(rowData); + // 총계 footer(commonInfo.verificationResult)는 행 우선보기 대상이 아님 const showVerifyPriorityToggle = hasVerificationErrors; // 조회 등으로 rowData가 교체되면 우선보기 해제 (자체 setRowData는 스킵) @@ -1338,10 +1378,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); @@ -1703,12 +1747,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, + ) ?? "", }; } } @@ -1797,7 +1855,7 @@ useEffect(() => { } } - // footer가 단일 객체이고 valueCallback이 있는 경우 + // footer가 단일 객체이고 valueCallback/styleName이 있는 경우 columns.forEach((col) => { if (!col.footer || Array.isArray(col.footer)) return; @@ -1812,6 +1870,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}:`, @@ -1871,8 +1936,45 @@ useEffect(() => { 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 +2062,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 +2084,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 +2142,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 +2222,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 +2242,6 @@ useEffect(() => { return; } - // 값 있는 셀 RealGrid 툴팁과 같이: 본문 다음 줄에 (검증 기준값...)이 오도록 - // 템플릿 리터럴 들여쓰기 공백은 제거 const normalizedMessage = msg.replace(/\n[ \t]+/g, "\n"); const key = `${index.itemIndex}:${fieldName}`; @@ -2096,7 +2275,7 @@ useEffect(() => { } clearEmptyCellHoverTooltip(); }; - }, [cellTooltipCallback]); + }, [cellTooltipCallback, footerVerificationResult]); useEffect(() => { const gridView = gridRef.current?.gridView; @@ -2433,19 +2612,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 +2655,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 +2710,7 @@ useEffect(() => { gv as GridViewWithSetColumnProperty, column, ); - if (cellTooltipCallbackRef.current) { + if (cellTooltipCallbackRef.current || footerVerificationResultRef.current) { ( gv as { setColumnProperty?: ( @@ -2588,13 +2811,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 +2842,11 @@ useEffect(() => { {emptyCellHoverTooltip && createPortal(
diff --git a/apps/st/src/components/module/TabDataGrid.tsx b/apps/st/src/components/module/TabDataGrid.tsx index 3d47b99a8..a2679e989 100644 --- a/apps/st/src/components/module/TabDataGrid.tsx +++ b/apps/st/src/components/module/TabDataGrid.tsx @@ -282,7 +282,7 @@ function createNumberComparer(dp: RealGridDataProviderComparer): (field: string const TabDataGrid = ({ columnInfo, isExcelImport = true, - excelImportSkipNonEditable = true, + excelImportSkipNonEditable = false, excelImportSewerBzmnCd, excelExportSearchContext, isExcelExport = true, diff --git a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx index 62c8af60a..c29bdfb89 100644 --- a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx @@ -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(null); const [statsCd, setStatsCd] = useState(""); const [isVerificationFailed, setIsVerificationFailed] = useState(false); + const [footerVerificationResult, setFooterVerificationResult] = + useState(null); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [isLoading, setIsLoading] = useState(false); const [sggOptions, setSggOptions] = useState([]); @@ -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[] => 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 = (() => { @@ -1076,6 +1081,7 @@ const ExcretaTreatmentFacilityInp = () => { excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} cellStyleCallback={verificationCellStyle} + footerVerificationResult={footerVerificationResult} cellTooltipCallback={verificationCellTooltip} />
diff --git a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx index 517559765..838329e00 100644 --- a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(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 diff --git a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx index 8e2f62bff..ca70f9ac4 100644 --- a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx @@ -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 & { @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); /** 탭키별 `engColNm -> columnGuide` 캐시 */ const [columnGuidesByTab, setColumnGuidesByTab] = useState>>({}); const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState>(() => 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 = () => { {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 ( { ); } - 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 ( { 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; diff --git a/apps/st/src/components/statistics/input/PumpingStationInp.tsx b/apps/st/src/components/statistics/input/PumpingStationInp.tsx index fa06062ab..1f1d21cbb 100644 --- a/apps/st/src/components/statistics/input/PumpingStationInp.tsx +++ b/apps/st/src/components/statistics/input/PumpingStationInp.tsx @@ -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(null); const [statsCd, setStatsCd] = useState(""); const [isVerificationFailed, setIsVerificationFailed] = useState(false); + const [footerVerificationResult, setFooterVerificationResult] = + useState(null); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [isLoading, setIsLoading] = useState(false); const [sggOptions, setSggOptions] = useState([]); @@ -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[] => 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} /> diff --git a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx index ab2068891..cc04b5458 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx @@ -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"; @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); const [isNotApplicableByTab, setIsNotApplicableByTab] = useState>( () => 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 @@ -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 ( @@ -2055,6 +2076,9 @@ const SewageExcretaRelatedInp = () => { } getGridRef={activeTab === tabKey ? getGridRef : undefined} cellStyleCallback={resolveGridCellStyleCallback(tabKey)} + footerVerificationResult={ + footerVerificationByTab.get(tabKey) ?? null + } cellTooltipCallback={verificationCellTooltip} /> diff --git a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx index 2a693744b..e3aaf9abc 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx @@ -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"; @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(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 @@ -2154,6 +2175,9 @@ const SewageExcretaSlurgeInp = () => { activeTab === tabKey ? getGridRef : undefined } cellStyleCallback={resolveGridCellStyleCallback(tabKey)} + footerVerificationResult={ + footerVerificationByTab.get(tabKey) ?? null + } cellTooltipCallback={verificationCellTooltip} onCellEdited={ tabKey === DigestorOperatingStatusInpColumn.tabKey diff --git a/apps/st/src/components/statistics/input/SewagePipeInp.tsx b/apps/st/src/components/statistics/input/SewagePipeInp.tsx index 4fa111494..ff79f75e2 100644 --- a/apps/st/src/components/statistics/input/SewagePipeInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeInp.tsx @@ -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"; @@ -435,7 +437,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 +467,9 @@ const SewagePipeInp = () => { const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< Map >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [periodCheckList, setPeriodCheckList] = useState([]); /** 탭키별 `engColNm -> columnGuide` 캐시 */ @@ -566,6 +571,7 @@ const SewagePipeInp = () => { setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); setIsVerificationFailedByTab(new Map()); + setFooterVerificationByTab(new Map()); setLastSearchedYear(null); setLastSearchedValues(null); setHasSearched(false); @@ -594,7 +600,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 +709,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 +756,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 +785,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); @@ -800,6 +815,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 +844,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 +880,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 +936,7 @@ const SewagePipeInp = () => { setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); setIsVerificationFailedByTab(new Map()); + setFooterVerificationByTab(new Map()); setStatsCd(""); setToolbarSnapshotByTab(new Map()); } @@ -2193,7 +2218,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 +2251,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; diff --git a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx index d2810d427..31dc7bf66 100644 --- a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(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; @@ -1192,6 +1207,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 diff --git a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx index df0a9393c..ccf89d9cf 100644 --- a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx +++ b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(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} /> diff --git a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx index 9836a25e7..33c5f0ee9 100644 --- a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(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 @@ -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 diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 778ec8e31..cd24f5a32 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -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 & { verificationResult?: SewerageSupplyRateVerificationResult; }; +/** commonInfo 푸터 필드명 → 그리드 fieldName */ +const FOOTER_VERIFY_FIELD_ALIASES: Record = { + 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>({}); const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null); + const [footerVerificationResult, setFooterVerificationResult] = + useState(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 = () => { } 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; diff --git a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx index 3f35e5bbf..b3eeb1a78 100644 --- a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(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 @@ -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 diff --git a/apps/st/src/components/statistics/input/WaterReuseInp.tsx b/apps/st/src/components/statistics/input/WaterReuseInp.tsx index d2c6dd403..6d1c5c44c 100644 --- a/apps/st/src/components/statistics/input/WaterReuseInp.tsx +++ b/apps/st/src/components/statistics/input/WaterReuseInp.tsx @@ -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"; @@ -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 >(new Map()); + const [footerVerificationByTab, setFooterVerificationByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(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 @@ -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 diff --git a/apps/st/src/components/statistics/review/ReviewSubmitDialog.tsx b/apps/st/src/components/statistics/review/ReviewSubmitDialog.tsx index 84f6aad10..840f83241 100644 --- a/apps/st/src/components/statistics/review/ReviewSubmitDialog.tsx +++ b/apps/st/src/components/statistics/review/ReviewSubmitDialog.tsx @@ -29,6 +29,11 @@ export interface ReviewSubmitDialogProps { onOpenChange: (open: boolean) => void; context: ReviewerInfoRequest | null; onSubmitted?: () => void | Promise; + /** + * 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({ -
- -