From 85ef1805e57335f417b5ded1e29ef5c87bda3fd6 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Fri, 7 Aug 2026 16:24:39 +0900 Subject: [PATCH] =?UTF-8?q?[ST]=20-=20=EA=B3=B5=EB=8B=A8=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EB=B0=8F=20=EA=B0=9C=EC=84=A0=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/DataGrid.tsx | 260 ++++++++++++++--- .../ExceptionProcessingHistoryDialog.tsx | 8 +- .../module/SidoSggFacilityNameSearchForm.tsx | 266 ++++++++++++------ .../module/VerificationResultDialog.tsx | 4 +- .../VerificationResultHistoryDialog.tsx | 4 +- .../VerificationSelectionReport.tsx | 4 +- .../input/ExcretaTreatmentFacilityInp.tsx | 10 +- .../input/PrivateSewageFacilityInp.tsx | 10 +- .../input/PublicSewageFacilityInp.tsx | 8 +- .../statistics/input/PumpingStationInp.tsx | 10 +- .../input/SewageExcretaRelatedInp.tsx | 10 +- .../input/SewageExcretaSlurgeInp.tsx | 12 +- .../statistics/input/SewagePipeInp.tsx | 8 +- .../input/SewagePipeMaintainInp.tsx | 8 +- .../input/SewageReservoirPumpInp.tsx | 10 +- .../statistics/input/SewerageFinanceInp.tsx | 10 +- .../input/SeweragePenetrationRateInp.tsx | 8 +- .../input/SewerageServiceLevelInp.tsx | 10 +- .../statistics/input/WaterReuseInp.tsx | 10 +- .../management/StatisticsInputManagement.tsx | 10 +- .../GeneralVerificationManagement.tsx | 26 +- apps/st/src/components/type/DataGrid.type.ts | 4 +- .../PublicSewageFactColumn.ts | 6 +- .../sewagePipe/RegionalFactStatusInpColumn.ts | 4 +- 24 files changed, 513 insertions(+), 207 deletions(-) diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index c910b104e..aba00d672 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -1,4 +1,5 @@ -import { useEffect, useLayoutEffect, useRef, type MutableRefObject } from "react"; +import { useEffect, useLayoutEffect, useRef, useState, type MutableRefObject } from "react"; +import { createPortal } from "react-dom"; import JSZip from "jszip"; import { ValueType } from "realgrid"; @@ -385,6 +386,20 @@ function toNum(v: unknown): number { return parseFloat(String(v).replace(/,/g, "")) || NaN; } +/** RealGrid 기본 hover가 안 되는 빈 셀 여부 */ +function isEmptyGridCellValue(value: unknown): boolean { + if (value == null) return true; + if (typeof value === "number") return Number.isNaN(value); + return String(value).trim() === ""; +} + +type EmptyCellHoverTooltip = { + message: string; + x: number; + y: number; + key: string; +}; + /** onSorting: mergeCells: true인 컬럼이 있으면 그룹 내 순서 유지 정렬, 없으면 RealGrid 기본 정렬 */ function createOnSortingHandler( columns: { @@ -761,6 +776,7 @@ const DataGrid = ({ }: DataGridProps) => { const { columns, columnLayout } = columnInfo; const gridRef = useRef(null); + const gridHostRef = useRef(null); const fileInputRef = useRef(null); const columnsRef = useRef(columns); const sortDirRef = useRef>({}); @@ -784,6 +800,10 @@ const DataGrid = ({ useRef(onItemAllChecked); const isSyncingChecksFromRowDataRef = useRef(false); const columnsFieldsSigRef = useRef(""); + const emptyCellHoverKeyRef = useRef(""); + const emptyCellHoverRafRef = useRef(null); + const [emptyCellHoverTooltip, setEmptyCellHoverTooltip] = + useState(null); columnsRef.current = columns; rowDataRef.current = rowData; cellStyleCallbackRef.current = cellStyleCallback; @@ -1758,34 +1778,43 @@ useEffect(() => { const fieldName = cellIndex?.fieldName ?? cellIndex?.column ?? ""; // cellTooltipCallback 있으면 해당 셀 메시지로 showMessage (공식 문서: TooltipOptions + CellIndex) + // 빈 셀은 hover 오버레이로만 표시하므로 클릭 showMessage는 생략 (이중 표출 방지) if (cellTooltipCallbackRef.current && row && cellIndex) { - const msg = cellTooltipCallbackRef.current({ - row: row as Record, - fieldName, - }); - if (msg) { - const colName = - columns.find( - (c) => - c.fieldName === fieldName || - c.name === cellIndex.column, - )?.name ?? - cellIndex.column ?? - fieldName; - ( - gridView as { - showMessage?: ( - opts: { - message: string; - hideDuration?: number; - }, - idx: { itemIndex: number; column: string }, - ) => void; - } - ).showMessage?.( - { message: msg, hideDuration: 5000 }, - { itemIndex: cellIndex.itemIndex, column: colName }, - ); + if (!isEmptyGridCellValue(row[fieldName])) { + const msg = cellTooltipCallbackRef.current({ + row: row as Record, + fieldName, + }); + if (msg) { + const colName = + columns.find( + (c) => + c.fieldName === fieldName || + c.name === cellIndex.column, + )?.name ?? + cellIndex.column ?? + fieldName; + ( + gridView as { + showMessage?: ( + opts: { + message: string; + hideDuration?: number; + }, + idx: { + itemIndex: number; + column: string; + }, + ) => void; + } + ).showMessage?.( + { message: msg, hideDuration: 5000 }, + { + itemIndex: cellIndex.itemIndex, + column: colName, + }, + ); + } } } if (onDataCellClickRef.current && row !== undefined) { @@ -1807,6 +1836,144 @@ useEffect(() => { }; }, [onRowClick, onDataCellClick, cellTooltipCallback, columns]); + // 빈 셀은 RealGrid hover 툴팁이 안 뜸 → React 오버레이로만 보완 (값 있는 셀은 기존 onShowTooltip) + useEffect(() => { + if (!cellTooltipCallback) { + emptyCellHoverKeyRef.current = ""; + setEmptyCellHoverTooltip(null); + return; + } + const host = gridHostRef.current; + if (!host) return; + + const clearEmptyCellHoverTooltip = () => { + if (emptyCellHoverKeyRef.current === "") return; + emptyCellHoverKeyRef.current = ""; + setEmptyCellHoverTooltip(null); + }; + + const handleMouseMove = (ev: MouseEvent) => { + if (emptyCellHoverRafRef.current != null) { + cancelAnimationFrame(emptyCellHoverRafRef.current); + } + emptyCellHoverRafRef.current = requestAnimationFrame(() => { + const gridView = gridRef.current?.gridView as + | { + mouseToIndex?: ( + x: number, + y: number, + ) => { + cellType?: string; + itemIndex?: number; + column?: string; + fieldName?: string; + } | null; + } + | undefined; + if (!gridView?.mouseToIndex) { + clearEmptyCellHoverTooltip(); + return; + } + + const rect = host.getBoundingClientRect(); + let index: { + cellType?: string; + itemIndex?: number; + column?: string; + fieldName?: string; + } | null = null; + try { + index = gridView.mouseToIndex( + ev.clientX - rect.left, + ev.clientY - rect.top, + ); + } catch { + clearEmptyCellHoverTooltip(); + return; + } + + if ( + !index || + (index.cellType != null && index.cellType !== "data") || + index.itemIndex == null || + index.itemIndex < 0 + ) { + clearEmptyCellHoverTooltip(); + return; + } + + const row = rowDataRef.current[index.itemIndex]; + if (!row) { + clearEmptyCellHoverTooltip(); + 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; + } + + // 값이 있으면 RealGrid 기본 hover에 맡김 + if (!isEmptyGridCellValue(row[fieldName])) { + clearEmptyCellHoverTooltip(); + return; + } + + const msg = cellTooltipCallbackRef.current?.({ + row: row as Record, + fieldName, + }); + if (!msg) { + clearEmptyCellHoverTooltip(); + return; + } + + // 값 있는 셀 RealGrid 툴팁과 같이: 본문 다음 줄에 (검증 기준값...)이 오도록 + // 템플릿 리터럴 들여쓰기 공백은 제거 + const normalizedMessage = msg.replace(/\n[ \t]+/g, "\n"); + + const key = `${index.itemIndex}:${fieldName}`; + if (emptyCellHoverKeyRef.current === key) return; + emptyCellHoverKeyRef.current = key; + setEmptyCellHoverTooltip({ + message: normalizedMessage, + x: ev.clientX + 12, + y: ev.clientY + 16, + key, + }); + }); + }; + + const handleMouseLeave = () => { + if (emptyCellHoverRafRef.current != null) { + cancelAnimationFrame(emptyCellHoverRafRef.current); + emptyCellHoverRafRef.current = null; + } + clearEmptyCellHoverTooltip(); + }; + + host.addEventListener("mousemove", handleMouseMove); + host.addEventListener("mouseleave", handleMouseLeave); + return () => { + host.removeEventListener("mousemove", handleMouseMove); + host.removeEventListener("mouseleave", handleMouseLeave); + if (emptyCellHoverRafRef.current != null) { + cancelAnimationFrame(emptyCellHoverRafRef.current); + emptyCellHoverRafRef.current = null; + } + clearEmptyCellHoverTooltip(); + }; + }, [cellTooltipCallback]); + useEffect(() => { const gridView = gridRef.current?.gridView; if (!gridView || !onScrollToBottom) return; @@ -1996,7 +2163,7 @@ useEffect(() => { )} -
+
{ > {columns.map((column) => { const baseStyleName = column.styleName; + const numberFormat = + column.dataFormat && column.dataFormat.trim() !== "" + ? column.dataFormat + : "#,##0.000"; + const resolvedEditor = + column.editor ?? + (column.valueType === ValueType.NUMBER + ? { + type: "number" as const, + editFormat: numberFormat, + } + : undefined); return ( { : ValueType.TEXT } {...(column.valueType === ValueType.NUMBER && { - numberFormat: column.dataFormat - ? column.dataFormat - : "#,##0.000", + numberFormat, })} header={realGridHeaderWithTooltip({ ...column.header, tooltip: resolveColumnTooltip(column), })} editable={column.editable} - {...(column.editor != null && { - editor: column.editor, + {...(resolvedEditor != null && { + editor: resolvedEditor, })} {...(column.values != null && { values: column.values, @@ -2287,6 +2464,21 @@ useEffect(() => { ); })} + {emptyCellHoverTooltip && + createPortal( +
+ {emptyCellHoverTooltip.message} +
, + document.body, + )}
); diff --git a/apps/st/src/components/module/ExceptionProcessingHistoryDialog.tsx b/apps/st/src/components/module/ExceptionProcessingHistoryDialog.tsx index 4d94b5aac..16035aec2 100644 --- a/apps/st/src/components/module/ExceptionProcessingHistoryDialog.tsx +++ b/apps/st/src/components/module/ExceptionProcessingHistoryDialog.tsx @@ -56,8 +56,8 @@ type ExceptionHistoryTabKey = "NV" | "IMR" | "HB"; const exceptionHistoryTabs: { value: ExceptionHistoryTabKey; label: string }[] = [ { value: "NV", label: "일반검증 내역" }, - { value: "IMR", label: "I-MR 검증내역" }, - { value: "HB", label: "HB 검증내역" }, + { value: "IMR", label: "시계열 검증내역" }, + { value: "HB", label: "모집단 검증내역" }, ]; const TITLE = "예외처리 내역 조회"; @@ -274,7 +274,7 @@ export function ExceptionProcessingHistoryDialog({ setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1); setImrPageIndex(pageIndex); } catch { - setErrorMessage("I-MR 검증내역 조회 중 오류가 발생했습니다."); + setErrorMessage("시계열 검증내역 조회 중 오류가 발생했습니다."); } finally { setImrLoading(false); } @@ -293,7 +293,7 @@ export function ExceptionProcessingHistoryDialog({ setHbPageCount(response?.data?.pageInfo?.totalPages ?? 1); setHbPageIndex(pageIndex); } catch { - setErrorMessage("HB 검증내역 조회 중 오류가 발생했습니다."); + setErrorMessage("모집단 검증내역 조회 중 오류가 발생했습니다."); } finally { setHbLoading(false); } diff --git a/apps/st/src/components/module/SidoSggFacilityNameSearchForm.tsx b/apps/st/src/components/module/SidoSggFacilityNameSearchForm.tsx index 338faa475..afe29293f 100644 --- a/apps/st/src/components/module/SidoSggFacilityNameSearchForm.tsx +++ b/apps/st/src/components/module/SidoSggFacilityNameSearchForm.tsx @@ -16,7 +16,6 @@ import { LoadingWithAnimation, } from "@common/components"; import CompactDataGrid from "./CompactDataGrid"; -import SaveButton from "../button/SaveButton"; import { useAlertStore } from "@/shared/lib"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; @@ -182,6 +181,7 @@ const SidoSggFacilityNameSearchForm = ({ Record | null >(null); const didAutoFetchRef = useRef(false); + const isSavingConnectionRef = useRef(false); const sggKey: [string, string] | null = sido && sido !== "ALL" ? ["sgg-all", sido] : null; @@ -329,119 +329,213 @@ const SidoSggFacilityNameSearchForm = ({ } }; - const handleSaveAdminRegions = async () => { + const canEditFactConnection = async (): Promise => { const statusCodeResponse = await statisticsApi.CheckStatusCode({ sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "", - statsCd: 'ST00020002', - statsYr: statsYr?.trim() ?? '' + statsCd: "ST00020002", + statsYr: statsYr?.trim() ?? "", }); - if (statusCodeResponse.status === 'success') { - if (!['0', '1', '4'].includes(statusCodeResponse.data)) { - const codeName = statusCodeResponse.data === '2' ? '제출' : (statusCodeResponse.data === '3' ? '승인' : '검증중'); - alert(`시설별 설치 현황 데이터가 ${codeName} 상태일 때는 처리 시설 정보를 변경할 수 없습니다.`); - return; - } - } else { - alert('오류가 발생했습니다.\n관리자에게 문의하세요') + if (statusCodeResponse.status === "success") { + if (!["0", "1", "4"].includes(statusCodeResponse.data)) { + const codeName = + statusCodeResponse.data === "2" + ? "제출" + : statusCodeResponse.data === "3" + ? "승인" + : "검증중"; + alert( + `시설별 설치 현황 데이터가 ${codeName} 상태일 때는 처리 시설 정보를 변경할 수 없습니다.`, + ); + return false; + } + return true; } + alert("오류가 발생했습니다.\n관리자에게 문의하세요"); + return false; + }; + + const persistFactConnection = async ( + saveList: SeweragePipeFactConnectionSaveItem[], + deleteList: string[], + ): Promise => { if (!statsYr?.trim()) { alert("상단에서 통계연도로 조회한 뒤 이용해 주세요."); - return; + return false; } if (!selectedFacilityRow) { alert("처리시설을 먼저 선택해주세요."); - return; + return false; + } + if (saveList.length === 0 && deleteList.length === 0) { + return true; + } + if (!(await canEditFactConnection())) { + return false; } - const hasChanged = adminRegionRows.some( - (row) => - (row.__checked === "1" && row.isConnection !== "1") || - (row.__checked !== "1" && row.isConnection === "1"), - ); + try { + setIsLoading(true); + const userInfo = await getUserInfo(); + const request: SeweragePipeFactConnectionSaveRequest = { + ctpvNm: String(selectedFacilityRow.ctpvNm ?? ""), + sggNm: String(selectedFacilityRow.sggNm ?? ""), + fcltSn: String(selectedFacilityRow.fcltSn ?? ""), + statsYr: statsYr.trim(), + swtrmtFcltCd: String(selectedFacilityRow.fcltCd ?? ""), + swtrmtFcltNm: String(selectedFacilityRow.fcltNm ?? ""), + prcsFcltCpct: Number(selectedFacilityRow.fcltCpct ?? 0), + sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "", + userId: userInfo.userUnqId, + saveList, + deleteList, + }; - const saveList: SeweragePipeFactConnectionSaveItem[] = adminRegionRows - .filter((row) => row.__checked === "1") - .map((row) => ({ - admdstCd: String(row.admdstCd ?? ""), - admdstNm: String(row.emdNm ?? ""), - })) - .filter((row) => row.admdstCd && row.admdstNm); - - const deleteList = adminRegionRows - .filter((row) => row.__checked !== "1" && row.isConnection === "1") - .map((row) => String(row.admdstCd ?? "")) - .filter(Boolean); - - if (!hasChanged) { - alert("변경된 연결 정보가 없습니다."); - return; - } - - const run = async () => { - try { - setIsLoading(true); - const userInfo = await getUserInfo(); - const request: SeweragePipeFactConnectionSaveRequest = { - ctpvNm: String(selectedFacilityRow.ctpvNm ?? ""), - sggNm: String(selectedFacilityRow.sggNm ?? ""), - fcltSn: String(selectedFacilityRow.fcltSn ?? ""), - statsYr: statsYr.trim(), - swtrmtFcltCd: String(selectedFacilityRow.fcltCd ?? ""), - swtrmtFcltNm: String(selectedFacilityRow.fcltNm ?? ""), - prcsFcltCpct: Number(selectedFacilityRow.fcltCpct ?? 0), - sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "", - userId: userInfo.userUnqId, - saveList, - deleteList, - }; - - const response = - await statisticsInputSewPipeApiService.saveFactConnection( - request, - ); - - if (response.status !== "success") { - alert(response.message ?? "연결관계 저장에 실패했습니다."); - return; - } - - setAdminRegionRows((prev) => - prev.map((row) => ({ - ...row, - isConnection: row.__checked === "1" ? "1" : "0", - })), + const response = + await statisticsInputSewPipeApiService.saveFactConnection( + request, ); - await onSaveSuccess?.(); - alert("저장되었습니다."); - } catch { - alert("연결관계 저장 중 오류가 발생했습니다."); - } finally { - setIsLoading(false); - } - }; - void run(); + if (response.status !== "success") { + alert(response.message ?? "연결관계 저장에 실패했습니다."); + return false; + } + + await onSaveSuccess?.(); + return true; + } catch { + alert("연결관계 저장 중 오류가 발생했습니다."); + return false; + } finally { + setIsLoading(false); + } }; - const handleAdminRegionItemChecked = ( + const syncConnectionFlags = ( + rows: Record[], + ): Record[] => + rows.map((row) => ({ + ...row, + isConnection: row.__checked === "1" ? "1" : "0", + })); + + const revertCheckedToConnection = ( + rows: Record[], + ): Record[] => + rows.map((row) => ({ + ...row, + __checked: row.isConnection === "1" ? "1" : "0", + })); + + const handleAdminRegionItemChecked = async ( itemIndex: number, checked: boolean, ) => { + if (isSavingConnectionRef.current) { + // RealGrid가 이미 토글했을 수 있어 __checked 기준으로 재동기화 + setAdminRegionRows((prev) => prev.map((row) => ({ ...row }))); + return; + } + + const target = adminRegionRows[itemIndex]; + if (!target) return; + + const nextChecked = checked ? "1" : "0"; + const prevChecked = target.isConnection === "1" ? "1" : "0"; + setAdminRegionRows((prev) => prev.map((row, idx) => idx === itemIndex - ? { ...row, __checked: checked ? "1" : "0" } + ? { ...row, __checked: nextChecked } : row, ), ); + + // 서버 상태와 동일하면 API 호출 없이 UI만 반영 + if (nextChecked === prevChecked) { + return; + } + + const saveList: SeweragePipeFactConnectionSaveItem[] = + checked + ? [ + { + admdstCd: String(target.admdstCd ?? ""), + admdstNm: String(target.emdNm ?? ""), + }, + ].filter((row) => row.admdstCd && row.admdstNm) + : []; + const deleteList = + !checked && String(target.admdstCd ?? "") + ? [String(target.admdstCd)] + : []; + + if (saveList.length === 0 && deleteList.length === 0) { + setAdminRegionRows((prev) => revertCheckedToConnection(prev)); + alert("행정구역 코드가 없어 저장할 수 없습니다."); + return; + } + + isSavingConnectionRef.current = true; + const ok = await persistFactConnection(saveList, deleteList); + isSavingConnectionRef.current = false; + + if (!ok) { + setAdminRegionRows((prev) => revertCheckedToConnection(prev)); + return; + } + + setAdminRegionRows((prev) => syncConnectionFlags(prev)); }; - const handleAdminRegionItemAllChecked = (checked: boolean) => { + const handleAdminRegionItemAllChecked = async (checked: boolean) => { + if (isSavingConnectionRef.current) { + setAdminRegionRows((prev) => prev.map((row) => ({ ...row }))); + return; + } + + const snapshot = adminRegionRows; + if (snapshot.length === 0) return; + setAdminRegionRows((prev) => - prev.map((row) => ({ ...row, __checked: checked ? "1" : "0" })), + prev.map((row) => ({ + ...row, + __checked: checked ? "1" : "0", + })), ); + + const saveList: SeweragePipeFactConnectionSaveItem[] = checked + ? snapshot + .filter((row) => row.isConnection !== "1") + .map((row) => ({ + admdstCd: String(row.admdstCd ?? ""), + admdstNm: String(row.emdNm ?? ""), + })) + .filter((row) => row.admdstCd && row.admdstNm) + : []; + const deleteList = !checked + ? snapshot + .filter((row) => row.isConnection === "1") + .map((row) => String(row.admdstCd ?? "")) + .filter(Boolean) + : []; + + if (saveList.length === 0 && deleteList.length === 0) { + setAdminRegionRows((prev) => syncConnectionFlags(prev)); + return; + } + + isSavingConnectionRef.current = true; + const ok = await persistFactConnection(saveList, deleteList); + isSavingConnectionRef.current = false; + + if (!ok) { + setAdminRegionRows((prev) => revertCheckedToConnection(prev)); + return; + } + + setAdminRegionRows((prev) => syncConnectionFlags(prev)); }; useEffect(() => { @@ -575,8 +669,10 @@ const SidoSggFacilityNameSearchForm = ({ 조회 -
- +
+ + * 체크 박스를 선택 / 해제 시 반영됩니다. +
{isLoading && ( diff --git a/apps/st/src/components/module/VerificationResultDialog.tsx b/apps/st/src/components/module/VerificationResultDialog.tsx index 7f7e08801..34f252a5d 100644 --- a/apps/st/src/components/module/VerificationResultDialog.tsx +++ b/apps/st/src/components/module/VerificationResultDialog.tsx @@ -41,8 +41,8 @@ const VERIFICATION_METHOD_CODES: VerificationMethodCd[] = ["NV", "IMR", "HB"]; const VRFC_MTHD_NAME_MAP: Record = { NV: "일반", - IMR: "I-MR", - HB: "HB", + IMR: "시계열", + HB: "모집단", }; export type VerificationExceptionSubmitItem = { diff --git a/apps/st/src/components/module/VerificationResultHistoryDialog.tsx b/apps/st/src/components/module/VerificationResultHistoryDialog.tsx index 863a82226..5ede47707 100644 --- a/apps/st/src/components/module/VerificationResultHistoryDialog.tsx +++ b/apps/st/src/components/module/VerificationResultHistoryDialog.tsx @@ -40,8 +40,8 @@ type VerificationHistoryTabKey = "NV" | "IMR" | "HB"; const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string }[] = [ { value: "NV", label: "일반검증 내역" }, - { value: "IMR", label: "I-MR 검증내역" }, - { value: "HB", label: "HB 검증내역" }, + { value: "IMR", label: "시계열 검증내역" }, + { value: "HB", label: "모집단 검증내역" }, ]; const PAGE_SIZE = 30; diff --git a/apps/st/src/components/statistics/VerificationSelectionReport.tsx b/apps/st/src/components/statistics/VerificationSelectionReport.tsx index 82b80d295..68b0f6a5d 100644 --- a/apps/st/src/components/statistics/VerificationSelectionReport.tsx +++ b/apps/st/src/components/statistics/VerificationSelectionReport.tsx @@ -40,8 +40,8 @@ type VerificationReportTabKey = "NV" | "IMR" | "HB"; const VERIFICATION_REPORT_TABS: { value: VerificationReportTabKey; label: string }[] = [ { value: "NV", label: "일반" }, - { value: "IMR", label: "I-MR" }, - { value: "HB", label: "HB" }, + { value: "IMR", label: "시계열" }, + { value: "HB", label: "모집단" }, ]; const TYPE_CODE_COLOR_MAP: Record = { diff --git a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx index 8aa9b1231..24207a518 100644 --- a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx @@ -290,11 +290,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } function createEmptyExcretaTreatmentFacilityRow( @@ -786,7 +786,7 @@ const ExcretaTreatmentFacilityInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -941,10 +941,10 @@ const ExcretaTreatmentFacilityInp = () => { .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 -"; + : "모집단 검증 -"; return `${prefix}${e.rsnExpln} (검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) diff --git a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx index 9b5144819..1a4b2ede7 100644 --- a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx @@ -305,11 +305,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } function resolvePrivateSewageFacilityTab(tabKey: string) { @@ -871,7 +871,7 @@ const PrivateSewageFacilityInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -1146,10 +1146,10 @@ const PrivateSewageFacilityInp = () => { .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 - "; + : "모집단 검증 - "; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) .filter(Boolean) diff --git a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx index 33461faf7..9d5c74be1 100644 --- a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx @@ -122,11 +122,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === 'COMPLETED' && Number(res.job_prog) === 100) return; if (res.status === 'FAILED' || res.status === 'ERROR' || res.status === 'CANCELLED') { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error('IMR 검증 완료 대기 시간이 초과되었습니다.'); + throw new Error('시계열 검증 완료 대기 시간이 초과되었습니다.'); } type MainFacilityMappingSaveDraft = { @@ -385,7 +385,7 @@ const PublicSewageFacilityInp = () => { if (finalStatus === '5') { setGlobalProgress(50); - setGlobalProgressLabel('IMR 검증 진행 중...'); + setGlobalProgressLabel('시계열 검증 진행 중...'); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? '', @@ -1416,7 +1416,7 @@ const PublicSewageFacilityInp = () => { return errors .map((e) => { const prefix = - e.vrfcCd === "IMR" ? "IMR검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - "); + e.vrfcCd === "IMR" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - "); const comp = e.compVlCn ?? ""; const now = e.nowVlCn ?? ""; return `${prefix}${e.rsnExpln} diff --git a/apps/st/src/components/statistics/input/PumpingStationInp.tsx b/apps/st/src/components/statistics/input/PumpingStationInp.tsx index 0ba599429..d6e98c42e 100644 --- a/apps/st/src/components/statistics/input/PumpingStationInp.tsx +++ b/apps/st/src/components/statistics/input/PumpingStationInp.tsx @@ -285,11 +285,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } function createEmptyPumpingStationRow( @@ -770,7 +770,7 @@ const PumpingStationInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -925,10 +925,10 @@ const PumpingStationInp = () => { .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 -"; + : "모집단 검증 -"; return `${prefix}${e.rsnExpln} (검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) diff --git a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx index 6fc83def9..364466f1d 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx @@ -521,10 +521,10 @@ function verificationCellTooltip({ .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 - "; + : "모집단 검증 - "; return `${prefix}${e.rsnExpln} (검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) @@ -577,11 +577,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } // 하수도 통계 관리 > 12. 하수 및 분뇨관련산업 @@ -1431,7 +1431,7 @@ const SewageExcretaRelatedInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", diff --git a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx index 4ddc75984..c3286e870 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx @@ -136,10 +136,10 @@ function verificationCellTooltip({ .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 - "; + : "모집단 검증 - "; return `${prefix}${e.rsnExpln} (검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) @@ -630,11 +630,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } // 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리 @@ -1437,7 +1437,7 @@ const SewageExcretaSlurgeInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -1898,7 +1898,7 @@ const SewageExcretaSlurgeInp = () => { col.fieldName === "sggNm" ) { next = buildSggNmColumn(col, canEdit, sggOptions); - } else if (col.fieldName === "sggNm" || col.fieldName === "swtrmtFcltNm") { + } else if (col.fieldName === "sggNm") { next = { ...col, editable: false as const, styleName: "rg-cell-readonly" }; } const guide = columnGuidesByTab[tabKey]?.[col.fieldName]?.trim(); diff --git a/apps/st/src/components/statistics/input/SewagePipeInp.tsx b/apps/st/src/components/statistics/input/SewagePipeInp.tsx index 57d3fc8e1..fabf5f726 100644 --- a/apps/st/src/components/statistics/input/SewagePipeInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeInp.tsx @@ -324,11 +324,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } function buildRegionalFactSubmitListFromGrid( @@ -1428,7 +1428,7 @@ const SewagePipeInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: values.bsnmCd ?? "", @@ -2244,7 +2244,7 @@ const SewagePipeInp = () => { return errors .map((e) => { const prefix = - e.vrfcCd === "IMR" ? "IMR검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - "); + e.vrfcCd === "IMR" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - "); const comp = e.compVlCn ?? ""; const now = e.nowVlCn ?? ""; return `${prefix}${e.rsnExpln} diff --git a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx index 3c2e86d24..18f08a272 100644 --- a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx @@ -183,11 +183,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } function buildColumnFormatNumber( @@ -655,7 +655,7 @@ const SewagePipeMaintainInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -1099,7 +1099,7 @@ const SewagePipeMaintainInp = () => { return errors .map((e) => { const prefix = - e.vrfcCd === "IMR" ? "IMR검증 - " : e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - "; + e.vrfcCd === "IMR" ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - "; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) .filter(Boolean) diff --git a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx index 80a9eb537..7104fce2e 100644 --- a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx +++ b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx @@ -446,11 +446,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } // 하수도 통계 관리 > 04. 하수저류 시설 및 빗물 펌프장 @@ -852,7 +852,7 @@ const SewageReservoirPumpInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -1500,10 +1500,10 @@ const SewageReservoirPumpInp = () => { .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 -"; + : "모집단 검증 -"; return `${prefix}${e.rsnExpln} (검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) diff --git a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx index 6c1d8fff7..6710e1b74 100644 --- a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx @@ -341,11 +341,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } // 하수도 통계 관리 > 09. 하수도재정 @@ -918,7 +918,7 @@ const SewerageFinanceInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -1212,10 +1212,10 @@ const SewerageFinanceInp = () => { .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 - "; + : "모집단 검증 - "; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) .filter(Boolean) diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 51ed15270..034bd94f2 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -87,11 +87,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } function getSewerageSupplyRateParams( @@ -507,7 +507,7 @@ const SeweragePenetrationRateInp = () => { setGlobalProgress(50); if (response.data.finalStatus === '5') { - setGlobalProgressLabel('IMR 검증 진행 중...'); + setGlobalProgressLabel('시계열 검증 진행 중...'); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: searchValues?.bsnmCd ?? '', @@ -833,7 +833,7 @@ const SeweragePenetrationRateInp = () => { if (Array.isArray(errors) && errors.length > 0) { return errors .map((e) => { - const prefix = e.vrfcCd === 'IMR' ? 'IMR검증 - ' : (e.vrfcCd === 'NV' ? '일반검증 - ' : 'HB검증 -'); + const prefix = e.vrfcCd === 'IMR' ? '시계열 검증 - ' : (e.vrfcCd === 'NV' ? '일반검증 - ' : '모집단 검증 -'); return `${prefix}${e.rsnExpln} (검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) diff --git a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx index 55e3aac24..dd587a7dc 100644 --- a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx @@ -271,11 +271,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } // 하수도 통계 관리 > 13. 하수도 서비스 수준 @@ -791,7 +791,7 @@ const SewerageServiceLevelInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -1110,10 +1110,10 @@ const SewerageServiceLevelInp = () => { .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 - "; + : "모집단 검증 - "; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) .filter(Boolean) diff --git a/apps/st/src/components/statistics/input/WaterReuseInp.tsx b/apps/st/src/components/statistics/input/WaterReuseInp.tsx index d003934bc..c974819ce 100644 --- a/apps/st/src/components/statistics/input/WaterReuseInp.tsx +++ b/apps/st/src/components/statistics/input/WaterReuseInp.tsx @@ -508,11 +508,11 @@ async function waitForImrJobCompleted( options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0))); if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return; if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") { - throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`); + throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`); } await sleep(pollIntervalMs); } - throw new Error("IMR 검증 완료 대기 시간이 초과되었습니다."); + throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } // 하수도 통계 관리 > 10. 물 재이용 @@ -1318,7 +1318,7 @@ const WaterReuseInp = () => { if (finalStatus === "5") { setGlobalProgress(50); - setGlobalProgressLabel("IMR 검증 진행 중..."); + setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", @@ -2053,10 +2053,10 @@ const WaterReuseInp = () => { .map((e) => { const prefix = e.vrfcCd === "IMR" - ? "IMR검증 - " + ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " - : "HB검증 - "; + : "모집단 검증 - "; return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`; }) .filter(Boolean) diff --git a/apps/st/src/components/statistics/management/StatisticsInputManagement.tsx b/apps/st/src/components/statistics/management/StatisticsInputManagement.tsx index 39e6fdda7..7c6f9731a 100644 --- a/apps/st/src/components/statistics/management/StatisticsInputManagement.tsx +++ b/apps/st/src/components/statistics/management/StatisticsInputManagement.tsx @@ -1,4 +1,4 @@ -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useAlertStore } from "@/shared/lib"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; @@ -115,6 +115,14 @@ const StatisticsInputManagement = () => { setRowData([]); }; + useEffect(() => { + const defaultYear = String(new Date().getFullYear() - 1); + void handleSearch({ + year: defaultYear, + clsfCd: "ALL", + }); + }, []); + const handleRegisterClick = () => { const yr = searchFormValues?.year ?? lastSearchRef.current?.year ?? ""; diff --git a/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx b/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx index 474b2993b..7960d12b2 100644 --- a/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx +++ b/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx @@ -48,15 +48,21 @@ const GeneralVerificationManagement = () => { const { setMessage: alert } = useAlertStore((state) => state); - const handleSearch = async () => { + const handleSearch = async ( + values: { + statsCd: string; + useYn: string; + vrfcClsfCd: string; + } = searchValues, + ) => { try { setIsLoading(true); const params = Object.fromEntries( Object.entries({ - trgtStatsCd: searchValues.statsCd, - vrfcClsfCd: searchValues.vrfcClsfCd, - useYn: searchValues.useYn, + trgtStatsCd: values.statsCd, + vrfcClsfCd: values.vrfcClsfCd, + useYn: values.useYn, }).filter(([, value]) => value !== "" && value !== "ALL"), ); @@ -83,7 +89,7 @@ const GeneralVerificationManagement = () => { }; const columns = useMemo( - () => getGeneralVerificationColumns(handleSearch), + () => getGeneralVerificationColumns(() => handleSearch()), [searchValues.statsCd, searchValues.vrfcClsfCd, searchValues.useYn], ); @@ -103,14 +109,16 @@ const GeneralVerificationManagement = () => { }; const handleReset = () => { + const resetValues = { statsCd: "", useYn: "", vrfcClsfCd: "" }; + setSearchValues(resetValues); setRowSelection({}); setSelectedVrfcRow(null); - handleSearch(); setShowRegistForm(false); + void handleSearch(resetValues); }; useEffect(() => { - handleSearch(); + void handleSearch(); }, []); return ( @@ -182,7 +190,7 @@ const GeneralVerificationManagement = () => { @@ -205,7 +213,7 @@ const GeneralVerificationManagement = () => { void handleSearch()} > diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index 47d0179fc..700924382 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -233,13 +233,15 @@ export type ColumnConfig = { datetimeFormat?: string; /** RealGrid 셀 편집기 */ editor?: { - type?: "dropdown" | "list" | "date" | "text" | "line"; + type?: "dropdown" | "list" | "date" | "text" | "line" | "number"; domainOnly?: boolean; textReadOnly?: boolean; commitOnSelect?: boolean; dropDownWhenClick?: boolean; showToday?: boolean; datetimeFormat?: string; + /** NUMBER 편집 시 천단위 구분·소수점 형식 (표시용 numberFormat과 동일하게 맞추는 것을 권장) */ + editFormat?: string; values?: string[]; labels?: string[]; /** RealGrid 입력 마스크 (예: 위탁계약기간 `yyyymmdd~yyyymmdd`) */ diff --git a/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts b/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts index 82198b336..7dbda56e5 100644 --- a/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts +++ b/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts @@ -34,9 +34,9 @@ export const PublicSewageFactColumn = { }, // (m³/일) - { name: 'prcsFcltPhysCpct', fieldName: 'prcsFcltPhysCpct', header: { text: '물리적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'prcsFcltBoilCpct', fieldName: 'prcsFcltBoilCpct', header: { text: '생물학적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, - { name: 'prcsFcltAltdCpct', fieldName: 'prcsFcltAltdCpct', header: { text: '고도' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'prcsFcltPhysCpct', fieldName: 'prcsFcltPhysCpct', header: { text: '물리적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly'}, + { name: 'prcsFcltBoilCpct', fieldName: 'prcsFcltBoilCpct', header: { text: '생물학적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly'}, + { name: 'prcsFcltAltdCpct', fieldName: 'prcsFcltAltdCpct', header: { text: '고도' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', editable: false, styleName: 'rg-cell-readonly'}, // 유입하수량 { name: 'inflwSwageQnt', fieldName: 'inflwSwageQnt', header: { text: '유입하수량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, diff --git a/apps/st/src/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn.ts index eca03bda2..5880c09a7 100644 --- a/apps/st/src/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn.ts @@ -199,10 +199,10 @@ export const RegionalFactStatusInpColumn = { editable: false, styleName: 'rg-cell-total', valueType: ValueType.NUMBER, - dataFormat: '#,##0.0', + dataFormat: '#,##0.00', computed: { numeratorFields: ['sewerLdgrEdpPrlg'], - denominatorFields: ['cbsysPplMtncPlanPrlg', 'drtwPplMtncPlanPrlg', 'exlnPplMtncPlanPrlg'], + denominatorFields: ['cbsysSqrePrlg', 'cbsysCiclPrlg', 'cbsysOpnPrlg', 'cbsysGttrPrlg', 'drtwPplSqrePrlg', 'drtwPplCiclPrlg', 'exlnPplSqrePrlg', 'exlnPplCiclPrlg', 'exlnPplOpnPrlg', 'exlnPplGttrPrlg'], type: 'pblswrgPopltnSuprtRate' as const, }, },