From 6f300918cdb363cadd49c7dad08246b80f3da9df Mon Sep 17 00:00:00 2001 From: rsone001 Date: Wed, 21 Oct 2026 16:04:41 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[ST]=20-=20fix:=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=93=9C=20=EC=97=AC=EB=9F=AC=ED=96=89=20=EB=B3=B5=EC=82=AC=20?= =?UTF-8?q?=EB=B6=99=EC=97=AC=EB=84=A3=EA=B8=B0=20=EC=8B=9C=20=EC=B4=88?= =?UTF-8?q?=EA=B3=BC=EB=90=9C=20=ED=96=89=20=EC=88=98=20=EB=A7=8E=ED=81=BC?= =?UTF-8?q?=20=EB=B9=88=ED=96=89=EC=9D=B4=20=EC=B6=94=EA=B0=80=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=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/module/DataGrid.tsx | 25 +++++++++++++++++--- apps/st/src/components/type/DataGrid.type.ts | 5 ++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index a87005031..cef70a624 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -289,14 +289,28 @@ type GridViewWithSetColumnProperty = { }; type GridViewWithEditOptions = { - setEditOptions?: (options: { commitWhenLeave?: boolean }) => void; + setEditOptions?: (options: { + commitWhenLeave?: boolean; + appendable?: boolean; + insertable?: boolean; + }) => void; + setPasteOptions?: (options: { enableAppend?: boolean }) => void; }; /** 포커스가 그리드 밖으로 나갈 때 편집 중인 값을 자동 commit */ function applyDefaultGridEditOptions( gridView: GridViewWithEditOptions | null | undefined, + options?: { preventRowAppend?: boolean }, ) { - gridView?.setEditOptions?.({ commitWhenLeave: true }); + gridView?.setEditOptions?.({ + commitWhenLeave: true, + ...(options?.preventRowAppend + ? { appendable: false, insertable: false } + : {}), + }); + if (options?.preventRowAppend) { + gridView?.setPasteOptions?.({ enableAppend: false }); + } } function isDateEditorColumn(column: { @@ -853,6 +867,7 @@ const DataGrid = ({ gridHeight = 55, // vh 단위 headerHeight = 60, fitStyle = GridFitStyle.EVEN, + preventRowAppend = true, }: DataGridProps) => { const { columns, columnLayout } = columnInfo; const fixedColumnNameSet = useMemo( @@ -1467,7 +1482,9 @@ useEffect(() => { const scheduleFooterTask = (task: () => void, delayMs: number) => { footerTimerIds.push(setTimeout(task, delayMs)); }; - applyDefaultGridEditOptions(gridView as GridViewWithEditOptions); + applyDefaultGridEditOptions(gridView as GridViewWithEditOptions, { + preventRowAppend, + }); const columnsFieldsSig = columns.map((c) => c.fieldName).join("\u0001"); if (columnsFieldsSig !== columnsFieldsSigRef.current) { @@ -1990,6 +2007,7 @@ useEffect(() => { fitStyle, enableSort, footerVerificationResult, + preventRowAppend, ]); // 행·셀 클릭 이벤트 (검증 오류 툴팁은 hover만 — onShowTooltip / 빈 셀 오버레이) @@ -2527,6 +2545,7 @@ useEffect(() => { const gridView = gridRef.current?.gridView ?? grid; applyDefaultGridEditOptions( gridView as GridViewWithEditOptions, + { preventRowAppend }, ); if (!gridView?.columnByName) return; setTimeout(() => { diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts index 7d2a82625..0899bf560 100644 --- a/apps/st/src/components/type/DataGrid.type.ts +++ b/apps/st/src/components/type/DataGrid.type.ts @@ -481,6 +481,11 @@ export type DataGridProps = { headerHeight?: number; /** RealGrid `displayOptions.fitStyle` (기본 EVEN). 컬럼별 width 유지 시 NONE */ fitStyle?: GridFitStyle; + /** + * true면 붙여넣기·편집으로 행을 추가하지 않음. + * 검증 결과처럼 조회 행 수가 고정된 그리드에서 사용. + */ + preventRowAppend?: boolean; }; export type TabDataGridProps = { From 9e15b23eda6ae25ba7b11aade2418859d8134dc2 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Wed, 21 Oct 2026 17:17:54 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[ST]=20-=20fix:=20input=202-3=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5,=20=EC=A0=9C=EC=B6=9C=20request=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/statistics/input/SewagePipeInp.tsx | 2 -- .../model/input/statistics-sewage-pipe-input-interface.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/apps/st/src/components/statistics/input/SewagePipeInp.tsx b/apps/st/src/components/statistics/input/SewagePipeInp.tsx index 14dc5e8ce..04d1c5353 100644 --- a/apps/st/src/components/statistics/input/SewagePipeInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeInp.tsx @@ -1338,7 +1338,6 @@ const SewagePipeInp = () => { rmrk: nullableTrimmedString(d.rmrk), lyngPrlgWhol: formatNumber(d.lyngPrlg, "lyngPrlg"), dmltnPrlgWhol: formatNumber(d.dmltnPrlg, "dmltnPrlg"), - pipeByPrstWhol: formatNumber(d.pipeByPrstWhol, "pipeByPrstWhol"), }; }); const requestData: SeweragePipeInputPipeTypeStatusSubmitRequest = { @@ -1830,7 +1829,6 @@ const SewagePipeInp = () => { rmrk: nullableTrimmedString(d.rmrk), lyngPrlgWhol: formatNumber(d.lyngPrlg, "lyngPrlg"), dmltnPrlgWhol: formatNumber(d.dmltnPrlg, "dmltnPrlg"), - pipeByPrstWhol: formatNumber(d.pipeByPrstWhol, "pipeByPrstWhol"), }; }); diff --git a/apps/st/src/entities/statistics/model/input/statistics-sewage-pipe-input-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-sewage-pipe-input-interface.ts index d6ded5b56..400eac0cd 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-sewage-pipe-input-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-sewage-pipe-input-interface.ts @@ -308,7 +308,6 @@ export interface SeweragePipeInputPipeTypeStatusSaveItem { rmrk: string | null; lyngPrlgWhol: number; dmltnPrlgWhol: number; - pipeByPrstWhol: number; } /** 관종별 현황 저장 API 요청 */ From 3732c9380bfced14bb532f6bc53de8ab28d296d7 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Thu, 22 Oct 2026 11:44:35 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[ST]=20-=20doList=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=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/util/column/input/DoList.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/st/src/components/util/column/input/DoList.ts b/apps/st/src/components/util/column/input/DoList.ts index 1ca39b72c..6e97285f6 100644 --- a/apps/st/src/components/util/column/input/DoList.ts +++ b/apps/st/src/components/util/column/input/DoList.ts @@ -8,6 +8,7 @@ export const doList = [ "4600000000", "4700000000", "4800000000", + "5000000000", ]; export const needBsnmFrgnrPoplSmSidoList = [ From 9a24e4aa234c28663fee0dfe34d27834de582ea4 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Thu, 22 Oct 2026 16:48:34 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[ST]=20-=20=EC=9E=85=EB=A0=A5=20verificatio?= =?UTF-8?q?nResult=20-=20cmprCn,=20nowCn=20=EA=B0=92=20null=20=EC=9D=BC?= =?UTF-8?q?=EC=8B=9C=20=EC=B9=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../input/ExcretaTreatmentFacilityInp.tsx | 26 +++---- .../input/PrivateSewageFacilityInp.tsx | 23 +++--- .../input/PublicSewageFacilityInp.tsx | 30 ++++---- .../statistics/input/PumpingStationInp.tsx | 26 +++---- .../input/SewageExcretaRelatedInp.tsx | 22 ++---- .../input/SewageExcretaSlurgeInp.tsx | 22 ++---- .../statistics/input/SewagePipeInp.tsx | 76 ++++++++++--------- .../input/SewagePipeMaintainInp.tsx | 35 ++++----- .../input/SewageReservoirPumpInp.tsx | 24 ++---- .../statistics/input/SewerageFinanceInp.tsx | 23 +++--- .../input/SeweragePenetrationRateInp.tsx | 23 +++--- .../input/SewerageServiceLevelInp.tsx | 23 +++--- .../statistics/input/WaterReuseInp.tsx | 35 ++++----- .../util/input/verification-failed-flag.ts | 10 ++- .../util/input/verification-result-display.ts | 47 +++++++++++- 15 files changed, 224 insertions(+), 221 deletions(-) diff --git a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx index 35ea779f2..87df51eca 100644 --- a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx @@ -75,7 +75,11 @@ import { toGridRows } from "@/components/util/grid/gridFunction"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport"; import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -207,7 +211,7 @@ function mapExcretaTreatmentFacilityListToGridRows( }, ])[0] as GridRowWithVerification; if (item.verificationResult) { - row.verificationResult = item.verificationResult; + row.verificationResult = normalizeVerificationResultMap(item.verificationResult); } return row; }); @@ -502,7 +506,9 @@ const ExcretaTreatmentFacilityInp = () => { setStatusInfo(nextStatusInfo); setStatsCd(commonInfo.statsCd); setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo)); - setFooterVerificationResult(commonInfo.verificationResult ?? null); + setFooterVerificationResult( + normalizeVerificationResultMap(commonInfo.verificationResult) ?? null, + ); if (overrides?.mode !== "PRE") { setIsNotApplicable(gridRows.length === 0); } else if (gridRows.length > 0) { @@ -952,19 +958,7 @@ const ExcretaTreatmentFacilityInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 -"; - return `${prefix}${e.rsnExpln} -(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }; diff --git a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx index d9198d218..9b37b277c 100644 --- a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx @@ -68,7 +68,11 @@ import { patchFooterVerificationByTab, patchVerificationFailedByTab, } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -175,7 +179,9 @@ function mapRegionalListToGridRows< return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const { verificationResult, ...rest } = item; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; - if (verificationResult) row.verificationResult = verificationResult; + if (verificationResult) { + row.verificationResult = normalizeVerificationResultMap(verificationResult); + } return row; }); } @@ -1144,18 +1150,7 @@ const PrivateSewageFacilityInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 - "; - return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }; diff --git a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx index bcc35197e..48b9ca3b0 100644 --- a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx @@ -52,7 +52,11 @@ import { fetchColumnGuidesRecord } from '@/components/util/grid/columnGuides'; import { toGridRows } from '@/components/util/grid/gridFunction'; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport"; import { patchFooterVerificationByTab,patchVerificationFailedByTab } from '@/components/util/input/verification-failed-flag'; -import type { VerificationResultLikeMap } from '@/components/util/input/verification-result-display'; +import { + formatVerificationTooltip, + withNormalizedVerificationResult, + type VerificationResultLikeMap, +} from '@/components/util/input/verification-result-display'; import { nullableTrimmedString } from '@/components/util/input/nullableString'; import { useAdminStore } from '@/store/admin-store'; import { useTabStore } from '@/store/tab-store'; @@ -789,11 +793,13 @@ const PublicSewageFacilityInp = () => { sidoListRef.current, searchContext, ); - const convertedData = list.map((item) => ({ - ctpvNm: commonInfo.ctpvNm, - ...item, - rmrk: item.rmrk ?? null, - })); + const convertedData = list.map((item) => + withNormalizedVerificationResult({ + ctpvNm: commonInfo.ctpvNm, + ...item, + rmrk: item.rmrk ?? null, + }), + ); if (list.length === 0) { setShowIsNotApplicable(true); @@ -1575,17 +1581,7 @@ const PublicSewageFacilityInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - "); - const comp = e.cmprCn ?? ""; - const now = e.nowCn ?? ""; - return `${prefix}${e.rsnExpln} - (검증 기준값: ${comp} / 현재 입력값: ${now})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }} /> diff --git a/apps/st/src/components/statistics/input/PumpingStationInp.tsx b/apps/st/src/components/statistics/input/PumpingStationInp.tsx index 0f00b1c01..752528b27 100644 --- a/apps/st/src/components/statistics/input/PumpingStationInp.tsx +++ b/apps/st/src/components/statistics/input/PumpingStationInp.tsx @@ -75,7 +75,11 @@ import { toGridRows } from "@/components/util/grid/gridFunction"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport"; import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -206,7 +210,7 @@ function mapPumpingStationListToGridRows( }, ])[0] as GridRowWithVerification; if (item.verificationResult) { - row.verificationResult = item.verificationResult; + row.verificationResult = normalizeVerificationResultMap(item.verificationResult); } return row; }); @@ -493,7 +497,9 @@ const PumpingStationInp = () => { setStatusInfo(nextStatusInfo); setStatsCd(commonInfo.statsCd); setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo)); - setFooterVerificationResult(commonInfo.verificationResult ?? null); + setFooterVerificationResult( + normalizeVerificationResultMap(commonInfo.verificationResult) ?? null, + ); if (overrides?.mode !== "PRE") { setIsNotApplicable(gridRows.length === 0); } else if (gridRows.length > 0) { @@ -935,19 +941,7 @@ const PumpingStationInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 -"; - return `${prefix}${e.rsnExpln} -(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }; diff --git a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx index 18b9e78b4..8ab561ba6 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx @@ -84,7 +84,11 @@ import { patchFooterVerificationByTab, patchVerificationFailedByTab, } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -552,19 +556,7 @@ function verificationCellTooltip({ if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 - "; - return `${prefix}${e.rsnExpln} -(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } } @@ -588,7 +580,7 @@ function mapListToGridRows { const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification; if (item.verificationResult) { - row.verificationResult = item.verificationResult; + row.verificationResult = normalizeVerificationResultMap(item.verificationResult); } return row; }); diff --git a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx index 32c414156..c40e5018a 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx @@ -90,7 +90,11 @@ import { patchFooterVerificationByTab, patchVerificationFailedByTab, } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -148,19 +152,7 @@ function verificationCellTooltip({ if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 - "; - return `${prefix}${e.rsnExpln} -(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } } @@ -185,7 +177,7 @@ function mapListToGridRows { const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification; if (item.verificationResult) { - row.verificationResult = item.verificationResult; + row.verificationResult = normalizeVerificationResultMap(item.verificationResult); } return row; }); diff --git a/apps/st/src/components/statistics/input/SewagePipeInp.tsx b/apps/st/src/components/statistics/input/SewagePipeInp.tsx index 04d1c5353..a05e9f982 100644 --- a/apps/st/src/components/statistics/input/SewagePipeInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeInp.tsx @@ -82,7 +82,10 @@ import { } from "@/components/util/input/verification-failed-flag"; import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { + formatVerificationTooltip, getVerificationErrorsForField, + normalizeVerificationResultMap, + withNormalizedVerificationResult, type VerificationResultLikeMap, } from "@/components/util/input/verification-result-display"; import { useAdminStore } from "@/store/admin-store"; @@ -289,7 +292,10 @@ function normalizePipeTypeStatusRow( lyngPrlg: Number(lyngPrlgWhol ?? 0), dmltnPrlg: Number(dmltnPrlgWhol ?? 0), ...yearFields, - verificationResult: remapPipeTypePrlgWholVerification(verificationResult) ?? undefined, + verificationResult: + normalizeVerificationResultMap( + remapPipeTypePrlgWholVerification(verificationResult), + ) ?? undefined, }; } @@ -719,10 +725,12 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getRegionalFactStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = filterFetched(response.data.regionalFactStatusList).map((d) => ({ - ctpvNm: commonInfo.ctpvNm, - ...d, - })); + const convertedData = filterFetched(response.data.regionalFactStatusList).map((d) => + withNormalizedVerificationResult({ + ctpvNm: commonInfo.ctpvNm, + ...d, + }), + ); applyRowMap((m) => m.set('ST00020001', toGridRows(convertedData))); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); @@ -749,10 +757,12 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getFactInstallStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = filterFetched(response.data.factInstallStatusList).map((d) => ({ - ctpvNm: commonInfo.ctpvNm, - ...d, - })); + const convertedData = filterFetched(response.data.factInstallStatusList).map((d) => + withNormalizedVerificationResult({ + ctpvNm: commonInfo.ctpvNm, + ...d, + }), + ); if (convertedData.length === 0) { const alreadyHandledForSameSearch = factInstallNoDataHandledKeyRef.current === searchConditionKey; @@ -829,10 +839,12 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getPipeDiameterStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = filterFetched(response.data.pipeDiameterStatusList).map((d) => ({ - ctpvNm: commonInfo.ctpvNm, - ...d, - })); + const convertedData = filterFetched(response.data.pipeDiameterStatusList).map((d) => + withNormalizedVerificationResult({ + ctpvNm: commonInfo.ctpvNm, + ...d, + }), + ); applyRowMap((m) => m.set('ST00020004', toGridRows(convertedData))); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); @@ -859,10 +871,12 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getManholeStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = filterFetched(response.data.manholeStatusList).map((d) => ({ - ctpvNm: commonInfo.ctpvNm, - ...d, - })); + const convertedData = filterFetched(response.data.manholeStatusList).map((d) => + withNormalizedVerificationResult({ + ctpvNm: commonInfo.ctpvNm, + ...d, + }), + ); applyRowMap((m) => m.set('ST00020005', toGridRows(convertedData))); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); @@ -889,13 +903,15 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getConnectionStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const facilityConvertedData = filterFetched(response.data.facilityConnectionList).map((d) => ({ - ...d, - })); - const adminConvertedData = filterFetched(response.data.adminAreaConnectionList).map((d) => ({ - ...d, - ctpvNm: d.ctpvNm || commonInfo.ctpvNm, - })); + const facilityConvertedData = filterFetched(response.data.facilityConnectionList).map((d) => + withNormalizedVerificationResult({ ...d }), + ); + const adminConvertedData = filterFetched(response.data.adminAreaConnectionList).map((d) => + withNormalizedVerificationResult({ + ...d, + ctpvNm: d.ctpvNm || commonInfo.ctpvNm, + }), + ); applyRowMap((m) => { m.set('treatmentPlantConnectionInp', toGridRows(facilityConvertedData)); m.set('adminAreaConnectionInp', toGridRows(adminConvertedData)); @@ -2235,17 +2251,7 @@ const SewagePipeInp = () => { tabKey, ); if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - "); - const comp = e.cmprCn ?? ""; - const now = e.nowCn ?? ""; - return `${prefix}${e.rsnExpln} - (검증 기준값: ${comp} / 현재 입력값: ${now})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }} /> diff --git a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx index 1b566a94f..a7bba0621 100644 --- a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx @@ -69,7 +69,11 @@ import { patchFooterVerificationByTab, patchVerificationFailedByTab, } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + withNormalizedVerificationResult, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { useAdminStore } from "@/store/admin-store"; import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store"; @@ -444,10 +448,12 @@ const SewagePipeMaintainInp = () => { repairStatusList ?? [], sidoListRef.current, searchContext, - ).map((d) => ({ - ctpvNm: commonInfo.ctpvNm, - ...d, - })); + ).map((d) => + withNormalizedVerificationResult({ + ctpvNm: commonInfo.ctpvNm, + ...d, + }), + ); const gridRows = toGridRows(convertedData); applyRowMap(mergeIntoExistingMap, (m) => { @@ -487,10 +493,12 @@ const SewagePipeMaintainInp = () => { pipeDredgingStatusList ?? [], sidoListRef.current, searchContext, - ).map((d) => ({ - ctpvNm: commonInfo.ctpvNm, - ...d, - })); + ).map((d) => + withNormalizedVerificationResult({ + ctpvNm: commonInfo.ctpvNm, + ...d, + }), + ); const gridRows = toGridRows(convertedData); applyRowMap(mergeIntoExistingMap, (m) => { @@ -1103,14 +1111,7 @@ const SewagePipeMaintainInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" ? "시계열 검증 - " : e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - "; - return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }; diff --git a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx index f16d6ed52..161434a8a 100644 --- a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx +++ b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx @@ -89,7 +89,11 @@ import { patchFooterVerificationByTab, patchVerificationFailedByTab, } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -279,7 +283,7 @@ function mapSewageReservoirListToGridRows( }, ])[0] as GridRowWithVerification; if (item.verificationResult) { - row.verificationResult = item.verificationResult; + row.verificationResult = normalizeVerificationResultMap(item.verificationResult); } return row; }); @@ -435,7 +439,7 @@ function mapRainfallPumpStationListToGridRows( ])[0] as GridRowWithVerification; if (item.verificationResult) { - row.verificationResult = item.verificationResult; + row.verificationResult = normalizeVerificationResultMap(item.verificationResult); } return row; }); @@ -1516,19 +1520,7 @@ const SewageReservoirPumpInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 -"; - return `${prefix}${e.rsnExpln} -(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }; diff --git a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx index 05ec4fba3..0d974602e 100644 --- a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx @@ -73,7 +73,11 @@ import { patchFooterVerificationByTab, patchVerificationFailedByTab, } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -174,7 +178,9 @@ function mapRegionalListToGridRows< return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const { verificationResult, ...rest } = item; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; - if (verificationResult) row.verificationResult = verificationResult; + if (verificationResult) { + row.verificationResult = normalizeVerificationResultMap(verificationResult); + } return row; }); } @@ -1215,18 +1221,7 @@ const SewerageFinanceInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 - "; - return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }; diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index d08827945..f83a04af8 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -58,7 +58,11 @@ import { toGridRows } from "@/components/util/grid/gridFunction"; import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport"; import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + 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"; @@ -87,7 +91,7 @@ const remapCommonInfoVerificationToGridFields = ( // API 키로도 조회 가능하도록 유지 if (gridField !== key) remapped[key] = errors; } - return remapped; + return normalizeVerificationResultMap(remapped) ?? remapped; }; /** 컬럼 가이드 API 조회용 통계코드 (하수도 보급률) */ @@ -302,7 +306,11 @@ const SeweragePenetrationRateInp = () => { setRowData( convertedData.map((d) => { const row = toGridRows([d])[0] as GridRowWithVerification; - if (d.verificationResult) row.verificationResult = d.verificationResult; + if (d.verificationResult) { + row.verificationResult = normalizeVerificationResultMap( + d.verificationResult, + ); + } return row; }) ); @@ -867,14 +875,7 @@ const SeweragePenetrationRateInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = e.vrfcCd === 'IMR' ? '시계열 검증 - ' : (e.vrfcCd === 'NV' ? '일반검증 - ' : '모집단 검증 -'); - return `${prefix}${e.rsnExpln} - (검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join('\n'); + return formatVerificationTooltip(errors); } }} /> diff --git a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx index 6bfcf4299..3b1d8a529 100644 --- a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx @@ -63,7 +63,11 @@ import { patchFooterVerificationByTab, patchVerificationFailedByTab, } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -187,7 +191,9 @@ function mapServiceLevelListToGridRows< return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const { verificationResult, ...rest } = item; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; - if (verificationResult) row.verificationResult = verificationResult; + if (verificationResult) { + row.verificationResult = normalizeVerificationResultMap(verificationResult); + } return row; }); } @@ -1146,18 +1152,7 @@ const SewerageServiceLevelInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 - "; - return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }; diff --git a/apps/st/src/components/statistics/input/WaterReuseInp.tsx b/apps/st/src/components/statistics/input/WaterReuseInp.tsx index 5a86b5ecd..ef27b944d 100644 --- a/apps/st/src/components/statistics/input/WaterReuseInp.tsx +++ b/apps/st/src/components/statistics/input/WaterReuseInp.tsx @@ -93,7 +93,11 @@ import { patchFooterVerificationByTab, patchVerificationFailedByTab, } from "@/components/util/input/verification-failed-flag"; -import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display"; +import { + formatVerificationTooltip, + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "@/components/util/input/verification-result-display"; import { useAdminStore } from "@/store/admin-store"; import { useTabStore } from "@/store/tab-store"; @@ -443,7 +447,9 @@ function mapPublicSewageTreatWaterReuseListToGridRows( instlAprvYmd: formatCompactYmdForGrid(item.instlAprvYmd), }, ])[0] as GridRowWithVerification; - if (verificationResult) row.verificationResult = verificationResult; + if (verificationResult) { + row.verificationResult = normalizeVerificationResultMap(verificationResult); + } return row; }); } @@ -462,7 +468,9 @@ function mapRecycledWaterStatusListToGridRows( oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd), }, ])[0] as GridRowWithVerification; - if (verificationResult) row.verificationResult = verificationResult; + if (verificationResult) { + row.verificationResult = normalizeVerificationResultMap(verificationResult); + } return row; }); } @@ -480,7 +488,9 @@ function mapRainFallUtilFactStatusListToGridRows( cmptnYmd: formatCompactYmdForGrid(item.cmptnYmd), }, ])[0] as GridRowWithVerification; - if (verificationResult) row.verificationResult = verificationResult; + if (verificationResult) { + row.verificationResult = normalizeVerificationResultMap(verificationResult); + } return row; }); } @@ -497,7 +507,9 @@ function mapTreatWaterReuseStatusListToGridRows< coRegYmd: formatCompactYmdForGrid(item.coRegYmd), }, ])[0] as GridRowWithVerification; - if (verificationResult) row.verificationResult = verificationResult; + if (verificationResult) { + row.verificationResult = normalizeVerificationResultMap(verificationResult); + } return row; }); } @@ -2063,18 +2075,7 @@ const WaterReuseInp = () => { if (!verification) return; const errors = verification[fieldName]; if (Array.isArray(errors) && errors.length > 0) { - return errors - .map((e) => { - const prefix = - e.vrfcCd === "IMR" - ? "시계열 검증 - " - : e.vrfcCd === "NV" - ? "일반검증 - " - : "모집단 검증 - "; - return `${prefix}${e.rsnExpln}(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`; - }) - .filter(Boolean) - .join("\n"); + return formatVerificationTooltip(errors); } }; diff --git a/apps/st/src/components/util/input/verification-failed-flag.ts b/apps/st/src/components/util/input/verification-failed-flag.ts index 7d6c986e9..092c7bae4 100644 --- a/apps/st/src/components/util/input/verification-failed-flag.ts +++ b/apps/st/src/components/util/input/verification-failed-flag.ts @@ -1,6 +1,9 @@ import type { StatisticsInputCommonInfoBase } from "@/entities/statistics/model/statistics-interface"; -import type { VerificationResultLikeMap } from "./verification-result-display"; +import { + normalizeVerificationResultMap, + type VerificationResultLikeMap, +} from "./verification-result-display"; export function isVerificationFailedFromCommonInfo( commonInfo: Pick | null | undefined, @@ -28,6 +31,9 @@ export function patchFooterVerificationByTab( | undefined, ): Map { const next = new Map(prev); - next.set(tabKey, commonInfo?.verificationResult ?? null); + next.set( + tabKey, + normalizeVerificationResultMap(commonInfo?.verificationResult) ?? null, + ); return next; } diff --git a/apps/st/src/components/util/input/verification-result-display.ts b/apps/st/src/components/util/input/verification-result-display.ts index 75dbbe459..78a3952d5 100644 --- a/apps/st/src/components/util/input/verification-result-display.ts +++ b/apps/st/src/components/util/input/verification-result-display.ts @@ -13,6 +13,49 @@ export type VerificationResultLikeMap = Record< VerificationResultLikeItem[] >; +/** 입력 조회 verificationResult의 기준값/입력값이 null이면 "-" */ +export function displayVerificationCn( + value: string | null | undefined, +): string { + if (value == null || String(value).trim() === "") return "-"; + return String(value); +} + +export function normalizeVerificationResultItem( + item: T, +): T { + return { + ...item, + nowCn: displayVerificationCn(item.nowCn ?? item.nowVlCn), + cmprCn: displayVerificationCn(item.cmprCn ?? item.compVlCn), + }; +} + +export function normalizeVerificationResultMap( + result: T | null | undefined, +): T | undefined { + if (!result || typeof result !== "object") return undefined; + const next = {} as T; + for (const [key, items] of Object.entries(result)) { + (next as VerificationResultLikeMap)[key] = Array.isArray(items) + ? items.map((item) => normalizeVerificationResultItem(item)) + : []; + } + return next; +} + +/** 조회 행/객체에 붙은 verificationResult의 cmprCn·nowCn null을 "-"로 바꿈 */ +export function withNormalizedVerificationResult(item: T): T { + if (!("verificationResult" in item) || item.verificationResult == null) { + return item; + } + const verificationResult = normalizeVerificationResultMap( + item.verificationResult as VerificationResultLikeMap, + ); + if (!verificationResult) return item; + return { ...item, verificationResult }; +} + /** 검증 오류 배열 → RealGrid styleName */ export function getVerificationStyleName( errors: VerificationResultLikeItem[] | null | undefined, @@ -37,8 +80,8 @@ export function formatVerificationTooltip( : e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - "; - const comp = e.cmprCn ?? e.compVlCn ?? ""; - const now = e.nowCn ?? e.nowVlCn ?? ""; + const comp = displayVerificationCn(e.cmprCn ?? e.compVlCn); + const now = displayVerificationCn(e.nowCn ?? e.nowVlCn); return `${prefix}${e.rsnExpln}\n(검증 기준값: ${comp} / 현재 입력값: ${now})`; }) .filter(Boolean) From 1cffe06cf97c0b91b771904e5937542bec3a5b1b Mon Sep 17 00:00:00 2001 From: rsone001 Date: Thu, 22 Oct 2026 18:02:48 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[ST]=20-=20input=20:=20=EC=8B=9C=EA=B3=84?= =?UTF-8?q?=EC=97=B4=20=EA=B2=80=EC=A6=9D=20=EC=98=A4=EB=A5=98=20=EC=8B=9C?= =?UTF-8?q?=20=EC=A7=84=ED=96=89=EC=83=81=ED=99=A9=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A1=A4=EB=B0=B1=20api=20=EC=B6=94=EA=B0=80,=20=EC=9D=BC?= =?UTF-8?q?=EB=B0=98=20=EA=B2=80=EC=A6=9D=20=EA=B4=80=EB=A6=AC=20interface?= =?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 --- .../input/ExcretaTreatmentFacilityInp.tsx | 9 +++++++- .../input/PrivateSewageFacilityInp.tsx | 9 +++++++- .../input/PublicSewageFacilityInp.tsx | 9 +++++++- .../statistics/input/PumpingStationInp.tsx | 9 +++++++- .../input/SewageExcretaRelatedInp.tsx | 9 +++++++- .../input/SewageExcretaSlurgeInp.tsx | 9 +++++++- .../statistics/input/SewagePipeInp.tsx | 9 +++++++- .../input/SewagePipeMaintainInp.tsx | 9 +++++++- .../input/SewageReservoirPumpInp.tsx | 9 +++++++- .../statistics/input/SewerageFinanceInp.tsx | 9 +++++++- .../input/SeweragePenetrationRateInp.tsx | 9 +++++++- .../input/SewerageServiceLevelInp.tsx | 9 +++++++- .../statistics/input/WaterReuseInp.tsx | 9 +++++++- .../generalVerification/DetailComponent.tsx | 11 +++++---- .../generalVerification/RegistDialog.tsx | 21 +++++++++++------ .../util/input/call-imr-with-rollback.ts | 23 +++++++++++++++++++ .../api/general-verification-service.ts | 9 +++++--- .../statistics/api/statistics-service.ts | 13 +++++++++++ .../model/general-verification-interface.ts | 6 ++--- .../statistics/model/statistics-interface.ts | 16 +++++++++++++ 20 files changed, 186 insertions(+), 30 deletions(-) create mode 100644 apps/st/src/components/util/input/call-imr-with-rollback.ts diff --git a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx index 87df51eca..912993caf 100644 --- a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx @@ -20,6 +20,7 @@ import { nullableTrimmedString } from "@/components/util/input/nullableString"; 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"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import { EXCRETA_TREATMENT_FACILITY_STATS_CD, type ExcretaTreatmentFacilityItem, @@ -819,7 +820,13 @@ const ExcretaTreatmentFacilityInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForSubmit, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: statusInfo?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx index 9b37b277c..ee95052a1 100644 --- a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx @@ -9,6 +9,7 @@ import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { statisticsInputPrivateSewageFacilityApi } from "@/entities/statistics/api/input/statistics-input-private-sewage-facility-service"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import type { PrivateSewageDesigStatusItem, PrivateSewageDesigStatusSaveItem, @@ -894,7 +895,13 @@ const PrivateSewageFacilityInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForTab, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx index 48b9ca3b0..cac3212cc 100644 --- a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx @@ -8,6 +8,7 @@ import { useStatisticsInputPendingNavigation } from '@/shared/lib/use-statistics import { statisticsInputSewSupplyRateApi } from '@/entities/statistics/api/input/statistics-input-sew-supply-rate-service'; import { statisticsInputPublicSewageFacilityApiService } from '@/entities/statistics/api/input/statistics-public-sewage-facility-input-service'; import { statisticsApi } from '@/entities/statistics/api/statistics-service'; +import { callImrServiceWithRollbackOnFailure } from '@/components/util/input/call-imr-with-rollback'; import type { PublicSewageFacilityRequestParams, PublicSewageFacilitySaveItem, @@ -442,7 +443,13 @@ const PublicSewageFacilityInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? '', + statsCd: statsCdForTab, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? '', + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? '', + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/PumpingStationInp.tsx b/apps/st/src/components/statistics/input/PumpingStationInp.tsx index 752528b27..412318321 100644 --- a/apps/st/src/components/statistics/input/PumpingStationInp.tsx +++ b/apps/st/src/components/statistics/input/PumpingStationInp.tsx @@ -20,6 +20,7 @@ import { nullableTrimmedString } from "@/components/util/input/nullableString"; 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"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import { PUMPING_STATION_STATS_CD, type PumpingStationItem, @@ -802,7 +803,13 @@ const PumpingStationInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForSubmit, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: statusInfo?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx index 8ab561ba6..7a325ee3c 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx @@ -13,6 +13,7 @@ import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsInputSewageExcretaRelatedApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-related-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import type { ExcretaRelatedIndustrySaveItem, PrivateSewageTreatRelatedIndustrySaveItem, @@ -1495,7 +1496,13 @@ const SewageExcretaRelatedInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForTab, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx index c40e5018a..d79f6be23 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx @@ -21,6 +21,7 @@ import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsInputSewageExcretaSlurgeApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import type { DigestorOperatingStatusSaveItem, ExcretaSlurgeProcessingStatusSaveItem, @@ -1478,7 +1479,13 @@ const SewageExcretaSlurgeInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForSubmit, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/SewagePipeInp.tsx b/apps/st/src/components/statistics/input/SewagePipeInp.tsx index a05e9f982..3aced129c 100644 --- a/apps/st/src/components/statistics/input/SewagePipeInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeInp.tsx @@ -10,6 +10,7 @@ import { import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import type { SeweragePipeInputFactInstallStatusSaveRequest, SeweragePipeInputFactInstallStatusSubmitRequest, @@ -1470,7 +1471,13 @@ const SewagePipeInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: values.year ?? "", + statsCd: statsCdForTab, + sewerBzmnCd: values?.bsnmCd ?? "", + rollbackStatusCode: statusForTab?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx index a7bba0621..e1235c14d 100644 --- a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx @@ -11,6 +11,7 @@ import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { sewagePipeMaintainApiService } from "@/entities/statistics/api/input/statistics-input-sewage-pipe-maintain-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import type { SewagePipeMaintainDredgingSaveItem, SewagePipeMaintainDredgingSaveRequest, @@ -686,7 +687,13 @@ const SewagePipeMaintainInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForTab, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx index 161434a8a..93d5f3db5 100644 --- a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx +++ b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx @@ -21,6 +21,7 @@ import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsInputSewageReservoirPumpApi } from "@/entities/statistics/api/input/statistics-input-sewage-reservoir-pump-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import { type RainfallPumpStationItem, type RainfallPumpStationSaveItem, @@ -868,7 +869,13 @@ const SewageReservoirPumpInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForTab, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx index 0d974602e..5e71ec541 100644 --- a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx @@ -8,6 +8,7 @@ import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsInputSewerageFinanceApi } from "@/entities/statistics/api/input/statistics-input-sewerage-finance-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import type { CauserBurdenItem, CauserBurdenSaveItem, @@ -946,7 +947,13 @@ const SewerageFinanceInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForTab, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index f83a04af8..d5f560a54 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -10,6 +10,7 @@ import { import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import { type CheckInputPeriodItem, getVerificationPeriodActionBlockedMessage, @@ -566,7 +567,13 @@ const SeweragePenetrationRateInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: searchValues?.year ?? "", + statsCd, + sewerBzmnCd: searchValues?.bsnmCd ?? "", + rollbackStatusCode: statusInfo?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted(imrResponse.job_id, (id) => statisticsApi.getImrStatus(id), diff --git a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx index 3b1d8a529..2ebae634a 100644 --- a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx @@ -9,6 +9,7 @@ import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsInputSewerageServiceLevelApi } from "@/entities/statistics/api/input/statistics-input-sewerage-service-level-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import { SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD, SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD, @@ -845,7 +846,13 @@ const SewerageServiceLevelInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForTab, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/input/WaterReuseInp.tsx b/apps/st/src/components/statistics/input/WaterReuseInp.tsx index ef27b944d..d631c6eba 100644 --- a/apps/st/src/components/statistics/input/WaterReuseInp.tsx +++ b/apps/st/src/components/statistics/input/WaterReuseInp.tsx @@ -21,6 +21,7 @@ import { nullableTrimmedString } from "@/components/util/input/nullableString"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsInputWaterReuseApi } from "@/entities/statistics/api/input/statistics-input-water-reuse-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { callImrServiceWithRollbackOnFailure } from "@/components/util/input/call-imr-with-rollback"; import { type PublicSewageTreatWaterReuseItem, type PublicSewageTreatWaterReuseSaveItem, @@ -1367,7 +1368,13 @@ const WaterReuseInp = () => { }; try { - const imrResponse = await statisticsApi.callImrService(imrParam); + const imrResponse = await callImrServiceWithRollbackOnFailure(imrParam, { + statsYr: lastSearchedValues.year ?? "", + statsCd: statsCdForTab, + sewerBzmnCd: lastSearchedValues?.bsnmCd ?? "", + rollbackStatusCode: activeTabStatus?.lclgvSbmsnSttsCd ?? "", + mdfrUuid: userInfo.userUnqId, + }); if (imrResponse.job_id) { await waitForImrJobCompleted( imrResponse.job_id, diff --git a/apps/st/src/components/statistics/verification/generalVerification/DetailComponent.tsx b/apps/st/src/components/statistics/verification/generalVerification/DetailComponent.tsx index 381e7fea7..632f57762 100644 --- a/apps/st/src/components/statistics/verification/generalVerification/DetailComponent.tsx +++ b/apps/st/src/components/statistics/verification/generalVerification/DetailComponent.tsx @@ -554,17 +554,20 @@ const DetailComponent = ({ const userInfo = await getUserInfo(); let response; + const vrfcSn = detailInfo?.commonInfo.vrfcSn; + if (vrfcSn == null) { + alert("검증 일련번호가 없습니다."); + return; + } if (!hasPersistedDetails) { response = - await generalverificationApiService.RegistVrfcDetail({ - vrfcSn: detailInfo?.commonInfo.vrfcSn ?? null, + await generalverificationApiService.RegistVrfcDetail(vrfcSn, { detailList: rows, rgtrUuid: userInfo.userUnqId, }); } else { response = - await generalverificationApiService.UpdateVrfcDetail({ - vrfcSn: detailInfo?.commonInfo.vrfcSn ?? null, + await generalverificationApiService.UpdateVrfcDetail(vrfcSn, { detailList: rows, mdfrUuid: userInfo.userUnqId, }); diff --git a/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx b/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx index 3eca65196..b589b6bb2 100644 --- a/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx +++ b/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx @@ -146,9 +146,15 @@ const RegistDialog = memo( } }; - const handleUpdateVrfcMaster = async (data: GeneralVerificationMasterSubmitFormData) => { + const handleUpdateVrfcMaster = async ( + updateVrfcSn: number, + body: UpdateVrfcMasterRequest, + ) => { try { - const response = await generalverificationApiService.UpdateVrfcMaster(data as UpdateVrfcMasterRequest); + const response = await generalverificationApiService.UpdateVrfcMaster( + updateVrfcSn, + body, + ); if (response.status === 'success') { alert('수정되었습니다.'); @@ -258,8 +264,11 @@ const RegistDialog = memo( if (await confirm(confirmMessage)) { const userInfo = await getUserInfo(); if (isVrfcSn) { - const updateData = { - vrfcSn: data.vrfcSn, + if (data.vrfcSn == null) { + alert('수정 대상이 없습니다.'); + return; + } + await handleUpdateVrfcMaster(data.vrfcSn, { vrfcNm: data.vrfcNm, vrfcClsfCd: data.vrfcClsfCd, dcptDgt: data.dcptDgt, @@ -267,10 +276,8 @@ const RegistDialog = memo( ...(data.dcptDgt === 0 ? {} : { dcptPrcsMthCd: data.dcptPrcsMthCd }), - trgtStatsCd: data.trgtStatsCd, mdfrUuid: userInfo.userUnqId, - }; - await handleUpdateVrfcMaster(updateData); + }); } else { const registData = { vrfcNm: data.vrfcNm, diff --git a/apps/st/src/components/util/input/call-imr-with-rollback.ts b/apps/st/src/components/util/input/call-imr-with-rollback.ts new file mode 100644 index 000000000..743680c49 --- /dev/null +++ b/apps/st/src/components/util/input/call-imr-with-rollback.ts @@ -0,0 +1,23 @@ +import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import type { + ImrRequest, + ImrResponse, + rollbackStatusRequest, +} from "@/entities/statistics/model/statistics-interface"; + +/** 제출 직후 IMR 요청이 실패하면 검증 이전 상태로 롤백 */ +export async function callImrServiceWithRollbackOnFailure( + imrParam: ImrRequest, + rollbackParam: rollbackStatusRequest, +): Promise { + try { + return await statisticsApi.callImrService(imrParam); + } catch (error) { + try { + await statisticsApi.rollbackStatus(rollbackParam); + } catch { + // 롤백 실패는 IMR 실패를 가리지 않음 + } + throw error; + } +} diff --git a/apps/st/src/entities/statistics/api/general-verification-service.ts b/apps/st/src/entities/statistics/api/general-verification-service.ts index 9b0b69ff4..2178c2671 100644 --- a/apps/st/src/entities/statistics/api/general-verification-service.ts +++ b/apps/st/src/entities/statistics/api/general-verification-service.ts @@ -56,10 +56,11 @@ export class GeneralverificationApiService implements GeneralverificationService } public async UpdateVrfcMaster( + vrfcSn: number, param: UpdateVrfcMasterRequest, ): Promise { const response = await stClient.put( - `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/masters/${param.vrfcSn}`, + `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/masters/${vrfcSn}`, param, ); return response.data; @@ -127,20 +128,22 @@ export class GeneralverificationApiService implements GeneralverificationService } public async RegistVrfcDetail( + vrfcSn: number, param: VerifyDetailRegistRequest, ): Promise { const response = await stClient.post( - `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${param.vrfcSn}/details`, + `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${vrfcSn}/details`, param, ); return response.data; } public async UpdateVrfcDetail( + vrfcSn: number, param: VerifyDetailUpdateRequest, ): Promise { const response = await stClient.put( - `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${param.vrfcSn}/details`, + `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${vrfcSn}/details`, param, ); return response.data; diff --git a/apps/st/src/entities/statistics/api/statistics-service.ts b/apps/st/src/entities/statistics/api/statistics-service.ts index 592508240..314f5e026 100644 --- a/apps/st/src/entities/statistics/api/statistics-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-service.ts @@ -16,6 +16,8 @@ import { type ImrStatusResponse, type ReviewerInfoRequest, type ReviewerInfoResponse, + type rollbackStatusRequest, + type rollbackStatusResponse, type ReviewInputSaveRequest, type ReviewOpinionSaveRequest, type ReviewOpinionSaveResponse, @@ -156,6 +158,17 @@ export class StatisticsApiService implements StatisticsService { return response.data; } + // 입력 - 검증 이전 상태로 롤백 + public async rollbackStatus( + param: rollbackStatusRequest, + ): Promise { + const response = await stClient.put( + `${ENDPOINT.ST_SERVICE.INPUTCOMMON}/rollback-status`, + param, + ); + return response.data; + } + public async callImrService(param: ImrRequest): Promise { const response = await client.post( `${ENDPOINT.STATVERIFY_SERVICE.IMR}`, diff --git a/apps/st/src/entities/statistics/model/general-verification-interface.ts b/apps/st/src/entities/statistics/model/general-verification-interface.ts index e7726bf45..9930ce246 100644 --- a/apps/st/src/entities/statistics/model/general-verification-interface.ts +++ b/apps/st/src/entities/statistics/model/general-verification-interface.ts @@ -46,7 +46,6 @@ export interface CheckVerificationNameResponse { } export interface UpdateVrfcMasterRequest { - vrfcSn?: number | null; vrfcNm?: string | null; vrfcClsfCd?: string | null; dcptDgt?: number | null; @@ -184,13 +183,11 @@ export interface VerifyDetailRegistData { } export interface VerifyDetailRegistRequest { - vrfcSn: number | null; detailList: VerifyDetailRegistData[]; rgtrUuid: string; } export interface VerifyDetailUpdateRequest { - vrfcSn: number | null; detailList: VerifyDetailRegistData[]; mdfrUuid: string; } @@ -297,6 +294,7 @@ export interface GeneralverificationService { param: CheckVerificationNameRequest, ): Promise; UpdateVrfcMaster( + vrfcSn: number, param: UpdateVrfcMasterRequest, ): Promise; RegistVrfcMaster( @@ -315,9 +313,11 @@ export interface GeneralverificationService { param: string, ): Promise; RegistVrfcDetail( + vrfcSn: number, param: VerifyDetailRegistRequest, ): Promise; UpdateVrfcDetail( + vrfcSn: number, param: VerifyDetailUpdateRequest, ): Promise; CopyVrfcData( diff --git a/apps/st/src/entities/statistics/model/statistics-interface.ts b/apps/st/src/entities/statistics/model/statistics-interface.ts index d95f97921..6682bfd74 100644 --- a/apps/st/src/entities/statistics/model/statistics-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-interface.ts @@ -31,6 +31,21 @@ export interface cancelSubmitResponse { message?: string; } +/** 검증 이전 상태로 롤백 요청 */ +export interface rollbackStatusRequest { + statsYr: string; + statsCd: string; + sewerBzmnCd: string; + rollbackStatusCode: string; + mdfrUuid: string; +} + +/** 검증 이전 상태로 롤백 응답 */ +export interface rollbackStatusResponse { + status: string; + message?: string; +} + export interface ApiResponse { status: string; data?: T; @@ -557,6 +572,7 @@ export interface StatisticsService { saveStatsInputPeriod(param: StatsInputPeriodSaveRequest): Promise; deleteStatsInputPeriod(param: StatsInputPeriodDeleteRequest): Promise; cancelSubmit(param: cancelSubmitRequest): Promise; + rollbackStatus(param: rollbackStatusRequest): Promise; callImrService(param: ImrRequest): Promise; submitException(param: SubmitExceptionRequest): Promise; getLogList(param: StatisticsLogRequest): Promise; From b6115381809f313b9e74b8389a333eb0132dc7d3 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Fri, 23 Oct 2026 13:48:17 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[ST]=20-=20input=202-5=20=EB=A7=A8=ED=99=80?= =?UTF-8?q?=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sewagePipe/ManholeStatusInpColumn.ts | 43 +++++++++++++++++++ .../statistics-sewage-pipe-input-interface.ts | 4 ++ 2 files changed, 47 insertions(+) diff --git a/apps/st/src/components/util/column/input/sewagePipe/ManholeStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewagePipe/ManholeStatusInpColumn.ts index a1fe36bc8..ed5f44331 100644 --- a/apps/st/src/components/util/column/input/sewagePipe/ManholeStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewagePipe/ManholeStatusInpColumn.ts @@ -49,6 +49,43 @@ export const ManholeStatusInpColumn = { styleName: "rg-cell-readonly", footer: { text: "합계" }, }, + { + name: "wholMnhlCntSum", + fieldName: "wholMnhlCntSum", + header: { text: "맨홀총계" }, + footer: footerInt, + editable: false, + valueType: ValueType.NUMBER, + dataFormat: "#,##0", + styleName: "rg-cell-readonly", + }, + { + name: "frlvlMnhlCntSum", + fieldName: "frlvlMnhlCntSum", + header: { text: "주철계" }, + footer: footerInt, + editable: false, + valueType: ValueType.NUMBER, + dataFormat: "#,##0", + styleName: "rg-cell-readonly", + }, + { + name: "thlvlMnhlCntSum", + fieldName: "thlvlMnhlCntSum", + header: { text: "콘크리트계" }, + footer: footerInt, + editable: false, + valueType: ValueType.NUMBER, + dataFormat: "#,##0", + styleName: "rg-cell-readonly", + }, + { + name: "picNm", + fieldName: "picNm", + header: { text: "담당자명" }, + editable: false, + styleName: "rg-cell-readonly", + }, { ...intCol("wholMnhlCnt", "계", false), computed: { @@ -88,6 +125,12 @@ export const ManholeStatusInpColumn = { }, "admdstNm", "emphsMngSeNm", + { + name: "adminRefGroup", + hideChildHeaders: false, + header: { text: "행정참고자료" }, + items: ["wholMnhlCntSum", "frlvlMnhlCntSum", "thlvlMnhlCntSum", "picNm"], + }, { name: "mtrlGroup", hideChildHeaders: false, diff --git a/apps/st/src/entities/statistics/model/input/statistics-sewage-pipe-input-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-sewage-pipe-input-interface.ts index 400eac0cd..9aa71e516 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-sewage-pipe-input-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-sewage-pipe-input-interface.ts @@ -419,6 +419,10 @@ export interface SeweragePipeInputManholeStatusItem { folvlMnhlCnt: number; filvlMnhlCnt: number; rmrk: string; + wholMnhlCntSum: number; + frlvlMnhlCntSum: number; + thlvlMnhlCntSum: number; + picNm: string | null; verificationResult?: SeweragePipeInputVerificationResult; } From 3cf3617513250fa171fda4bf407a7c05678bb881 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Fri, 23 Oct 2026 14:45:14 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[ST]=20-=20=EA=B2=80=ED=86=A0=20&=20?= =?UTF-8?q?=EC=A7=91=EA=B3=84=202-5=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/column/review/SewagePipeRvwColumn.ts | 10 ++++++++++ .../util/column/total/SewagePipeTotColumn.ts | 9 +++++++++ .../model/statistics-sewage-pipe-review-interface.ts | 4 ++++ .../statistics-sewage-pipe-total-interface.ts | 4 ++++ 4 files changed, 27 insertions(+) diff --git a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts index f4ce17c0b..ec95fdcaa 100644 --- a/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts +++ b/apps/st/src/components/util/column/review/SewagePipeRvwColumn.ts @@ -491,6 +491,10 @@ const rawList = [ { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true }, { name: 'emphsMngSeNm', fieldName: 'emphsMngSeNm', header: { text: '집중강우\n중점관리\n구역구분' }, mergeCells: true }, + { name: 'wholMnhlCntSum', fieldName: 'wholMnhlCntSum', header: { text: '맨홀총계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'frlvlMnhlCntSum', fieldName: 'frlvlMnhlCntSum', header: { text: '주철계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'thlvlMnhlCntSum', fieldName: 'thlvlMnhlCntSum', header: { text: '콘크리트계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'picNm', fieldName: 'picNm', header: { text: '담당자명' } }, { name: 'displayYear', fieldName: 'displayYear', @@ -544,6 +548,12 @@ const rawList = [ ], }, 'emphsMngSeNm', + { + name: 'adminRefGroup', + hideChildHeaders: false, + header: { text: '행정참고자료' }, + items: ['wholMnhlCntSum', 'frlvlMnhlCntSum', 'thlvlMnhlCntSum', 'picNm'], + }, 'displayYear', { name: 'mtrlGroup', diff --git a/apps/st/src/components/util/column/total/SewagePipeTotColumn.ts b/apps/st/src/components/util/column/total/SewagePipeTotColumn.ts index ca1ad1bf3..5d0043de2 100644 --- a/apps/st/src/components/util/column/total/SewagePipeTotColumn.ts +++ b/apps/st/src/components/util/column/total/SewagePipeTotColumn.ts @@ -710,6 +710,10 @@ const rawSewagePipeTotList = [ { name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false }, { name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역명" }, editable: false }, { name: "emphsMngSeNm", fieldName: "emphsMngSeNm", header: { text: "집중강우\n중점관리\n구역구분" }, editable: false }, + { name: "wholMnhlCntSum", fieldName: "wholMnhlCntSum", header: { text: "맨홀총계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false }, + { name: "frlvlMnhlCntSum", fieldName: "frlvlMnhlCntSum", header: { text: "주철계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false }, + { name: "thlvlMnhlCntSum", fieldName: "thlvlMnhlCntSum", header: { text: "콘크리트계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false }, + { name: "picNm", fieldName: "picNm", header: { text: "담당자명" }, editable: false }, { name: "wholMnhlCnt", fieldName: "wholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false }, { name: "frlvlMnhlCnt", fieldName: "frlvlMnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false }, { name: "sclvlMnhlCnt", fieldName: "sclvlMnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false }, @@ -720,6 +724,11 @@ const rawSewagePipeTotList = [ columnLayout: [ { name: "addrGroup", header: { text: "행정구역" }, items: ["ctpvNm", "sggNm", "admdstNm"] }, "emphsMngSeNm", + { + name: "adminRefGroup", + header: { text: "행정참고자료" }, + items: ["wholMnhlCntSum", "frlvlMnhlCntSum", "thlvlMnhlCntSum", "picNm"], + }, { name: "mtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, diff --git a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts index 6be096c80..9ae98c6f9 100644 --- a/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-sewage-pipe-review-interface.ts @@ -247,6 +247,10 @@ export interface SewagePipeReviewManholeItem { folvlMnhlCnt: number; filvlMnhlCnt: number; rmrk: string | null; + wholMnhlCntSum: number; + frlvlMnhlCntSum: number; + thlvlMnhlCntSum: number; + picNm: string | null; } export interface SewagePipeManholeResponseData { diff --git a/apps/st/src/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface.ts b/apps/st/src/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface.ts index 8dc0a2fd1..265666cad 100644 --- a/apps/st/src/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface.ts @@ -168,6 +168,10 @@ export interface FcltyManholeSummaryItem { thlvlMnhlCnt: number; folvlMnhlCnt: number; filvlMnhlCnt: number; + wholMnhlCntSum: number; + frlvlMnhlCntSum: number; + thlvlMnhlCntSum: number; + picNm: string | null; } /** From 2ec58f1f5b861daf699cef05e6231de3f158f88c Mon Sep 17 00:00:00 2001 From: rsone001 Date: Tue, 27 Oct 2026 15:52:00 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[ST]=20-=20=EA=B8=B0=EB=B3=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EB=8C=80=EC=8B=9C?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EB=8F=84=EC=9B=80=EB=A7=90=EC=9D=84=20?= =?UTF-8?q?=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20=EC=9C=84=ED=95=B4=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=EC=BD=94=EB=93=9C=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/shared/lib/statistics-tab-url.ts | 2 ++ apps/st/src/shared/ui/statistics-page-layout.tsx | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/st/src/shared/lib/statistics-tab-url.ts b/apps/st/src/shared/lib/statistics-tab-url.ts index c3588254f..1e864689e 100644 --- a/apps/st/src/shared/lib/statistics-tab-url.ts +++ b/apps/st/src/shared/lib/statistics-tab-url.ts @@ -1,5 +1,7 @@ export const STATISTICS_ROUTE = "/statistics"; export const STATISTICS_TAB_QUERY_KEY = "tab"; +/** 기본 화면(대시보드) 도움말 매칭용. URL에는 넣지 않음 */ +export const STATISTICS_DASHBOARD_TAB_ID = "Dashboard"; export function buildStatisticsTabUrl(tabId: string): string { return `${STATISTICS_ROUTE}?${STATISTICS_TAB_QUERY_KEY}=${encodeURIComponent(tabId)}`; diff --git a/apps/st/src/shared/ui/statistics-page-layout.tsx b/apps/st/src/shared/ui/statistics-page-layout.tsx index d94c42304..224a6b643 100644 --- a/apps/st/src/shared/ui/statistics-page-layout.tsx +++ b/apps/st/src/shared/ui/statistics-page-layout.tsx @@ -1,13 +1,20 @@ import { PageTitle } from "@common/components"; +import { findMenuItemByUrlPath } from "@/shared/lib/stats-cd-input-navigation"; +import { STATISTICS_DASHBOARD_TAB_ID } from "@/shared/lib/statistics-tab-url"; + import { TabBar } from "./tab-bar"; import { useTabStore } from "@/store/tab-store"; function StatisticsPageLayout({ children }: { children: React.ReactNode }) { - const { activeTabId, tabs } = useTabStore(); + const { activeTabId, tabs, menuList } = useTabStore(); const pageTitle = tabs.find((t) => t.id === activeTabId); + const dashboardMenu = + !pageTitle && menuList + ? findMenuItemByUrlPath(menuList, STATISTICS_DASHBOARD_TAB_ID) + : null; const menuPaths = pageTitle?.path?.split(">").map((path) => path.trim()); @@ -16,7 +23,7 @@ function StatisticsPageLayout({ children }: { children: React.ReactNode }) { {children}