From cea2d39df8132257b249e46d69622fd6c078f166 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Thu, 2 Jul 2026 17:46:13 +0900 Subject: [PATCH] =?UTF-8?q?[st]=20-=20=EC=9D=BC=EC=A0=95=EB=93=B1=EB=A1=9D?= =?UTF-8?q?,=20=EC=9E=85=EB=A0=A5=EB=8B=A8=20=EC=A7=80=EB=82=9C=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=EA=B2=B0=EA=B3=BC=20=EC=95=8C=EB=A6=BC=20=EB=B1=83?= =?UTF-8?q?=EC=A7=80,=20=EB=A1=9C=EA=B7=B8=20=EC=83=81=EC=84=B8=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EA=B2=80=ED=86=A0=EB=8B=A8=20cell=20style=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../button/VerificationResultButton.css | 24 +++ .../button/VerificationResultButton.tsx | 30 ++- apps/st/src/components/css/DataGrid.css | 24 +++ apps/st/src/components/module/DataGrid.tsx | 52 +++-- .../input/ExcretaTreatmentFacilityInp.tsx | 24 ++- .../input/PrivateSewageFacilityInp.tsx | 45 ++++- .../input/PublicSewageFacilityInp.tsx | 50 ++++- .../statistics/input/PumpingStationInp.tsx | 25 ++- .../input/SewageExcretaRelatedInp.tsx | 61 +++++- .../input/SewageExcretaSlurgeInp.tsx | 66 ++++++- .../statistics/input/SewagePipeInp.tsx | 29 +++ .../input/SewagePipeMaintainInp.tsx | 15 ++ .../input/SewageReservoirPumpInp.tsx | 57 +++++- .../statistics/input/SewerageFinanceInp.tsx | 48 ++++- .../input/SeweragePenetrationRateInp.tsx | 5 + .../input/SewerageServiceLevelInp.tsx | 51 ++++- .../statistics/input/WaterReuseInp.tsx | 75 ++++++- .../statistics/management/LogHistory.tsx | 2 + .../management/LogHistoryDetailDialog.tsx | 98 ++++++++-- .../LogHistoryDetailsNestedDialog.tsx | 98 ++++++++++ .../management/StatisticsInputManagement.tsx | 10 + .../StatisticsInputPeriodDialog.tsx | 10 + .../review/ExcretaTreatmentFacilityRvw.tsx | 9 +- .../review/PrivateSewageFacilityRvw.tsx | 9 +- .../review/PublicSewageFacilityRvw.tsx | 29 +-- .../statistics/review/PumpingStationRvw.tsx | 11 +- .../review/ReviewEmptyCurrLegend.tsx | 16 ++ .../review/SewageExcretaRelatedRvw.tsx | 9 +- .../review/SewageExcretaSlurgeRvw.tsx | 9 +- .../review/SewagePipeMaintainRvw.tsx | 9 +- .../statistics/review/SewagePipeRvw.tsx | 9 +- .../review/SewageReservoirPumpRvw.tsx | 11 +- .../statistics/review/SewerageFinanceRvw.tsx | 9 +- .../review/SeweragePenetrationRateRvw.tsx | 29 +-- .../review/SewerageServiceLevelRvw.tsx | 9 +- .../statistics/review/WaterReuseRvw.tsx | 9 +- .../generalVerification/CopyDialog.tsx | 13 +- .../generalVerification/RegistDialog.tsx | 7 +- apps/st/src/components/util/MenuMatchList.ts | 107 +++++++++- .../management/LogHistoryDetailColumn.ts | 119 +++++++++++- .../review/PublicSewageFacilityRvwColumn.ts | 5 +- .../src/components/util/grid/gridFunction.ts | 20 ++ .../util/grid/reviewEmptyCurrCellStyle.ts | 183 ++++++++++++++++++ .../util/input/verification-failed-flag.ts | 17 ++ .../api/general-verification-service.ts | 17 +- .../api/statistics-management-service.ts | 2 +- .../statistics/api/statistics-service.ts | 7 +- .../model/general-verification-interface.ts | 6 +- ...ut-excreta-treatment-facility-interface.ts | 4 +- ...input-private-sewage-facility-interface.ts | 8 +- ...tistics-input-pumping-station-interface.ts | 4 +- ...-input-sewage-excreta-related-interface.ts | 4 +- ...s-input-sewage-excreta-slurge-interface.ts | 4 +- ...cs-input-sewage-pipe-maintain-interface.ts | 4 +- ...s-input-sewage-reservoir-pump-interface.ts | 6 +- ...istics-input-sewerage-finance-interface.ts | 10 +- ...-input-sewerage-service-level-interface.ts | 6 +- .../statistics-input-water-reuse-interface.ts | 4 +- ...-public-sewage-facility-input-interface.ts | 4 +- .../statistics-sewage-pipe-input-interface.ts | 4 +- ...tics-sewage-supply-rate-input-interface.ts | 4 +- ...eta-treatment-facility-review-interface.ts | 4 +- .../statistics/model/statistics-interface.ts | 9 + ...istics-pumping-station-review-interface.ts | 2 +- .../shared/lib/stats-cd-input-navigation.ts | 90 +++++++-- ...use-statistics-input-pending-navigation.ts | 71 +++++++ 66 files changed, 1615 insertions(+), 206 deletions(-) create mode 100644 apps/st/src/components/button/VerificationResultButton.css create mode 100644 apps/st/src/components/statistics/management/LogHistoryDetailsNestedDialog.tsx create mode 100644 apps/st/src/components/statistics/review/ReviewEmptyCurrLegend.tsx create mode 100644 apps/st/src/components/util/grid/reviewEmptyCurrCellStyle.ts create mode 100644 apps/st/src/components/util/input/verification-failed-flag.ts create mode 100644 apps/st/src/shared/lib/use-statistics-input-pending-navigation.ts diff --git a/apps/st/src/components/button/VerificationResultButton.css b/apps/st/src/components/button/VerificationResultButton.css new file mode 100644 index 000000000..c08a1f2fb --- /dev/null +++ b/apps/st/src/components/button/VerificationResultButton.css @@ -0,0 +1,24 @@ +.verification-result-btn-wrap { + position: relative; + display: inline-flex; +} + +.verification-result-btn-failed-dot { + position: absolute; + left: 0.35rem; + top: 0.35rem; + width: 0.55rem; + height: 0.55rem; + border-radius: 9999px; + background: #ff6c64; + box-shadow: 0 0 0 0 rgba(255, 108, 100, 0.75); + animation: verification-result-btn-dot-ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + pointer-events: none; +} + +@keyframes verification-result-btn-dot-ping { + 75%, + 100% { + box-shadow: 0 0 0 0.5rem rgba(255, 108, 100, 0); + } +} diff --git a/apps/st/src/components/button/VerificationResultButton.tsx b/apps/st/src/components/button/VerificationResultButton.tsx index 580f6d0d0..73eab7f98 100644 --- a/apps/st/src/components/button/VerificationResultButton.tsx +++ b/apps/st/src/components/button/VerificationResultButton.tsx @@ -1,16 +1,34 @@ import { Button } from "@common/components"; +import "./VerificationResultButton.css"; + interface VerificationResultButtonProps { VerificationResultButtonFunc: () => void; + isVerificationFailed?: boolean; } -const VerificationResultButton = ({ VerificationResultButtonFunc }: VerificationResultButtonProps) => { +const VerificationResultButton = ({ + VerificationResultButtonFunc, + isVerificationFailed = false, +}: VerificationResultButtonProps) => { return ( - + + + {isVerificationFailed && ( + + )} + ); }; -export default VerificationResultButton; \ No newline at end of file +export default VerificationResultButton; diff --git a/apps/st/src/components/css/DataGrid.css b/apps/st/src/components/css/DataGrid.css index d4b5429f8..2f3519372 100644 --- a/apps/st/src/components/css/DataGrid.css +++ b/apps/st/src/components/css/DataGrid.css @@ -166,6 +166,30 @@ @apply !bg-white !text-[#333]; } + /* 하수관로 검토: CURR 구분 이후 미입력 — OLD 있음(노랑) / OLD 없음(하늘) */ + .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-warn, + .rg-grid .rg-body .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-warn, + .rg-grid .rg-fixed-body .rg-table .rg-data-row td.rg-cell-readonly.rg-cell-review-curr-empty-warn, + .rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-warn, + .rg-grid .rg-body .rg-data-cell.rg-cell-review-curr-empty-warn.rg-cell-review-curr-empty-warn, + .rg-grid .rg-fixed-body .rg-data-cell.rg-cell-review-curr-empty-warn.rg-cell-review-curr-empty-warn, + .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-warn, + .rg-grid .rg-body .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-warn, + .rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-warn { + @apply !bg-[#feee85] !text-[#333]; + } + .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-new, + .rg-grid .rg-body .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-new, + .rg-grid .rg-fixed-body .rg-table .rg-data-row td.rg-cell-readonly.rg-cell-review-curr-empty-new, + .rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-new, + .rg-grid .rg-body .rg-data-cell.rg-cell-review-curr-empty-new.rg-cell-review-curr-empty-new, + .rg-grid .rg-fixed-body .rg-data-cell.rg-cell-review-curr-empty-new.rg-cell-review-curr-empty-new, + .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-new, + .rg-grid .rg-body .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-new, + .rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-new { + @apply !bg-[#bae6fd] !text-[#333]; + } + /* * 검증 오류 셀(rg-cell-verify-error / -imr) 최우선 적용. * 클래스 이중화로 specificity를 한 단계 올려 동일 specificity의 다른 셀 클래스 diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index 0cdd2bf94..7403df3b8 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -39,10 +39,10 @@ function mergeCellStyleCallbackResult( return baseStyleName ? { styleName: baseStyleName } : {}; } if (typeof extra === "string") { - const styleName = [extra, baseStyleName].filter(Boolean).join(" "); + const styleName = [baseStyleName, extra].filter(Boolean).join(" "); return styleName ? { styleName } : {}; } - const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" "); + const styleName = [baseStyleName, extra.styleName].filter(Boolean).join(" "); return { ...extra, ...(styleName ? { styleName } : {}), @@ -812,13 +812,24 @@ useEffect(() => { useLayoutEffect(() => { const gv = gridRef.current?.gridView; if (!gv || !cellStyleCallback) return; - applyCellStyleCallbacksToColumns( - gv, - columns, - rowDataRef, - cellStyleCallbackRef, - ); - (gv as { refresh?: () => void }).refresh?.(); + + const apply = () => { + applyCellStyleCallbacksToColumns( + gv, + columns, + rowDataRef, + cellStyleCallbackRef, + ); + (gv as { refresh?: () => void }).refresh?.(); + }; + + apply(); + // footer setColumns / setColumnLayout 비동기 처리 이후 styleCallback 재적용 + const timerId = window.setTimeout(apply, 300); + + return () => { + window.clearTimeout(timerId); + }; }, [cellStyleCallback, columns, rowData]); const { setMessage: alert } = useAlertStore((state) => state); @@ -1416,6 +1427,13 @@ useEffect(() => { | ((layout: unknown) => void) | undefined )?.(safeLayout); + applyCellStyleCallbacksToColumns( + gridRef.current.gridView, + columns, + rowDataRef, + cellStyleCallbackRef, + ); + gridRef.current.gridView.refresh?.(); } catch (e) { console.warn("setColumnLayout skipped due to invalid columns:", e); } @@ -1482,16 +1500,16 @@ useEffect(() => { col, ), ); + applyFooterSettings(currentGridView); + if (hasFooterUserSpans) { + currentGridView.setColumnLayout?.(columnLayout); + } applyCellStyleCallbacksToColumns( currentGridView, columns, rowDataRef, cellStyleCallbackRef, ); - applyFooterSettings(currentGridView); - if (hasFooterUserSpans) { - currentGridView.setColumnLayout?.(columnLayout); - } // setColumns 호출 후 footer 배열의 valueCallback을 다시 설정 scheduleFooterTask(() => { @@ -1535,16 +1553,16 @@ useEffect(() => { ); } }); + applyFooterSettings(liveGridView); + if (hasFooterUserSpans) { + liveGridView.setColumnLayout?.(columnLayout); + } applyCellStyleCallbacksToColumns( liveGridView, columns, rowDataRef, cellStyleCallbackRef, ); - applyFooterSettings(liveGridView); - if (hasFooterUserSpans) { - liveGridView.setColumnLayout?.(columnLayout); - } liveGridView.refresh?.(); }, 50); } catch (e) { diff --git a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx index 6304f1f9c..ca9872612 100644 --- a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx @@ -38,6 +38,7 @@ import type { import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { doList } from "@/components/util/column/input/DoList"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; import { statisticsInputExcretaTreatmentFacilityApi } from "@/entities/statistics/api/input/statistics-input-excreta-treatment-facility-service"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; @@ -59,6 +60,8 @@ import type { SewerageStatusInfo, } from "@/entities/statistics/model/statistics-interface"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAdminStore } from "@/store/admin-store"; @@ -325,6 +328,7 @@ const ExcretaTreatmentFacilityInp = () => { const [hasSearched, setHasSearched] = useState(false); const [statusInfo, setStatusInfo] = useState(null); const [statsCd, setStatsCd] = useState(""); + const [isVerificationFailed, setIsVerificationFailed] = useState(false); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [isLoading, setIsLoading] = useState(false); const [sggOptions, setSggOptions] = useState([]); @@ -454,11 +458,17 @@ const ExcretaTreatmentFacilityInp = () => { excretaTreatmentFacilityList, } = response.data; const list = excretaTreatmentFacilityList ?? []; + const gridRows = mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm); - setRowData(mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm)); + setRowData(gridRows); setStatusInfo(nextStatusInfo); setStatsCd(commonInfo.statsCd); - setIsNotApplicable(false); + setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo)); + if (overrides?.mode !== "PRE") { + setIsNotApplicable(gridRows.length === 0); + } else if (gridRows.length > 0) { + setIsNotApplicable(false); + } setSearchLoaded(true); return true; }; @@ -512,6 +522,7 @@ const ExcretaTreatmentFacilityInp = () => { setHasSearched(false); setStatusInfo(null); setStatsCd(""); + setIsVerificationFailed(false); setIsNotApplicable(false); setInputPeriodEnterable(true); setSggOptions([]); @@ -974,10 +985,18 @@ const ExcretaTreatmentFacilityInp = () => { }; })(); + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.EXCRETA_TREATMENT_FACILITY, + onAutoSearch: (values) => handleSearch(values), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx index 946e17aa3..7a365dae9 100644 --- a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx @@ -3,6 +3,8 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useAlertStore, useGlobalProgressStore } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; 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"; @@ -53,6 +55,7 @@ import { PrivateSewageDesigStatusInpColumn } from "@/components/util/column/inpu import { SepticTankStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SepticTankStatusInpColumn"; import { SewageTreatFactStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SewageTreatFactStatusInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { useAdminStore } from "@/store/admin-store"; const PrivateSewageFacilityInpTabList = [ @@ -338,6 +341,9 @@ const PrivateSewageFacilityInp = () => { Map >(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); @@ -462,6 +468,9 @@ const PrivateSewageFacilityInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); return true; @@ -487,6 +496,9 @@ const PrivateSewageFacilityInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); return true; @@ -512,6 +524,9 @@ const PrivateSewageFacilityInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); return true; @@ -528,6 +543,7 @@ const PrivateSewageFacilityInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; @@ -539,7 +555,10 @@ const PrivateSewageFacilityInp = () => { periodEnterableRef.current = true; }; - const handleSearch = async (values: SearchFormValues) => { + const handleSearch = async ( + values: SearchFormValues, + opts?: { forcedTabKey?: string }, + ) => { if (!values.sido || values.sido === "ALL") { alert("시도를 선택해주세요."); return; @@ -566,11 +585,17 @@ const PrivateSewageFacilityInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); } + if (opts?.forcedTabKey) { + activeTabRef.current = opts.forcedTabKey; + setActiveTab(opts.forcedTabKey); + } + setIsLoading(true); searchDoneRef.current = true; setHasSearched(true); @@ -578,7 +603,7 @@ const PrivateSewageFacilityInp = () => { searchParamRef.current = searchParam; await loadActiveTabData( - activeTabRef.current, + opts?.forcedTabKey ?? activeTabRef.current, searchParam, values, !yearChangedFromLastSearch, @@ -1131,10 +1156,23 @@ const PrivateSewageFacilityInp = () => { } }; + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.PRIVATE_SEWAGE_FACILITY, + onPending: (pending) => { + activeTabRef.current = pending.tabKey; + setActiveTab(pending.tabKey); + }, + onAutoSearch: (values, tabKey) => + handleSearch(values, { forcedTabKey: tabKey }), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx index 3bcb3364b..8b24a86eb 100644 --- a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx @@ -3,6 +3,8 @@ import { useEffect, useRef, useState, type Dispatch, type SetStateAction } from import { useConfirm } from '@frontend-opensource/use-react-hooks'; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from '@/shared/lib'; +import { STATISTICS_INPUT_URL_PATH } from '@/shared/lib/stats-cd-input-navigation'; +import { useStatisticsInputPendingNavigation } from '@/shared/lib/use-statistics-input-pending-navigation'; 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'; @@ -46,6 +48,7 @@ import type { SearchFormValues } from '@/components/type/SearchForm.type'; import { doList } from '@/components/util/column/input/DoList'; import { PublicSewageFactInpColumnList } from '@/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactInpColumn'; import { toGridRows } from '@/components/util/grid/gridFunction'; +import { patchVerificationFailedByTab } from '@/components/util/input/verification-failed-flag'; import { useAdminStore } from '@/store/admin-store'; type GridRowWithVerification = Record & { @@ -165,6 +168,9 @@ const PublicSewageFacilityInp = () => { const [lastSearchedValues, setLastSearchedValues] = useState(null); const [tabStatusInfoMap, setTabStatusInfoMap] = useState>(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); /** 탭키별 `engColNm -> columnGuide` 캐시 */ const [columnGuidesByTab, setColumnGuidesByTab] = useState>>({}); const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState>(() => new Map()); @@ -685,8 +691,10 @@ const PublicSewageFacilityInp = () => { if (list.length === 0) { setShowIsNotApplicable(true); + setIsNotApplicable(true); } else { setShowIsNotApplicable(false); + setIsNotApplicable(false); } const gridRows = toGridRows(convertedData); @@ -700,6 +708,9 @@ const PublicSewageFacilityInp = () => { next.set(tabKey, statsCd); return next; }); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); setRowDataMap((prev) => { const next = new Map(prev); next.set('ST00060001', gridRows); @@ -744,6 +755,9 @@ const PublicSewageFacilityInp = () => { next.set(tabKey, statsCd); return next; }); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, response.data.commonInfo), + ); setRowDataMap((prev) => { const next = new Map(prev); next.set('MainFacility', leftGridRows); @@ -874,7 +888,10 @@ const PublicSewageFacilityInp = () => { } }; - const handleSearch = async (values: SearchFormValues) => { + const handleSearch = async ( + values: SearchFormValues, + opts?: { forcedTabKey?: string }, + ) => { if (!values.sido || values.sido === 'ALL') { alert('시도를 선택해주세요.'); return; @@ -902,6 +919,7 @@ const PublicSewageFacilityInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); @@ -910,16 +928,17 @@ const PublicSewageFacilityInp = () => { setLastSearchedValues(values); searchDoneRef.current = true; setHasSearched(true); - setIsNotApplicableByTab((prev) => { - const next = new Map(prev); - next.set('ST00060001', false); - return next; - }); setIsLoading(true); const param = buildPublicSewageFacilityParams(values); searchParamRef.current = param; - const tabKeyToLoad = yearChangedFromLastSearch ? firstTabKey : activeTabRef.current; + const tabKeyToLoad = + opts?.forcedTabKey ?? + (yearChangedFromLastSearch ? firstTabKey : activeTabRef.current); + if (opts?.forcedTabKey) { + activeTabRef.current = opts.forcedTabKey; + setActiveTab(opts.forcedTabKey); + } await loadPublicSewageFacilityActiveTabData(tabKeyToLoad, param, values); } catch { alert('데이터 조회 중 오류가 발생했습니다.'); @@ -963,6 +982,7 @@ const PublicSewageFacilityInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setInputPeriodEnterable(true); @@ -1138,10 +1158,23 @@ const PublicSewageFacilityInp = () => { }; })(); + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.PUBLIC_SEWAGE_FACILITY, + onPending: (pending) => { + activeTabRef.current = pending.tabKey; + setActiveTab(pending.tabKey); + }, + onAutoSearch: (values, tabKey) => + handleSearch(values, { forcedTabKey: tabKey }), + }); + return ( <>
{ {!isMainFacilityTab && ( )} {showEnterableToolbarButtons && !isMainFacilityTab && ( diff --git a/apps/st/src/components/statistics/input/PumpingStationInp.tsx b/apps/st/src/components/statistics/input/PumpingStationInp.tsx index fe35e2a63..67d359d86 100644 --- a/apps/st/src/components/statistics/input/PumpingStationInp.tsx +++ b/apps/st/src/components/statistics/input/PumpingStationInp.tsx @@ -38,6 +38,7 @@ import type { import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { doList } from "@/components/util/column/input/DoList"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; import { statisticsInputPumpingStationApi } from "@/entities/statistics/api/input/statistics-input-pumping-station-service"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; @@ -58,6 +59,8 @@ import type { SewerageStatusInfo, } from "@/entities/statistics/model/statistics-interface"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAdminStore } from "@/store/admin-store"; @@ -320,6 +323,7 @@ const PumpingStationInp = () => { const [hasSearched, setHasSearched] = useState(false); const [statusInfo, setStatusInfo] = useState(null); const [statsCd, setStatsCd] = useState(""); + const [isVerificationFailed, setIsVerificationFailed] = useState(false); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [isLoading, setIsLoading] = useState(false); const [sggOptions, setSggOptions] = useState([]); @@ -445,11 +449,18 @@ const PumpingStationInp = () => { const { commonInfo, statusInfo: nextStatusInfo, pumpingStationList } = response.data; const list = pumpingStationList ?? []; + const gridRows = mapPumpingStationListToGridRows(list, commonInfo.ctpvNm); - setRowData(mapPumpingStationListToGridRows(list, commonInfo.ctpvNm)); + setPumpingStationDeleteList([]); + setRowData(gridRows); setStatusInfo(nextStatusInfo); setStatsCd(commonInfo.statsCd); - setIsNotApplicable(false); + setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo)); + if (overrides?.mode !== "PRE") { + setIsNotApplicable(gridRows.length === 0); + } else if (gridRows.length > 0) { + setIsNotApplicable(false); + } setSearchLoaded(true); return true; }; @@ -503,6 +514,7 @@ const PumpingStationInp = () => { setHasSearched(false); setStatusInfo(null); setStatsCd(""); + setIsVerificationFailed(false); setIsNotApplicable(false); setInputPeriodEnterable(true); setSggOptions([]); @@ -957,10 +969,18 @@ const PumpingStationInp = () => { }; })(); + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.PUMPING_STATION, + onAutoSearch: (values) => handleSearch(values), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx index f9f722b4f..aeb2b712f 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx @@ -35,6 +35,7 @@ import { PrivateSewageTreatRelatedIndustryInpColumn } from "@/components/util/co import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn"; import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { statisticsInputSewageExcretaRelatedApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-related-service"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; @@ -61,6 +62,8 @@ import type { } from "@/entities/statistics/model/statistics-interface"; import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAdminStore } from "@/store/admin-store"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -613,6 +616,9 @@ const SewageExcretaRelatedInp = () => { const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [loadedTabKeys, setLoadedTabKeys] = useState>(new Set()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [isNotApplicableByTab, setIsNotApplicableByTab] = useState>( () => new Map(), ); @@ -823,7 +829,16 @@ const SewageExcretaRelatedInp = () => { const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param; const fromPreYear = overrides?.mode === "PRE"; - setIsNotApplicableForTab(tabKey, false); + const applyNotApplicableForLoadedTab = ( + gridRows: readonly Record[], + ) => { + if (!NOT_APPLICABLE_TAB_KEYS.has(tabKey)) return; + if (!fromPreYear) { + setIsNotApplicableForTab(tabKey, gridRows.length === 0); + } else if (gridRows.length > 0) { + setIsNotApplicableForTab(tabKey, false); + } + }; if (tabKey === PublicInstitutionSewerIndustryInpColumn.tabKey) { const response = @@ -852,6 +867,9 @@ const SewageExcretaRelatedInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab( tabKey, @@ -859,6 +877,7 @@ const SewageExcretaRelatedInp = () => { gridRows.length > 0, searchContext, ); + applyNotApplicableForLoadedTab(gridRows); markTabLoaded(tabKey); return true; } @@ -891,6 +910,9 @@ const SewageExcretaRelatedInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab( tabKey, @@ -898,6 +920,7 @@ const SewageExcretaRelatedInp = () => { gridRows.length > 0, searchContext, ); + applyNotApplicableForLoadedTab(gridRows); markTabLoaded(tabKey); return true; } @@ -918,6 +941,9 @@ const SewageExcretaRelatedInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab( tabKey, @@ -949,6 +975,9 @@ const SewageExcretaRelatedInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab( tabKey, @@ -979,6 +1008,7 @@ const SewageExcretaRelatedInp = () => { loadedTabKeysRef.current.clear(); setLoadedTabKeys(new Set()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setIsNotApplicableByTab(new Map()); setDeleteListByTab(new Map()); clearArticleOgnzSnTracking( @@ -990,7 +1020,10 @@ const SewageExcretaRelatedInp = () => { setVerificationHistoryOpen(false); }; - const handleSearch = async (values: SearchFormValues) => { + const handleSearch = async ( + values: SearchFormValues, + opts?: { forcedTabKey?: string }, + ) => { if (!values.sido || values.sido === "ALL") { alert("시도를 선택해주세요."); return; @@ -1025,6 +1058,7 @@ const SewageExcretaRelatedInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setIsNotApplicableByTab(new Map()); setDeleteListByTab(new Map()); @@ -1054,8 +1088,13 @@ const SewageExcretaRelatedInp = () => { return; } + if (opts?.forcedTabKey) { + activeTabRef.current = opts.forcedTabKey; + setActiveTab(opts.forcedTabKey); + } + const ok = await loadActiveTabData( - activeTabRef.current, + opts?.forcedTabKey ?? activeTabRef.current, searchParam, values, !yearChangedFromLastSearch, @@ -1894,10 +1933,23 @@ const SewageExcretaRelatedInp = () => { }; })(); + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_RELATED, + onPending: (pending) => { + activeTabRef.current = pending.tabKey; + setActiveTab(pending.tabKey); + }, + onAutoSearch: (values, tabKey) => + handleSearch(values, { forcedTabKey: tabKey }), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx index 5a5519dee..9244da4f3 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx @@ -49,6 +49,7 @@ import { SewageSlurgeProcessingFacStatusInpColumn } from "@/components/util/colu import { SlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn"; import { doList } from "@/components/util/column/input/DoList"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { statisticsInputSewageExcretaSlurgeApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; @@ -70,6 +71,8 @@ import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statisti import type { ApiResponse, cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sgg } from "@/entities/statistics/model/statistics-interface"; import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAdminStore } from "@/store/admin-store"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -660,6 +663,9 @@ const SewageExcretaSlurgeInp = () => { Map >(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); @@ -895,7 +901,15 @@ const SewageExcretaSlurgeInp = () => { const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param; const fromPreYear = overrides?.mode === "PRE"; - setIsNotApplicableForTab(tabKey, false); + const applyNotApplicableForLoadedTab = ( + gridRows: readonly Record[], + ) => { + if (!fromPreYear) { + setIsNotApplicableForTab(tabKey, gridRows.length === 0); + } else if (gridRows.length > 0) { + setIsNotApplicableForTab(tabKey, false); + } + }; if (tabKey === SlurgeProcessingFacStatusInpColumn.tabKey) { const response = @@ -914,6 +928,9 @@ const SewageExcretaSlurgeInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab( tabKey, @@ -921,6 +938,7 @@ const SewageExcretaSlurgeInp = () => { gridRows.length > 0, searchContext, ); + applyNotApplicableForLoadedTab(gridRows); markTabLoaded(tabKey); return true; } @@ -947,6 +965,9 @@ const SewageExcretaSlurgeInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab( tabKey, @@ -954,6 +975,7 @@ const SewageExcretaSlurgeInp = () => { gridRows.length > 0, searchContext, ); + applyNotApplicableForLoadedTab(gridRows); markTabLoaded(tabKey); return true; } @@ -978,6 +1000,9 @@ const SewageExcretaSlurgeInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab( tabKey, @@ -985,6 +1010,7 @@ const SewageExcretaSlurgeInp = () => { gridRows.length > 0, searchContext, ); + applyNotApplicableForLoadedTab(gridRows); markTabLoaded(tabKey); return true; } @@ -1014,6 +1040,9 @@ const SewageExcretaSlurgeInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab( tabKey, @@ -1021,6 +1050,7 @@ const SewageExcretaSlurgeInp = () => { gridRows.length > 0, searchContext, ); + applyNotApplicableForLoadedTab(gridRows); markTabLoaded(tabKey); return true; } @@ -1033,6 +1063,7 @@ const SewageExcretaSlurgeInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; @@ -1056,7 +1087,10 @@ const SewageExcretaSlurgeInp = () => { sggOptionsSidoRef.current = null; }; - const handleSearch = async (values: SearchFormValues) => { + const handleSearch = async ( + values: SearchFormValues, + opts?: { forcedTabKey?: string }, + ) => { if (!values.sido || values.sido === "ALL") { alert("시도를 선택해주세요."); return; @@ -1087,6 +1121,7 @@ const SewageExcretaSlurgeInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); loadedTabKeysRef.current.clear(); setLoadedTabKeys(new Set()); @@ -1113,13 +1148,20 @@ const SewageExcretaSlurgeInp = () => { return; } + if (opts?.forcedTabKey) { + activeTabRef.current = opts.forcedTabKey; + setActiveTab(opts.forcedTabKey); + } + const ok = await loadActiveTabData( - activeTabRef.current, + opts?.forcedTabKey ?? activeTabRef.current, searchParam, values, !yearChangedFromLastSearch, true, - buildEmptyDataAlertOverrides(activeTabRef.current), + buildEmptyDataAlertOverrides( + opts?.forcedTabKey ?? activeTabRef.current, + ), ); if (!ok) return; } catch { @@ -1988,10 +2030,23 @@ const SewageExcretaSlurgeInp = () => { }; })(); + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_SLURGE, + onPending: (pending) => { + activeTabRef.current = pending.tabKey; + setActiveTab(pending.tabKey); + }, + onAutoSearch: (values, tabKey) => + handleSearch(values, { forcedTabKey: tabKey }), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/SewagePipeInp.tsx b/apps/st/src/components/statistics/input/SewagePipeInp.tsx index 37468b59d..a042a51f8 100644 --- a/apps/st/src/components/statistics/input/SewagePipeInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeInp.tsx @@ -69,6 +69,9 @@ import { PipeTypeStatusInpColumn } from "@/components/util/column/input/sewagePi import { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn"; import { SewagePipeInpColumnInfoList } from "@/components/util/column/input/sewagePipe/SewagePipeInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { + patchVerificationFailedByTab, +} from "@/components/util/input/verification-failed-flag"; import { useAdminStore } from "@/store/admin-store"; import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store"; import { useTabStore } from "@/store/tab-store"; @@ -360,6 +363,9 @@ const SewagePipeInp = () => { const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false); const [statsCd, setStatsCd] = useState(''); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [periodCheckList, setPeriodCheckList] = useState([]); /** 탭키별 `engColNm -> columnGuide` 캐시 */ @@ -470,6 +476,7 @@ const SewagePipeInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setLastSearchedYear(null); setLastSearchedValues(null); setHasSearched(false); @@ -582,6 +589,9 @@ const SewagePipeInp = () => { 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)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd); if (updateLastSearchedValues) setLastSearchedValues(values); commitToolbarSnapshotForLoadedTab( @@ -623,6 +633,9 @@ const SewagePipeInp = () => { applyRowMap((m) => m.set('ST00020002', toGridRows(convertedData))); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd); if (updateLastSearchedValues) setLastSearchedValues(values); commitToolbarSnapshotForLoadedTab( @@ -646,6 +659,9 @@ const SewagePipeInp = () => { applyRowMap((m) => m.set('ST00020003', toGridRows(convertedData))); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd); if (updateLastSearchedValues) setLastSearchedValues(values); commitToolbarSnapshotForLoadedTab( @@ -670,6 +686,9 @@ const SewagePipeInp = () => { 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)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd); if (updateLastSearchedValues) setLastSearchedValues(values); commitToolbarSnapshotForLoadedTab( @@ -693,6 +712,9 @@ const SewagePipeInp = () => { 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)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd); if (updateLastSearchedValues) setLastSearchedValues(values); commitToolbarSnapshotForLoadedTab( @@ -723,6 +745,9 @@ const SewagePipeInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd); if (updateLastSearchedValues) setLastSearchedValues(values); commitToolbarSnapshotForLoadedTab( @@ -774,6 +799,7 @@ const SewagePipeInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setStatsCd(""); setToolbarSnapshotByTab(new Map()); } @@ -1896,6 +1922,9 @@ const SewagePipeInp = () => {
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx index 29fb142d3..35d2480d5 100644 --- a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx @@ -56,6 +56,7 @@ import { doList } from "@/components/util/column/input/DoList"; import { PipeDredgingStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/PipeDredgingStatusInpColumn"; import { RepairStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/RepairStatusInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { useAdminStore } from "@/store/admin-store"; import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store"; import { useTabStore } from "@/store/tab-store"; @@ -291,6 +292,9 @@ const SewagePipeMaintainInp = () => { Map >(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); @@ -441,6 +445,9 @@ const SewagePipeMaintainInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) { setLastSearchedValues(searchContext); } @@ -474,6 +481,9 @@ const SewagePipeMaintainInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) { setLastSearchedValues(searchContext); } @@ -868,6 +878,7 @@ const SewagePipeMaintainInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setActiveTab(firstTabKey); setLastSearchedValues(null); @@ -912,6 +923,7 @@ const SewagePipeMaintainInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); @@ -1109,6 +1121,9 @@ const SewagePipeMaintainInp = () => {
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx index 21a498c4e..f0ed5b9cd 100644 --- a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx +++ b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx @@ -42,6 +42,7 @@ import { doList } from "@/components/util/column/input/DoList"; import { RainfallPumpStationInpColumn } from "@/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn"; import { SewageReservoirInpColumn } from "@/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { formatCompactYmdForGrid, normalizeOxYnValue, @@ -69,6 +70,8 @@ import type { SewerageStatusInfo, } from "@/entities/statistics/model/statistics-interface"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAdminStore } from "@/store/admin-store"; @@ -926,6 +929,9 @@ const SewageReservoirPumpInp = () => { Map >(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false); @@ -1046,8 +1052,7 @@ const SewageReservoirPumpInp = () => { updateLastSearchedValues: boolean, overrides?: { mode?: string }, ): Promise => { - // 서버 데이터로 새로 불러오는 시점이므로 현재 탭의 "해당없음" 체크는 초기화 - setIsNotApplicableForTab(tabKey, false); + const fromPreYear = overrides?.mode === "PRE"; if (tabKey === SEWAGE_RESERVOIR_STATS_CD) { const response = await statisticsInputSewageReservoirPumpApi.getSewageReservoirList( @@ -1072,6 +1077,9 @@ const SewageReservoirPumpInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) { setLastSearchedValues(values); } @@ -1081,6 +1089,11 @@ const SewageReservoirPumpInp = () => { gridRows.length > 0, values, ); + if (!fromPreYear) { + setIsNotApplicableForTab(tabKey, gridRows.length === 0); + } else if (gridRows.length > 0) { + setIsNotApplicableForTab(tabKey, false); + } loadedTabKeysRef.current.add(tabKey); return true; } @@ -1109,6 +1122,9 @@ const SewageReservoirPumpInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) { setLastSearchedValues(values); } @@ -1118,6 +1134,11 @@ const SewageReservoirPumpInp = () => { gridRows.length > 0, values, ); + if (!fromPreYear) { + setIsNotApplicableForTab(tabKey, gridRows.length === 0); + } else if (gridRows.length > 0) { + setIsNotApplicableForTab(tabKey, false); + } loadedTabKeysRef.current.add(tabKey); return true; } @@ -1170,6 +1191,7 @@ const SewageReservoirPumpInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setLastSearchedValues(null); setSggOptions([]); @@ -1183,7 +1205,10 @@ const SewageReservoirPumpInp = () => { setActiveTab(firstTabKey); }; - const handleSearch = async (values: SearchFormValues) => { + const handleSearch = async ( + values: SearchFormValues, + opts?: { forcedTabKey?: string }, + ) => { if (!values.sido || values.sido === "ALL") { alert("시도를 선택해주세요."); return; @@ -1216,6 +1241,7 @@ const SewageReservoirPumpInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); loadedTabKeysRef.current.clear(); setActiveTab(firstTabKey); @@ -1233,8 +1259,13 @@ const SewageReservoirPumpInp = () => { return; } - const tabKeyToLoad = yearChangedFromLastSearch ? firstTabKey : activeTabRef.current; - if (yearChangedFromLastSearch) { + const tabKeyToLoad = + opts?.forcedTabKey ?? + (yearChangedFromLastSearch ? firstTabKey : activeTabRef.current); + if (opts?.forcedTabKey) { + activeTabRef.current = opts.forcedTabKey; + setActiveTab(opts.forcedTabKey); + } else if (yearChangedFromLastSearch) { activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); } @@ -1484,10 +1515,23 @@ const SewageReservoirPumpInp = () => { }; })(); + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_RESERVOIR_PUMP, + onPending: (pending) => { + activeTabRef.current = pending.tabKey; + setActiveTab(pending.tabKey); + }, + onAutoSearch: (values, tabKey) => + handleSearch(values, { forcedTabKey: tabKey }), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx index 939e219d0..6733d0a0b 100644 --- a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx @@ -3,6 +3,8 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useAlertStore, useGlobalProgressStore } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; 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"; @@ -58,6 +60,7 @@ import { SewerageChargeInpColumn } from "@/components/util/column/input/sewerage import { SewerageFinanceSettleInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageFinanceSettleInpColumn"; import { SewerageUsageFeeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageUsageFeeInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { useAdminStore } from "@/store/admin-store"; const SewerageFinanceInpTabList = [ @@ -367,6 +370,9 @@ const SewerageFinanceInp = () => { Map >(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); @@ -482,6 +488,9 @@ const SewerageFinanceInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); return true; @@ -505,6 +514,9 @@ const SewerageFinanceInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); return true; @@ -528,6 +540,9 @@ const SewerageFinanceInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); return true; @@ -551,6 +566,9 @@ const SewerageFinanceInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); return true; @@ -567,6 +585,7 @@ const SewerageFinanceInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; @@ -578,7 +597,10 @@ const SewerageFinanceInp = () => { periodEnterableRef.current = true; }; - const handleSearch = async (values: SearchFormValues) => { + const handleSearch = async ( + values: SearchFormValues, + opts?: { forcedTabKey?: string }, + ) => { if (!values.sido || values.sido === "ALL") { alert("시도를 선택해주세요."); return; @@ -605,11 +627,17 @@ const SewerageFinanceInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); } + if (opts?.forcedTabKey) { + activeTabRef.current = opts.forcedTabKey; + setActiveTab(opts.forcedTabKey); + } + setIsLoading(true); searchDoneRef.current = true; setHasSearched(true); @@ -617,7 +645,7 @@ const SewerageFinanceInp = () => { searchParamRef.current = searchParam; await loadActiveTabData( - activeTabRef.current, + opts?.forcedTabKey ?? activeTabRef.current, searchParam, values, !yearChangedFromLastSearch, @@ -1194,10 +1222,23 @@ const SewerageFinanceInp = () => { } }; + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_FINANCE, + onPending: (pending) => { + activeTabRef.current = pending.tabKey; + setActiveTab(pending.tabKey); + }, + onAutoSearch: (values, tabKey) => + handleSearch(values, { forcedTabKey: tabKey }), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 50435dbb7..fa4b09ee7 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -49,6 +49,7 @@ import { doList } from "@/components/util/column/input/DoList"; import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn"; import { getFooterSummary } from "@/components/util/grid/footerSummary"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag"; import { useAdminStore } from "@/store/admin-store"; import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store"; import { useTabStore } from "@/store/tab-store"; @@ -131,6 +132,7 @@ const SeweragePenetrationRateInp = () => { /** 예외제출 버튼으로 연 다이얼로그인지 (확인 시 executeSubmit 대신 SubmitExceptionFunc 경로) */ const [isExceptionSubmitFlow, setIsExceptionSubmitFlow] = useState(false); const [statsCd, setStatsCd] = useState(''); + const [isVerificationFailed, setIsVerificationFailed] = useState(false); /** `engColNm` → `columnGuide` (헤더 hover) */ const [columnGuidesByEngCol, setColumnGuidesByEngCol] = useState>({}); const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null); @@ -240,6 +242,7 @@ const SeweragePenetrationRateInp = () => { setLastSearchedValues(null); setFooterFromApi(null); setVerificationSearchDefaults(null); + setIsVerificationFailed(false); setSearchFormRemountKey((k) => k + 1); }; @@ -275,6 +278,7 @@ const SeweragePenetrationRateInp = () => { : null ); setStatsCd(commonInfo.statsCd); + setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo)); if (statusInfo && statusInfo.laeSbmsnSttsCd) { if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) { setIsEnterable(false); @@ -779,6 +783,7 @@ const SeweragePenetrationRateInp = () => {
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx index abd48918a..bd1efe8a9 100644 --- a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx @@ -23,6 +23,7 @@ import { doList } from "@/components/util/column/input/DoList"; import { ComplaintInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ComplaintInpColumn"; import { ResidentFriendlyFactInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ResidentFriendlyFactInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag"; import { statisticsInputSewerageServiceLevelApi } from "@/entities/statistics/api/input/statistics-input-sewerage-service-level-service"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; @@ -44,6 +45,8 @@ import type { } from "@/entities/statistics/model/statistics-interface"; import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAdminStore } from "@/store/admin-store"; @@ -300,6 +303,9 @@ const SewerageServiceLevelInp = () => { Map >(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); @@ -425,6 +431,9 @@ const SewerageServiceLevelInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); setSearchLoaded(true); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); @@ -457,9 +466,17 @@ const SewerageServiceLevelInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); setSearchLoaded(true); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); + if (!fromPreYear) { + setIsNotApplicable(gridRows.length === 0); + } else if (gridRows.length > 0) { + setIsNotApplicable(false); + } return true; } @@ -474,6 +491,7 @@ const SewerageServiceLevelInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; @@ -488,7 +506,10 @@ const SewerageServiceLevelInp = () => { publicSewageFacilitiesAvailableRef.current = false; }; - const handleSearch = async (values: SearchFormValues) => { + const handleSearch = async ( + values: SearchFormValues, + opts?: { forcedTabKey?: string }, + ) => { if (!values.sido || values.sido === "ALL") { alert("시도를 선택해주세요."); return; @@ -515,12 +536,20 @@ const SewerageServiceLevelInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setIsNotApplicable(false); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); } + if (opts?.forcedTabKey) { + activeTabRef.current = opts.forcedTabKey; + setActiveTab(opts.forcedTabKey); + } + + const tabForLoad = opts?.forcedTabKey ?? activeTabRef.current; + setIsLoading(true); searchDoneRef.current = true; setHasSearched(true); @@ -528,12 +557,12 @@ const SewerageServiceLevelInp = () => { searchParamRef.current = searchParam; await loadActiveTabData( - activeTabRef.current, + tabForLoad, searchParam, values, !yearChangedFromLastSearch, true, - buildEmptyDataAlertOverrides(activeTabRef.current), + buildEmptyDataAlertOverrides(tabForLoad), ); } catch { alert("데이터 조회 중 오류가 발생했습니다."); @@ -1091,10 +1120,23 @@ const SewerageServiceLevelInp = () => { } }; + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_SERVICE_LEVEL, + onPending: (pending) => { + activeTabRef.current = pending.tabKey; + setActiveTab(pending.tabKey); + }, + onAutoSearch: (values, tabKey) => + handleSearch(values, { forcedTabKey: tabKey }), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/input/WaterReuseInp.tsx b/apps/st/src/components/statistics/input/WaterReuseInp.tsx index 6b86dbf03..8cb416861 100644 --- a/apps/st/src/components/statistics/input/WaterReuseInp.tsx +++ b/apps/st/src/components/statistics/input/WaterReuseInp.tsx @@ -38,6 +38,9 @@ import { RecycledWaterStatusInpColumn } from "@/components/util/column/input/wat import { TreatWaterReuseBusinessApprStatusInpColumn } from "@/components/util/column/input/waterReuse/TreatWaterReuseBusinessApprStatusInpColumn"; import { TreatWaterReuseDesignConstStatusInpColumn } from "@/components/util/column/input/waterReuse/TreatWaterReuseDesignConstStatusInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +import { + patchVerificationFailedByTab, +} from "@/components/util/input/verification-failed-flag"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; import { statisticsInputWaterReuseApi } from "@/entities/statistics/api/input/statistics-input-water-reuse-service"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; @@ -71,6 +74,8 @@ import type { } from "@/entities/statistics/model/statistics-interface"; import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAdminStore } from "@/store/admin-store"; @@ -533,6 +538,9 @@ const WaterReuseInp = () => { Map >(new Map()); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); + const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< + Map + >(new Map()); const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); @@ -677,8 +685,19 @@ const WaterReuseInp = () => { overrides?: { mode?: string }, ): Promise => { const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param; + const fromPreYear = overrides?.mode === "PRE"; + + const applyNotApplicableForLoadedTab = ( + gridRows: readonly Record[], + ) => { + if (!NO_APPLICABLE_TAB_KEYS.has(tabKey)) return; + if (!fromPreYear) { + setIsNotApplicableForTab(tabKey, gridRows.length === 0); + } else if (gridRows.length > 0) { + setIsNotApplicableForTab(tabKey, false); + } + }; - setIsNotApplicableForTab(tabKey, false); if (ARTICLE_EDIT_TAB_KEYS.has(tabKey)) { setDeleteListByTab((prev) => { const next = new Map(prev); @@ -706,8 +725,12 @@ const WaterReuseInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); + applyNotApplicableForLoadedTab(gridRows); loadedTabKeysRef.current.add(tabKey); return true; } @@ -730,8 +753,12 @@ const WaterReuseInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); + applyNotApplicableForLoadedTab(gridRows); loadedTabKeysRef.current.add(tabKey); return true; } @@ -754,8 +781,12 @@ const WaterReuseInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); + applyNotApplicableForLoadedTab(gridRows); loadedTabKeysRef.current.add(tabKey); return true; } @@ -780,8 +811,12 @@ const WaterReuseInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); + applyNotApplicableForLoadedTab(gridRows); loadedTabKeysRef.current.add(tabKey); return true; } @@ -807,8 +842,12 @@ const WaterReuseInp = () => { }); setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo)); setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd)); + setIsVerificationFailedByTab((prev) => + patchVerificationFailedByTab(prev, tabKey, commonInfo), + ); if (updateLastSearchedValues) setLastSearchedValues(searchContext); commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext); + applyNotApplicableForLoadedTab(gridRows); loadedTabKeysRef.current.add(tabKey); return true; } @@ -862,6 +901,7 @@ const WaterReuseInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; @@ -878,7 +918,10 @@ const WaterReuseInp = () => { sggOptionsSidoRef.current = null; }; - const handleSearch = async (values: SearchFormValues) => { + const handleSearch = async ( + values: SearchFormValues, + opts?: { forcedTabKey?: string }, + ) => { if (!values.sido || values.sido === "ALL") { alert("시도를 선택해주세요."); return; @@ -905,6 +948,7 @@ const WaterReuseInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setTabStatsCdMap(new Map()); + setIsVerificationFailedByTab(new Map()); setToolbarSnapshotByTab(new Map()); setDeleteListByTab(new Map()); loadedTabKeysRef.current.clear(); @@ -912,6 +956,13 @@ const WaterReuseInp = () => { setActiveTab(firstTabKey); } + if (opts?.forcedTabKey) { + activeTabRef.current = opts.forcedTabKey; + setActiveTab(opts.forcedTabKey); + } + + const tabForLoad = opts?.forcedTabKey ?? activeTabRef.current; + setIsLoading(true); searchDoneRef.current = true; setHasSearched(true); @@ -920,13 +971,13 @@ const WaterReuseInp = () => { const loadedSgg = await loadSggOptions(values.sido); sggOptionsSidoRef.current = values.sido; - if (ARTICLE_EDIT_TAB_KEYS.has(activeTabRef.current) && loadedSgg.length === 0) { + if (ARTICLE_EDIT_TAB_KEYS.has(tabForLoad) && loadedSgg.length === 0) { alert("시군구 목록을 불러오지 못했습니다."); return; } await loadActiveTabData( - activeTabRef.current, + tabForLoad, searchParam, values, !yearChangedFromLastSearch, @@ -2009,10 +2060,23 @@ const WaterReuseInp = () => { } }; + const { verificationSearchDefaults, searchFormRemountKey } = + useStatisticsInputPendingNavigation({ + urlPath: STATISTICS_INPUT_URL_PATH.WATER_REUSE, + onPending: (pending) => { + activeTabRef.current = pending.tabKey; + setActiveTab(pending.tabKey); + }, + onAutoSearch: (values, tabKey) => + handleSearch(values, { forcedTabKey: tabKey }), + }); + return ( <>
{
{showEnterableToolbarButtons && ( <> diff --git a/apps/st/src/components/statistics/management/LogHistory.tsx b/apps/st/src/components/statistics/management/LogHistory.tsx index 86c6712d1..365d61b72 100644 --- a/apps/st/src/components/statistics/management/LogHistory.tsx +++ b/apps/st/src/components/statistics/management/LogHistory.tsx @@ -165,6 +165,8 @@ const LogHistory = () => { const logList = logListRaw.map((l) => ({ ...l, sggNm: String(l.sggNm ?? "").trim() || "-", + ctpvNm: String(l.ctpvNm ?? "").trim() || "-", + statsYr: String(l.statsYr ?? "").trim() || "-", })); setRowData(toGridRows(logList)); diff --git a/apps/st/src/components/statistics/management/LogHistoryDetailDialog.tsx b/apps/st/src/components/statistics/management/LogHistoryDetailDialog.tsx index 359eba00d..416543fa1 100644 --- a/apps/st/src/components/statistics/management/LogHistoryDetailDialog.tsx +++ b/apps/st/src/components/statistics/management/LogHistoryDetailDialog.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { GridFitStyle } from "realgrid"; import { @@ -15,17 +15,25 @@ import { } from "@common/components"; import DataGrid from "@/components/module/DataGrid"; -import type { ColumnInfo } from "@/components/type/DataGrid.type"; +import type { + ColumnInfo, + DataCellClickCallbackParams, +} from "@/components/type/DataGrid.type"; import { alignLogDetailRowsToColumns, buildLogHistoryDetailColumnInfo, emptyLogHistoryDetailColumnInfo, + LOG_DETAIL_DETAILS_FIELD, + LOG_DETAIL_DETAILS_RAW_FIELD, + type LogDetailVerificationItem, + prepareLogDetailRowsForGrid, } from "@/components/util/column/management/LogHistoryDetailColumn"; -import { toGridRows } from "@/components/util/grid/gridFunction"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; -import type { StatisticsLogDetailRow } from "@/entities/statistics/model/statistics-interface"; +import { UserApi } from "@/entities/user"; import { useAlertStore } from "@/shared/lib"; +import { LogHistoryDetailsNestedDialog } from "./LogHistoryDetailsNestedDialog"; + export interface LogHistoryDetailDialogProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -45,11 +53,19 @@ export function LogHistoryDetailDialog({ const [columnInfo, setColumnInfo] = useState( emptyLogHistoryDetailColumnInfo, ); + const [userName, setUserName] = useState(""); + const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); + const [selectedDetails, setSelectedDetails] = useState< + LogDetailVerificationItem[] + >([]); useEffect(() => { if (!open) { setRowData([]); setColumnInfo(emptyLogHistoryDetailColumnInfo); + setUserName(""); + setDetailsDialogOpen(false); + setSelectedDetails([]); setIsLoading(false); return; } @@ -57,23 +73,42 @@ export function LogHistoryDetailDialog({ if (!row) return; const sn = row.statsLogSn; + const rgtrId = + row.rgtrId != null && String(row.rgtrId).trim() !== "" + ? String(row.rgtrId).trim() + : ""; let cancelled = false; void (async () => { setIsLoading(true); setRowData([]); + setUserName(""); try { - const response = await statisticsApi.getLogDetail( - sn !== undefined && sn !== null ? String(sn) : "", - ); + const userLookupPromise = + rgtrId !== "" + ? UserApi.findByUserUnqId(rgtrId).catch(() => { + return null; + }) + : Promise.resolve(null); + + const [response, userResponse] = await Promise.all([ + statisticsApi.getLogDetail( + sn !== undefined && sn !== null ? String(sn) : "", + ), + userLookupPromise, + ]); + + if (userResponse) { + setUserName(userResponse.userName ?? ""); + } if (cancelled) return; if (response.status !== "success") { alert( response.message ?? - "로그 상세 정보를 불러오는데 실패했습니다.", + "로그 상세 정보를 불러오는데 실패했습니다.", ); return; } @@ -83,7 +118,7 @@ export function LogHistoryDetailDialog({ : []; const nextColumnInfo = buildLogHistoryDetailColumnInfo(list); const nextRows = alignLogDetailRowsToColumns( - toGridRows(list as StatisticsLogDetailRow[]), + prepareLogDetailRowsForGrid(list), nextColumnInfo, ); setColumnInfo(nextColumnInfo); @@ -102,6 +137,25 @@ export function LogHistoryDetailDialog({ }; }, [open, row, alert]); + const handleDataCellClick = useCallback( + ({ fieldName, row }: DataCellClickCallbackParams) => { + if (fieldName !== LOG_DETAIL_DETAILS_FIELD) return; + + const raw = row[LOG_DETAIL_DETAILS_RAW_FIELD]; + if (raw == null || String(raw).trim() === "") return; + + try { + const parsed = JSON.parse(String(raw)) as unknown; + if (!Array.isArray(parsed) || parsed.length === 0) return; + setSelectedDetails(parsed as LogDetailVerificationItem[]); + setDetailsDialogOpen(true); + } catch { + alert("상세 데이터를 불러오는데 실패했습니다."); + } + }, + [alert], + ); + const gridRemountKey = useMemo( () => columnInfo.columns.map((c) => c.fieldName).join("\u0001"), [columnInfo], @@ -110,13 +164,14 @@ export function LogHistoryDetailDialog({ const showGrid = !isLoading && rowData.length > 0; return ( - - + <> + + - 로그 상세 정보 + 로그 상세 정보(기능 실행자: {userName})
{isLoading && ( @@ -136,7 +191,8 @@ export function LogHistoryDetailDialog({ isExcelExport={false} enableSort={false} gridHeight={55} - fitStyle={GridFitStyle.NONE} + fitStyle={GridFitStyle.EVEN} + onDataCellClick={handleDataCellClick} /> ) : ( !isLoading && ( @@ -153,7 +209,13 @@ export function LogHistoryDetailDialog({ - -
+
+
+ + ); } diff --git a/apps/st/src/components/statistics/management/LogHistoryDetailsNestedDialog.tsx b/apps/st/src/components/statistics/management/LogHistoryDetailsNestedDialog.tsx new file mode 100644 index 000000000..bc6069847 --- /dev/null +++ b/apps/st/src/components/statistics/management/LogHistoryDetailsNestedDialog.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { + Button, + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@common/components"; + +import type { LogDetailVerificationItem } from "@/components/util/column/management/LogHistoryDetailColumn"; + +export interface LogHistoryDetailsNestedDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + items: LogDetailVerificationItem[]; +} + +export function LogHistoryDetailsNestedDialog({ + open, + onOpenChange, + items, +}: LogHistoryDetailsNestedDialogProps) { + return ( + + + + 검증 상세 + +
+ {items.length === 0 ? ( +

+ 표시할 상세 데이터가 없습니다. +

+ ) : ( + items.map((item, index) => ( +
+
+

+ + 조합순번: + {" "} + {item.eqtnCombSeq ?? "-"} +

+

+ + 검증상세순번: + {" "} + {item.vrfcDtlSn ?? "-"} +

+

+ + 비교연산: + {" "} + {item.cmprOpert ?? "-"} +

+

+ + 참조통계코드: + {" "} + {item.refStatsCd ?? "-"} +

+

+ + 한글 수식: + {" "} + {item.kornArtopnEqtnCn ?? "-"} +

+

+ + 영문 수식: + {" "} + {item.engArtopnEqtnCn ?? "-"} +

+
+
+ )) + )} +
+ + + + + +
+
+ ); +} diff --git a/apps/st/src/components/statistics/management/StatisticsInputManagement.tsx b/apps/st/src/components/statistics/management/StatisticsInputManagement.tsx index cb63324eb..2a813ed95 100644 --- a/apps/st/src/components/statistics/management/StatisticsInputManagement.tsx +++ b/apps/st/src/components/statistics/management/StatisticsInputManagement.tsx @@ -33,6 +33,12 @@ function rowSeTypeToCode(row: Record): "INPT" | "VRFC" return "INPT"; } +function parseExtSchdlId(value: string | number | undefined): number | undefined { + if (typeof value === "number" && Number.isFinite(value)) return value; + const parsed = parseInt(String(value ?? ""), 10); + return Number.isFinite(parsed) ? parsed : undefined; +} + function parseYmdToDate(ymd: string): Date | undefined { const s = String(ymd ?? "").replace(/\D/g, ""); if (s.length !== 8) return undefined; @@ -132,6 +138,7 @@ const StatisticsInputManagement = () => { const seCd = Number.isFinite(seCdNum) ? seCdNum : 0; const statsYr = String(row.statsYr ?? ""); const seType = rowSeTypeToCode(row); + const extSchdlId = parseExtSchdlId(row.extSchdlId); try { setIsLoading(true); @@ -139,6 +146,7 @@ const StatisticsInputManagement = () => { statsYr, seType, seCd, + ...(extSchdlId != null ? { extSchdlId } : {}), }); if (res.status !== "success" || !res.data) { @@ -147,8 +155,10 @@ const StatisticsInputManagement = () => { } const detail = res.data; + const detailExtSchdlId = parseExtSchdlId(detail.extSchdlId) ?? extSchdlId; setPeriodDialogMode("edit"); setPeriodDialogInitial({ + extSchdlId: detailExtSchdlId, statsYr: String(detail.statsYr ?? statsYr), seType: detail.seType === "VRFC" ? "VRFC" : "INPT", seCd: diff --git a/apps/st/src/components/statistics/management/StatisticsInputPeriodDialog.tsx b/apps/st/src/components/statistics/management/StatisticsInputPeriodDialog.tsx index 4e0c878da..8ec3c30ec 100644 --- a/apps/st/src/components/statistics/management/StatisticsInputPeriodDialog.tsx +++ b/apps/st/src/components/statistics/management/StatisticsInputPeriodDialog.tsx @@ -31,6 +31,7 @@ import { import { getUserInfo } from "../../../../../../packages/common/components/src/api"; export type StatisticsInputPeriodDialogValues = { + extSchdlId?: number; statsYr: string; seType: "INPT" | "VRFC"; seCd: number; @@ -146,6 +147,12 @@ export function StatisticsInputPeriodDialog({ }; if (!isCreate) { param.seCd = seCdParsed; + if ( + initial?.extSchdlId != null && + Number.isFinite(initial.extSchdlId) + ) { + param.extSchdlId = initial.extSchdlId; + } } const res = await statisticsApi.saveStatsInputPeriod(param); if (res.status === "success") { @@ -191,6 +198,9 @@ export function StatisticsInputPeriodDialog({ inptEndYmd: end, rgtrId: userInfo.userUnqId, }; + if (initial.extSchdlId != null && Number.isFinite(initial.extSchdlId)) { + param.extSchdlId = initial.extSchdlId; + } const res = await statisticsApi.deleteStatsInputPeriod(param); if (res.status === "success") { alert("삭제되었습니다."); diff --git a/apps/st/src/components/statistics/review/ExcretaTreatmentFacilityRvw.tsx b/apps/st/src/components/statistics/review/ExcretaTreatmentFacilityRvw.tsx index c053df71d..9b3d7f25e 100644 --- a/apps/st/src/components/statistics/review/ExcretaTreatmentFacilityRvw.tsx +++ b/apps/st/src/components/statistics/review/ExcretaTreatmentFacilityRvw.tsx @@ -30,7 +30,9 @@ import { } from "@/components/statistics/review/ReviewOpinionDialog"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { ExcretaTreatmentFacilityInfoList } from "@/components/util/column/review/ExcretaTreatmentFacilityRvwColumn"; -import { buildReviewRowTypeCurrCellStyleExcludeMergeCells, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; const excretaTreatmentFacilityTab = ExcretaTreatmentFacilityInfoList[0]; @@ -68,8 +70,8 @@ const ExcretaTreatmentFacilityRvw = () => { }, []); const cellStyleCallback = useMemo( - () => buildReviewRowTypeCurrCellStyleExcludeMergeCells(columnInfo.columns), - [columnInfo], + () => buildReviewCellStyleCallback(columnInfo.columns, rowData), + [columnInfo, rowData], ); const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { @@ -274,6 +276,7 @@ const ExcretaTreatmentFacilityRvw = () => { )}
+ { }, []); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const reviewOpinionContext = useMemo(() => { @@ -349,6 +351,7 @@ const PrivateSewageFacilityRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/review/PublicSewageFacilityRvw.tsx b/apps/st/src/components/statistics/review/PublicSewageFacilityRvw.tsx index 45079dbea..f4b379e82 100644 --- a/apps/st/src/components/statistics/review/PublicSewageFacilityRvw.tsx +++ b/apps/st/src/components/statistics/review/PublicSewageFacilityRvw.tsx @@ -22,7 +22,9 @@ import { type ReviewOpinionDialogContext, } from "@/components/statistics/review/ReviewOpinionDialog"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { buildReviewRowTypeCurrCellStyleExcludeMergeCells, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; import { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn"; @@ -56,8 +58,8 @@ const PublicSewageFacilityRvw = () => { }, []); const cellStyleCallback = useMemo( - () => buildReviewRowTypeCurrCellStyleExcludeMergeCells(columnInfo.columns), - [columnInfo], + () => buildReviewCellStyleCallback(columnInfo.columns, rowData), + [columnInfo, rowData], ); const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { @@ -248,15 +250,18 @@ const PublicSewageFacilityRvw = () => {
)} - +
+ + +
{ }, []); const cellStyleCallback = useMemo( - () => buildReviewRowTypeCurrCellStyleExcludeMergeCells(columnInfo.columns), - [columnInfo], + () => buildReviewCellStyleCallback(columnInfo.columns, rowData), + [columnInfo, rowData], ); const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { @@ -132,7 +134,7 @@ const PumpingStationRvw = () => { sggNm: values.gugun === "ALL" ? "" : values.gugun, sewerBzmnCd: values.bsnmCd, statsYr: values.year, - pumpstNm: values.fctName, + fcltNm: values.fctName, }); if (response.status === "success" && response.data) { @@ -264,6 +266,7 @@ const PumpingStationRvw = () => { )}
+ ( +
+
+
+
+ 전년도만 존재 +
+
+
+ 현년도만 존재 +
+
+
+); + +export default ReviewEmptyCurrLegend; diff --git a/apps/st/src/components/statistics/review/SewageExcretaRelatedRvw.tsx b/apps/st/src/components/statistics/review/SewageExcretaRelatedRvw.tsx index 2a4741661..c9ad04528 100644 --- a/apps/st/src/components/statistics/review/SewageExcretaRelatedRvw.tsx +++ b/apps/st/src/components/statistics/review/SewageExcretaRelatedRvw.tsx @@ -30,7 +30,9 @@ import { import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { SewageExcretaRelatedInfoListRvw } from "@/components/util/column/review/SewageExcretaRelatedRvwColumn"; -import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; const firstTabKey = SewageExcretaRelatedInfoListRvw[0]?.tabKey ?? "ST00120001"; @@ -89,8 +91,8 @@ const SewageExcretaRelatedRvw = () => { }, []); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const reviewOpinionContext = useMemo(() => { @@ -354,6 +356,7 @@ const SewageExcretaRelatedRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/review/SewageExcretaSlurgeRvw.tsx b/apps/st/src/components/statistics/review/SewageExcretaSlurgeRvw.tsx index 3d38d84c5..fd83c82fb 100644 --- a/apps/st/src/components/statistics/review/SewageExcretaSlurgeRvw.tsx +++ b/apps/st/src/components/statistics/review/SewageExcretaSlurgeRvw.tsx @@ -30,7 +30,9 @@ import { import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { SewageExcretaSludgeInfoListRvw } from "@/components/util/column/review/SewageExcretaSlurgeRvwColumn"; -import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; const firstTabKey = SewageExcretaSludgeInfoListRvw[0]?.tabKey ?? "ST00110001"; @@ -89,8 +91,8 @@ const SewageExcretaSlurgeRvw = () => { }, []); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const reviewOpinionContext = useMemo(() => { @@ -355,6 +357,7 @@ const SewageExcretaSlurgeRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx index 585556a7d..a90af6ac2 100644 --- a/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeMaintainRvw.tsx @@ -27,7 +27,9 @@ import { import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn"; -import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; const firstTabKey = SewagePipeMaintainColumnInfoList[0]?.tabKey ?? "ST00030001"; @@ -104,8 +106,8 @@ const SewagePipeMaintainRvw = () => { }, []); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const reviewOpinionContext = useMemo(() => { @@ -367,6 +369,7 @@ const SewagePipeMaintainRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx index ea105fd2f..02c111d38 100644 --- a/apps/st/src/components/statistics/review/SewagePipeRvw.tsx +++ b/apps/st/src/components/statistics/review/SewagePipeRvw.tsx @@ -32,7 +32,9 @@ import type { } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn"; -import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; +import { toGridRows } from "@/components/util/grid/gridFunction"; const firstTabKey = SewagePipeColumnInfoListRvw[0]?.tabKey ?? "ST00020001"; @@ -158,8 +160,8 @@ const SewagePipeRvw = () => { ); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const reviewOpinionContext = useMemo(() => { @@ -418,6 +420,7 @@ const SewagePipeRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx index 3bcf889ee..efb748aa3 100644 --- a/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx +++ b/apps/st/src/components/statistics/review/SewageReservoirPumpRvw.tsx @@ -25,7 +25,9 @@ import { import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn"; -import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; const firstTabKey = SewageReservoirPumpInfoListRvw[0]?.tabKey ?? "ST00040001"; @@ -52,7 +54,7 @@ function getListDataByTab(tabKey: string, responseData: unknown): object[] { const data = responseData as Record; if (tabKey === "ST00040001") { - const list = data.sewerReservoirList ?? data.facilitiesList; + const list = data.sewerReservoirList ?? data.reservoirList; return Array.isArray(list) ? (list as object[]) : []; } if (tabKey === "ST00040002") { @@ -108,8 +110,8 @@ const SewageReservoirPumpRvw = () => { }, []); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const activeStatsCd = activeStatsStatusInfo.statsCd ?? activeTab; @@ -373,6 +375,7 @@ const SewageReservoirPumpRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/review/SewerageFinanceRvw.tsx b/apps/st/src/components/statistics/review/SewerageFinanceRvw.tsx index e73e0653a..30d2faaed 100644 --- a/apps/st/src/components/statistics/review/SewerageFinanceRvw.tsx +++ b/apps/st/src/components/statistics/review/SewerageFinanceRvw.tsx @@ -30,7 +30,9 @@ import { import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { SewerageFinanceInfoListRvw } from "@/components/util/column/review/SewerageFinanceRvwColumn"; -import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; const firstTabKey = SewerageFinanceInfoListRvw[0]?.tabKey ?? "ST00090001"; @@ -89,8 +91,8 @@ const SewerageFinanceRvw = () => { }, []); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const reviewOpinionContext = useMemo(() => { @@ -352,6 +354,7 @@ const SewerageFinanceRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx index 2fb7c8375..8aeb157c5 100644 --- a/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx +++ b/apps/st/src/components/statistics/review/SeweragePenetrationRateRvw.tsx @@ -18,7 +18,9 @@ import type { import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { buildReviewRowTypeCurrCellStyleExcludeMergeCells, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; import { ReviewOpinionDialog, type ReviewOpinionDialogContext, @@ -55,8 +57,8 @@ const SeweragePenetrationRateRvw = () => { }, []); const cellStyleCallback = useMemo( - () => buildReviewRowTypeCurrCellStyleExcludeMergeCells(columnInfo.columns), - [columnInfo], + () => buildReviewCellStyleCallback(columnInfo.columns, rowData), + [columnInfo, rowData], ); const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => { @@ -243,15 +245,18 @@ const SeweragePenetrationRateRvw = () => {
)} - +
+ + +
{ }, []); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const reviewOpinionContext = useMemo(() => { @@ -353,6 +355,7 @@ const SewerageServiceLevelRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/review/WaterReuseRvw.tsx b/apps/st/src/components/statistics/review/WaterReuseRvw.tsx index 8fda5b79a..d3976cf01 100644 --- a/apps/st/src/components/statistics/review/WaterReuseRvw.tsx +++ b/apps/st/src/components/statistics/review/WaterReuseRvw.tsx @@ -30,7 +30,9 @@ import { import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { WaterReuseInfoListRvw } from "@/components/util/column/review/WaterReuseRvwColumn"; -import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction"; +import { toGridRows } from "@/components/util/grid/gridFunction"; +import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle"; +import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend"; const firstTabKey = WaterReuseInfoListRvw[0]?.tabKey ?? "ST00100001"; @@ -92,8 +94,8 @@ const WaterReuseRvw = () => { }, []); const reviewCellStyleCallbackByTab = useMemo( - () => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo), - [columnInfo], + () => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap), + [columnInfo, rowDataMap], ); const reviewOpinionContext = useMemo(() => { @@ -356,6 +358,7 @@ const WaterReuseRvw = () => { )}
+ void handleActiveTabChange(v)} diff --git a/apps/st/src/components/statistics/verification/generalVerification/CopyDialog.tsx b/apps/st/src/components/statistics/verification/generalVerification/CopyDialog.tsx index 556d3f54a..bb2f30974 100644 --- a/apps/st/src/components/statistics/verification/generalVerification/CopyDialog.tsx +++ b/apps/st/src/components/statistics/verification/generalVerification/CopyDialog.tsx @@ -63,11 +63,14 @@ const CopyDialog = ({ try { setIsLoading(true); const userInfo = await getUserInfo(); - const response = await generalverificationApiService.CopyVrfcData({ - vrfcSn: row?.vrfcSn ?? null, - targetStatsCd: selectedCode ?? '', - rgtrId: userInfo.userUnqId - }); + const response = await generalverificationApiService.CopyVrfcData( + { + vrfcSn: row?.vrfcSn ?? null, + targetStatsCd: selectedCode ?? '', + rgtrId: userInfo.userUnqId, + }, + userInfo.userUnqId, + ); if (response.status === 'success') { alert('복사되었습니다.'); diff --git a/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx b/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx index 1aa0de71f..518f2da32 100644 --- a/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx +++ b/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx @@ -177,7 +177,12 @@ const RegistDialog = memo( if (await confirm(deleteMsg)) { try { - const response = await generalverificationApiService.DeleteVrfcMaster(vrfcSn!); + const userInfo = await getUserInfo(); + const response = + await generalverificationApiService.DeleteVrfcMaster( + vrfcSn!, + userInfo.userUnqId, + ); if (response.status === 'success') { alert('삭제되었습니다.'); diff --git a/apps/st/src/components/util/MenuMatchList.ts b/apps/st/src/components/util/MenuMatchList.ts index ded92e5a4..0ece5c741 100644 --- a/apps/st/src/components/util/MenuMatchList.ts +++ b/apps/st/src/components/util/MenuMatchList.ts @@ -1,8 +1,11 @@ /** * statistics/input 화면과 탭(·분할 그리드) 키 매칭용 상수. - * 각 컴포넌트에 실제로 쓰이는 `tabKey`를 모두 포함합니다. + * 각 컴포넌트에 실제로 쓰이는 `tabKey`·`statsCd`를 모두 포함합니다. */ +/** `SeweragePenetrationRateInp.tsx` — 단일 그리드 */ +export const SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS = ["ST00010001"] as const; + /** `SewagePipeInp.tsx` — 메인 탭 + `ConnectionStatusInpColumn` 좌·우 그리드 서브 키 */ export const SEWAGE_PIPE_INP_TAB_KEYS = [ "ST00020001", @@ -18,16 +21,108 @@ export const SEWAGE_PIPE_INP_TAB_KEYS = [ /** `SewagePipeMaintainInp.tsx` — 보수·준설 탭 */ export const SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS = ["ST00030001", "ST00030002"] as const; -/** `SeweragePenetrationRateInp.tsx` — 단일 그리드(통계코드) */ -export const SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS = ["ST00010001"] as const; +/** `SewageReservoirPumpInp.tsx` */ +export const SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS = ["ST00040001", "ST00040002"] as const; +/** `PumpingStationInp.tsx` */ +export const PUMPING_STATION_INP_TAB_KEYS = ["ST00050001"] as const; + +/** `PublicSewageFacilityInp.tsx` */ +export const PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS = ["ST00060001", "ST00060002"] as const; + +/** `ExcretaTreatmentFacilityInp.tsx` */ +export const EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS = ["ST00070001"] as const; + +/** + * `PrivateSewageFacilityInp.tsx` + * ST00080003은 tabKey(`privateSewageDesigStatusInp`)와 다름 — `INPUT_TAB_KEY_BY_STATS_CD` 참고 + */ +export const PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS = [ + "ST00080001", + "ST00080002", + "ST00080003", +] as const; + +/** `SewerageFinanceInp.tsx` */ +export const SEWERAGE_FINANCE_INP_TAB_KEYS = [ + "ST00090001", + "ST00090002", + "ST00090003", + "ST00090004", +] as const; + +/** `WaterReuseInp.tsx` */ +export const WATER_REUSE_INP_TAB_KEYS = [ + "ST00100001", + "ST00100002", + "ST00100003", + "ST00100004", + "ST00100005", +] as const; + +/** `SewageExcretaSlurgeInp.tsx` */ +export const SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS = [ + "ST00110001", + "ST00110002", + "ST00110003", + "ST00110004", +] as const; + +/** `SewageExcretaRelatedInp.tsx` */ +export const SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS = [ + "ST00120001", + "ST00120002", + "ST00120003", + "ST00120004", +] as const; + +/** `SewerageServiceLevelInp.tsx` */ +export const SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS = [ + "ST00130001", + "ST00130002", +] as const; + +/** statsCd와 입력 화면 activeTab(tabKey)이 다른 경우 */ +export const INPUT_TAB_KEY_BY_STATS_CD: Record = { + ST00080003: "privateSewageDesigStatusInp", +}; + +export type SeweragePenetrationRateInpTabKey = + (typeof SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS)[number]; export type SewagePipeInpTabKey = (typeof SEWAGE_PIPE_INP_TAB_KEYS)[number]; -export type SewagePipeMaintainInpTabKey = (typeof SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS)[number]; -export type SeweragePenetrationRateInpTabKey = (typeof SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS)[number]; +export type SewagePipeMaintainInpTabKey = + (typeof SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS)[number]; +export type SewageReservoirPumpInpTabKey = + (typeof SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS)[number]; +export type PumpingStationInpTabKey = (typeof PUMPING_STATION_INP_TAB_KEYS)[number]; +export type PublicSewageFacilityInpTabKey = + (typeof PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS)[number]; +export type ExcretaTreatmentFacilityInpTabKey = + (typeof EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS)[number]; +export type PrivateSewageFacilityInpTabKey = + (typeof PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS)[number]; +export type SewerageFinanceInpTabKey = (typeof SEWERAGE_FINANCE_INP_TAB_KEYS)[number]; +export type WaterReuseInpTabKey = (typeof WATER_REUSE_INP_TAB_KEYS)[number]; +export type SewageExcretaSlurgeInpTabKey = + (typeof SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS)[number]; +export type SewageExcretaRelatedInpTabKey = + (typeof SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS)[number]; +export type SewerageServiceLevelInpTabKey = + (typeof SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS)[number]; /** 컴포넌트 파일명 기준 묶음 (메뉴·라우트 매칭 시 참고) */ export const STATISTICS_INPUT_TAB_KEYS_BY_SCREEN = { + SeweragePenetrationRateInp: SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS, SewagePipeInp: SEWAGE_PIPE_INP_TAB_KEYS, SewagePipeMaintainInp: SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS, - SeweragePenetrationRateInp: SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS, + SewageReservoirPumpInp: SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS, + PumpingStationInp: PUMPING_STATION_INP_TAB_KEYS, + PublicSewageFacilityInp: PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS, + ExcretaTreatmentFacilityInp: EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS, + PrivateSewageFacilityInp: PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS, + SewerageFinanceInp: SEWERAGE_FINANCE_INP_TAB_KEYS, + WaterReuseInp: WATER_REUSE_INP_TAB_KEYS, + SewageExcretaSlurgeInp: SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS, + SewageExcretaRelatedInp: SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS, + SewerageServiceLevelInp: SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS, } as const; diff --git a/apps/st/src/components/util/column/management/LogHistoryDetailColumn.ts b/apps/st/src/components/util/column/management/LogHistoryDetailColumn.ts index e8c93a45d..220aa70e5 100644 --- a/apps/st/src/components/util/column/management/LogHistoryDetailColumn.ts +++ b/apps/st/src/components/util/column/management/LogHistoryDetailColumn.ts @@ -3,6 +3,28 @@ import { ValueType } from "realgrid"; import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type"; import type { StatisticsLogDetailRow } from "@/entities/statistics/model/statistics-interface"; +export const LOG_DETAIL_DETAILS_FIELD = "details"; +export const LOG_DETAIL_DETAILS_RAW_FIELD = "__detailsRaw"; + +export interface LogDetailVerificationColumn { + tblNm: string; + metaSn: number; + fieldNm: string; + engColNm: string; + dataScpCd: string; + kornColNm: string; +} + +export interface LogDetailVerificationItem { + columns: LogDetailVerificationColumn[]; + cmprOpert: string | null; + vrfcDtlSn: number; + refStatsCd: string | null; + eqtnCombSeq: number; + engArtopnEqtnCn: string; + kornArtopnEqtnCn: string; +} + const COMMON_FIELD_ORDER = ["rmrk", "sggNm", "admdstCd", "admdstNm"] as const; const FIELD_HEADER: Record = { @@ -10,8 +32,71 @@ const FIELD_HEADER: Record = { sggNm: "시군구", admdstCd: "행정구역코드", admdstNm: "행정구역명", + [LOG_DETAIL_DETAILS_FIELD]: "상세", }; +function parseLogDetailsValue( + value: unknown, +): LogDetailVerificationItem[] | null { + if (value == null || value === "") return null; + if (Array.isArray(value)) { + return value as LogDetailVerificationItem[]; + } + if (typeof value === "string") { + try { + const parsed: unknown = JSON.parse(value); + return Array.isArray(parsed) + ? (parsed as LogDetailVerificationItem[]) + : null; + } catch { + return null; + } + } + return null; +} + +/** details JSON 배열을 그리드 버튼 셀 + 숨김 raw 필드로 변환 */ +export function prepareLogDetailRowsForGrid( + rows: StatisticsLogDetailRow[], +): Record[] { + return rows.map((row) => { + const next: Record = {}; + + for (const [key, value] of Object.entries(row)) { + if (key === LOG_DETAIL_DETAILS_FIELD) { + const parsed = parseLogDetailsValue(value); + if (parsed && parsed.length > 0) { + next[LOG_DETAIL_DETAILS_RAW_FIELD] = JSON.stringify(parsed); + next[LOG_DETAIL_DETAILS_FIELD] = "상세보기"; + } else { + next[LOG_DETAIL_DETAILS_RAW_FIELD] = ""; + next[LOG_DETAIL_DETAILS_FIELD] = + value === null || value === undefined + ? "" + : typeof value === "object" + ? "" + : String(value); + } + continue; + } + + next[key] = + value === null || value === undefined + ? "" + : (value as string | number); + } + + if ( + LOG_DETAIL_DETAILS_FIELD in row && + !(LOG_DETAIL_DETAILS_RAW_FIELD in next) + ) { + next[LOG_DETAIL_DETAILS_RAW_FIELD] = ""; + } + + return next; + }); +} + function collectFieldNames(rows: StatisticsLogDetailRow[]): string[] { const keys = new Set(); for (const row of rows) { @@ -47,6 +132,16 @@ function buildColumns(rows: StatisticsLogDetailRow[]): ColumnConfig[] { return fieldNames.map((fieldName) => { const numeric = isNumericField(rows, fieldName); + if (fieldName === LOG_DETAIL_DETAILS_FIELD) { + return { + name: fieldName, + fieldName, + header: { text: FIELD_HEADER[fieldName] ?? fieldName }, + editable: false, + width: 100, + styleName: "rg-cell-outline-action-btn", + }; + } return { name: fieldName, fieldName, @@ -60,13 +155,33 @@ function buildColumns(rows: StatisticsLogDetailRow[]): ColumnConfig[] { }); } +function appendHiddenDetailsRawColumn(columns: ColumnConfig[]): ColumnConfig[] { + const hasDetails = columns.some( + (column) => column.fieldName === LOG_DETAIL_DETAILS_FIELD, + ); + if (!hasDetails) return columns; + + return [ + ...columns, + { + name: LOG_DETAIL_DETAILS_RAW_FIELD, + fieldName: LOG_DETAIL_DETAILS_RAW_FIELD, + header: { text: "" }, + editable: false, + visible: false, + }, + ]; +} + export function buildLogHistoryDetailColumnInfo( rows: StatisticsLogDetailRow[], ): ColumnInfo { - const columns = buildColumns(rows); + const columns = appendHiddenDetailsRawColumn(buildColumns(rows)); return { columns, - columnLayout: columns.map((c) => c.fieldName), + columnLayout: columns + .filter((column) => column.visible !== false) + .map((column) => column.fieldName), }; } diff --git a/apps/st/src/components/util/column/review/PublicSewageFacilityRvwColumn.ts b/apps/st/src/components/util/column/review/PublicSewageFacilityRvwColumn.ts index d02b5360b..b6952ff38 100644 --- a/apps/st/src/components/util/column/review/PublicSewageFacilityRvwColumn.ts +++ b/apps/st/src/components/util/column/review/PublicSewageFacilityRvwColumn.ts @@ -33,16 +33,19 @@ export const rawPublicSewageFacilityInfoList = { name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, + mergeCells: true, + sortdisable: true, footer: [ { valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) }, { valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) }, ], }, - { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } }, + { name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true }, { name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, + sortdisable: true, footer: [ { valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) }, { valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) }, diff --git a/apps/st/src/components/util/grid/gridFunction.ts b/apps/st/src/components/util/grid/gridFunction.ts index ae19943d8..d6e36375a 100644 --- a/apps/st/src/components/util/grid/gridFunction.ts +++ b/apps/st/src/components/util/grid/gridFunction.ts @@ -61,6 +61,26 @@ export function buildReviewRowTypeCurrCellStyleCallbackByTab( return map; } +/** 여러 cellStyleCallback 결과를 styleName으로 병합 */ +export function composeCellStyleCallbacks( + ...callbacks: CellStyleCallback[] +): CellStyleCallback { + return (params) => { + const styleNames: string[] = []; + for (const cb of callbacks) { + const result = cb(params); + if (!result) continue; + if (typeof result === "string") { + styleNames.push(result); + } else if (result.styleName) { + styleNames.push(result.styleName); + } + } + const styleName = styleNames.join(" ").trim(); + return styleName ? styleName : undefined; + }; +} + /** * 객체 배열을 그리드 row 형식으로 변환 (null/undefined → ""). * 어떤 엔티티 타입이든 공통 사용 가능. diff --git a/apps/st/src/components/util/grid/reviewEmptyCurrCellStyle.ts b/apps/st/src/components/util/grid/reviewEmptyCurrCellStyle.ts new file mode 100644 index 000000000..65a125475 --- /dev/null +++ b/apps/st/src/components/util/grid/reviewEmptyCurrCellStyle.ts @@ -0,0 +1,183 @@ +import type { CellStyleCallback, ColumnConfig } from "@/components/type/DataGrid.type"; +import { + buildReviewRowTypeCurrCellStyleExcludeMergeCells, + composeCellStyleCallbacks, +} from "@/components/util/grid/gridFunction"; + +const DISPLAY_YEAR_FIELD = "displayYear"; + +/** CURR 미입력 + OLD(전년) 데이터 있음 */ +export const REVIEW_CURR_EMPTY_WARN_STYLE = "rg-cell-review-curr-empty-warn"; +/** CURR 미입력 + OLD 행 없음 */ +export const REVIEW_CURR_EMPTY_NEW_STYLE = "rg-cell-review-curr-empty-new"; + +function getColumnFieldNames(columns: readonly Pick[]): string[] { + return columns.map((col) => col.fieldName).filter(Boolean); +} + +function getFieldNamesBeforeDivision(columns: readonly Pick[]): string[] { + const fields = getColumnFieldNames(columns); + const divisionIndex = fields.indexOf(DISPLAY_YEAR_FIELD); + if (divisionIndex <= 0) return []; + return fields.slice(0, divisionIndex); +} + +function getFieldNamesAfterDivision(columns: readonly Pick[]): string[] { + const fields = getColumnFieldNames(columns); + const divisionIndex = fields.indexOf(DISPLAY_YEAR_FIELD); + if (divisionIndex < 0 || divisionIndex >= fields.length - 1) return []; + return fields.slice(divisionIndex + 1); +} + +function getDivisionHighlightFieldNames( + columns: readonly Pick[], +): Set { + const divisionColumn = columns.find((col) => col.fieldName === DISPLAY_YEAR_FIELD); + return new Set( + [DISPLAY_YEAR_FIELD, divisionColumn?.name, divisionColumn?.fieldName].filter( + Boolean, + ) as string[], + ); +} + +function buildRowGroupKey( + row: Record, + groupKeyFields: readonly string[], +): string { + return groupKeyFields.map((field) => String(row[field] ?? "")).join("\0"); +} + +export function isEmptyOrZeroGridValue(value: string | number | undefined): boolean { + if (value === null || value === undefined || value === "") return true; + if (typeof value === "number") return value === 0; + const trimmed = String(value).trim(); + if (trimmed === "") return true; + const numeric = Number(trimmed.replace(/,/g, "")); + return !Number.isNaN(numeric) && numeric === 0; +} + +export function isRowEmptyOrZeroAfterDivision( + row: Record, + dataFieldNames: readonly string[], +): boolean { + if (dataFieldNames.length === 0) return false; + return dataFieldNames.every((field) => isEmptyOrZeroGridValue(row[field])); +} + +type EmptyCurrHighlight = + | typeof REVIEW_CURR_EMPTY_WARN_STYLE + | typeof REVIEW_CURR_EMPTY_NEW_STYLE; + +function buildOldRowByGroupKey( + rows: readonly Record[], + groupKeyFields: readonly string[], +): Map> { + const map = new Map>(); + for (const row of rows) { + if (String(row.rowType ?? "") !== "OLD") continue; + map.set(buildRowGroupKey(row, groupKeyFields), row); + } + return map; +} + +function buildOldRowIndexByGroupKey( + rows: readonly Record[], + groupKeyFields: readonly string[], +): Map { + const map = new Map(); + rows.forEach((row, index) => { + if (String(row.rowType ?? "") !== "OLD") return; + map.set(buildRowGroupKey(row, groupKeyFields), index); + }); + return map; +} + +function resolveEmptyCurrHighlight( + currRow: Record, + oldRowByGroupKey: ReadonlyMap>, + groupKeyFields: readonly string[], + dataFieldNames: readonly string[], +): EmptyCurrHighlight | undefined { + if (String(currRow.rowType ?? "") !== "CURR") return undefined; + if (!isRowEmptyOrZeroAfterDivision(currRow, dataFieldNames)) return undefined; + + const groupKey = buildRowGroupKey(currRow, groupKeyFields); + const oldRow = oldRowByGroupKey.get(groupKey); + + if (!oldRow) { + return REVIEW_CURR_EMPTY_NEW_STYLE; + } + if (isRowEmptyOrZeroAfterDivision(oldRow, dataFieldNames)) { + return undefined; + } + return REVIEW_CURR_EMPTY_WARN_STYLE; +} + +/** + * 검토 그리드 — CURR 행의 구분 이후 값이 모두 비어있거나 0일 때 + * 구분(displayYear): OLD 있음 → OLD·CURR 노란색, OLD 없음 → CURR 하늘색, 둘 다 없음 → 미적용 + */ +export function buildReviewEmptyCurrCellStyleCallback( + columns: readonly Pick[], + rows: readonly Record[], +): CellStyleCallback { + const groupKeyFields = getFieldNamesBeforeDivision(columns); + const dataFieldNames = getFieldNamesAfterDivision(columns); + const highlightFieldNames = getDivisionHighlightFieldNames(columns); + const oldRowByGroupKey = buildOldRowByGroupKey(rows, groupKeyFields); + const oldRowIndexByGroupKey = buildOldRowIndexByGroupKey(rows, groupKeyFields); + + const highlightByRowIndex = new Map(); + rows.forEach((row, index) => { + if (String(row.rowType ?? "") !== "CURR") return; + + const highlight = resolveEmptyCurrHighlight( + row, + oldRowByGroupKey, + groupKeyFields, + dataFieldNames, + ); + if (!highlight) return; + + highlightByRowIndex.set(index, highlight); + + if (highlight === REVIEW_CURR_EMPTY_WARN_STYLE) { + const oldIndex = oldRowIndexByGroupKey.get(buildRowGroupKey(row, groupKeyFields)); + if (oldIndex !== undefined) { + highlightByRowIndex.set(oldIndex, highlight); + } + } + }); + + return ({ rowIndex, fieldName }) => { + if (highlightFieldNames.size === 0 || !highlightFieldNames.has(fieldName)) { + return undefined; + } + return highlightByRowIndex.get(rowIndex); + }; +} + +export function buildReviewCellStyleCallback( + columns: readonly Pick[], + rows: readonly Record[], +): CellStyleCallback { + const base = buildReviewRowTypeCurrCellStyleExcludeMergeCells(columns); + const emptyCurr = buildReviewEmptyCurrCellStyleCallback(columns, rows); + return composeCellStyleCallbacks(base, emptyCurr); +} + +export function buildReviewCellStyleCallbackByTab( + tabs: readonly { + tabKey: string; + columns?: readonly Pick[]; + }[], + rowDataMap: ReadonlyMap[]>, +): Map { + const map = new Map(); + for (const tab of tabs) { + const columns = tab.columns ?? []; + const rows = rowDataMap.get(tab.tabKey) ?? []; + map.set(tab.tabKey, buildReviewCellStyleCallback(columns, rows)); + } + return map; +} diff --git a/apps/st/src/components/util/input/verification-failed-flag.ts b/apps/st/src/components/util/input/verification-failed-flag.ts new file mode 100644 index 000000000..22c81dad7 --- /dev/null +++ b/apps/st/src/components/util/input/verification-failed-flag.ts @@ -0,0 +1,17 @@ +import type { StatisticsInputCommonInfoBase } from "@/entities/statistics/model/statistics-interface"; + +export function isVerificationFailedFromCommonInfo( + commonInfo: Pick | null | undefined, +): boolean { + return commonInfo?.isVerificationFailed === true; +} + +export function patchVerificationFailedByTab( + prev: Map, + tabKey: string, + commonInfo: Pick | null | undefined, +): Map { + const next = new Map(prev); + next.set(tabKey, isVerificationFailedFromCommonInfo(commonInfo)); + return next; +} 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 95c96b467..4e3f3a478 100644 --- a/apps/st/src/entities/statistics/api/general-verification-service.ts +++ b/apps/st/src/entities/statistics/api/general-verification-service.ts @@ -75,10 +75,16 @@ export class GeneralverificationApiService implements GeneralverificationService } public async DeleteVrfcMaster( - param: number, + vrfcSn: number, + userUnqId: string, ): Promise { const response = await stClient.delete( - `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${param}`, + `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${vrfcSn}`, + { + headers: { + userId: userUnqId, + }, + }, ); return response.data; } @@ -141,9 +147,16 @@ export class GeneralverificationApiService implements GeneralverificationService public async CopyVrfcData( param: CopyVerifyDataRequest, + userUnqId: string, ): Promise { const response = await stClient.post( `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${param.vrfcSn}/copy?targetStatsCd=${param.targetStatsCd}&rgtrId=${param.rgtrId}`, + undefined, + { + headers: { + userId: userUnqId, + }, + }, ); return response.data; } diff --git a/apps/st/src/entities/statistics/api/statistics-management-service.ts b/apps/st/src/entities/statistics/api/statistics-management-service.ts index d7d2f052f..93bed5573 100644 --- a/apps/st/src/entities/statistics/api/statistics-management-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-management-service.ts @@ -258,7 +258,7 @@ export class StatisticsManagementApiService { sggNm?: string; statsYr?: string; sewerBzmnCd?: string; - pumpstNm?: string; + fcltNm?: string; } ): Promise { const response = await stClient.get( diff --git a/apps/st/src/entities/statistics/api/statistics-service.ts b/apps/st/src/entities/statistics/api/statistics-service.ts index 23a1bd394..557c47150 100644 --- a/apps/st/src/entities/statistics/api/statistics-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-service.ts @@ -93,7 +93,7 @@ export class StatisticsApiService implements StatisticsService { ): Promise { const response = await stClient.get( `${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info/list`, - { params }, + withAuthHeader({ params }), ); return response.data; } @@ -101,7 +101,7 @@ export class StatisticsApiService implements StatisticsService { public async getStatsInputPeriodDetail(params: StatsInputPeriodDetailRequest): Promise { const response = await stClient.get( `${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info/detail`, - { params }, + withAuthHeader({ params }), ); return response.data; } @@ -113,6 +113,7 @@ export class StatisticsApiService implements StatisticsService { const response = await stClient.post( `${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info`, param, + withAuthHeader(), ); return response.data; } @@ -123,7 +124,7 @@ export class StatisticsApiService implements StatisticsService { ): Promise { const response = await stClient.delete( `${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info`, - { data: param }, + withAuthHeader({ data: param }), ); return response.data; } 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 a3fcd66c0..07c0fbea3 100644 --- a/apps/st/src/entities/statistics/model/general-verification-interface.ts +++ b/apps/st/src/entities/statistics/model/general-verification-interface.ts @@ -275,7 +275,10 @@ export interface GeneralverificationService { RegistVrfcMaster( param: RegistVrfcMasterRequest, ): Promise; - DeleteVrfcMaster(param: number): Promise; + DeleteVrfcMaster( + vrfcSn: number, + userUnqId: string, + ): Promise; getVrfcOperators(): Promise; getVrfcDetail(param: number): Promise; getVerifyColumnList( @@ -292,6 +295,7 @@ export interface GeneralverificationService { ): Promise; CopyVrfcData( param: CopyVerifyDataRequest, + userUnqId: string, ): Promise; getVerifyResultList( param: VerifyResultListRequest, diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-excreta-treatment-facility-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-excreta-treatment-facility-interface.ts index 07d5c3c00..82a721d35 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-excreta-treatment-facility-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-excreta-treatment-facility-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; /** 분뇨처리시설(ST00070001) — `ExcretaTreatFactInpColumn` */ export const EXCRETA_TREATMENT_FACILITY_STATS_CD = "ST00070001" as const; @@ -18,7 +18,7 @@ export type ExcretaTreatmentFacilityVerificationResult = Record< >; /** 분뇨처리시설 조회 `data.commonInfo` */ -export interface ExcretaTreatmentFacilityCommonInfo { +export interface ExcretaTreatmentFacilityCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-private-sewage-facility-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-private-sewage-facility-interface.ts index 89179711b..b0285ac8d 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-private-sewage-facility-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-private-sewage-facility-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; /** 처리용량별 오수처리시설 현황(ST00080001) — `SewageTreatFactStatusInpColumn.tabKey` */ export const SEWAGE_TREAT_FACT_STATUS_STATS_CD = "ST00080001" as const; @@ -18,7 +18,7 @@ export type PrivateSewageFacilityVerificationResult = Record< >; /** ST00080001 조회 `data.commonInfo` */ -export interface SewageTreatFactStatusCommonInfo { +export interface SewageTreatFactStatusCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; @@ -78,7 +78,7 @@ export interface SewageTreatFactStatusRequestParams { export const SEPTIC_TANK_STATUS_STATS_CD = "ST00080002" as const; /** ST00080002 조회 `data.commonInfo` */ -export interface SepticTankStatusCommonInfo { +export interface SepticTankStatusCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; @@ -156,7 +156,7 @@ export interface SepticTankStatusRequestParams { export const PRIVATE_SEWAGE_DESIG_STATUS_STATS_CD = "ST00080003" as const; /** ST00080003 조회 `data.commonInfo` */ -export interface PrivateSewageDesigStatusCommonInfo { +export interface PrivateSewageDesigStatusCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-pumping-station-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-pumping-station-interface.ts index 105eb1214..e9cfe9b99 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-pumping-station-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-pumping-station-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; /** 펌프장(ST00050001) — `PumpingStationInpColumn` tabKey */ export const PUMPING_STATION_STATS_CD = "ST00050001" as const; @@ -18,7 +18,7 @@ export type PumpingStationVerificationResult = Record< >; /** 펌프장 조회 `data.commonInfo` */ -export interface PumpingStationCommonInfo { +export interface PumpingStationCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface.ts index f2db92d5a..fbfc464b3 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-related-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; /** 하수·분뇨관련산업 — 공공기관 하수분야(ST00120001) `PublicInstitutionSewerIndustryInpColumn.tabKey` */ export const SEWAGE_EXCRETA_PUBLIC_INSTITUTION_SEWER_STATS_CD = "ST00120001" as const; @@ -18,7 +18,7 @@ export type SewageExcretaRelatedVerificationResult = Record< >; /** 조회 `data.commonInfo` */ -export interface SewageExcretaRelatedCommonInfo { +export interface SewageExcretaRelatedCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface.ts index 4c575fe84..1ad5e2294 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; /** 슬러지 처리 — 찌꺼기(슬러지)처리시설현황(ST00110001) `SlurgeProcessingFacStatusInpColumn.tabKey` */ export const SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD = "ST00110001" as const; @@ -27,7 +27,7 @@ export type SewageExcretaSlurgeVerificationResult = Record< >; /** 조회 `data.commonInfo` */ -export interface SewageExcretaSlurgeCommonInfo { +export interface SewageExcretaSlurgeCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-pipe-maintain-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-pipe-maintain-interface.ts index 36743925e..df35d2f76 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-pipe-maintain-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-pipe-maintain-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; export interface SewagePipeMaintainRepairVerificationResultItem { vrfcCd: string; @@ -13,7 +13,7 @@ export type SewagePipeMaintainRepairVerificationResult = Record< SewagePipeMaintainRepairVerificationResultItem[] >; -export interface SewagePipeMaintainRepairCommonInfo { +export interface SewagePipeMaintainRepairCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-reservoir-pump-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-reservoir-pump-interface.ts index 4a5d0bd66..eb734afca 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-sewage-reservoir-pump-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-sewage-reservoir-pump-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; /** 하수저류시설 Tab1 — `SewageReservoirInpColumn.tabKey` */ export const SEWAGE_RESERVOIR_STATS_CD = "ST00040001" as const; @@ -18,7 +18,7 @@ export type SewageReservoirVerificationResult = Record< >; /** 하수저류시설 조회 `data.commonInfo` */ -export interface SewageReservoirCommonInfo { +export interface SewageReservoirCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; @@ -86,7 +86,7 @@ export type SewageReservoirApiResponse = ApiResponse; /** ST00090001 조회 `data.commonInfo` */ -export interface SewerageFinanceSettleCommonInfo { +export interface SewerageFinanceSettleCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; @@ -202,7 +202,7 @@ export interface SewerageFinanceSubmitResponse { export const SEWERAGE_CHARGE_STATS_CD = "ST00090002" as const; /** ST00090002 조회 `data.commonInfo` */ -export interface SewerageChargeCommonInfo { +export interface SewerageChargeCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; @@ -292,7 +292,7 @@ export type SewerageChargeSubmitItem = SewerageChargeSaveItem; export const SEWERAGE_USAGE_FEE_STATS_CD = "ST00090003" as const; /** ST00090003 조회 `data.commonInfo` */ -export interface SewerageUsageFeeCommonInfo { +export interface SewerageUsageFeeCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; @@ -416,7 +416,7 @@ export type SewerageUsageFeeSubmitItem = SewerageUsageFeeSaveItem; export const CAUSER_BURDEN_STATS_CD = "ST00090004" as const; /** ST00090004 조회 `data.commonInfo` */ -export interface CauserBurdenCommonInfo { +export interface CauserBurdenCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-sewerage-service-level-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-sewerage-service-level-interface.ts index f331aa558..b2e9f95df 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-sewerage-service-level-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-sewerage-service-level-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; /** 하수도 서비스 수준 — 민원(ST00130001) `ComplaintInpColumn.tabKey` */ export const SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD = "ST00130001" as const; @@ -18,7 +18,7 @@ export type SewerageServiceLevelVerificationResult = Record< >; /** ST00130001 조회 `data.commonInfo` */ -export interface SewerageServiceLevelComplaintCommonInfo { +export interface SewerageServiceLevelComplaintCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; @@ -72,7 +72,7 @@ export interface SewerageServiceLevelComplaintRequestParams { export const SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD = "ST00130002" as const; /** ST00130002 조회 `data.commonInfo` */ -export interface SewerageServiceLevelResidentFriendlyFactCommonInfo { +export interface SewerageServiceLevelResidentFriendlyFactCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-water-reuse-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-water-reuse-interface.ts index e157b9a68..b40a17da5 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-water-reuse-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-water-reuse-interface.ts @@ -1,4 +1,4 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; /** 물 재이용 — 공공하수처리수 재이용(ST00100001) `PublicSewageTreatWaterReuseInpColumn.tabKey` */ export const WATER_REUSE_PUBLIC_SEWAGE_TREAT_STATS_CD = "ST00100001" as const; @@ -30,7 +30,7 @@ export type WaterReuseVerificationResult = Record< >; /** 조회 `data.commonInfo` */ -export interface WaterReuseCommonInfo { +export interface WaterReuseCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-public-sewage-facility-input-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-public-sewage-facility-input-interface.ts index a2570a9e2..7d685fdcb 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-public-sewage-facility-input-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-public-sewage-facility-input-interface.ts @@ -1,4 +1,4 @@ -import type { SewerageStatusInfo } from "../statistics-interface"; +import type { SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; export interface PublicSewageFacilityRequestParams { ctpvNm?: string; @@ -22,7 +22,7 @@ export type PublicSewageFacilityVerificationResult = Record< PublicSewageFacilityVerifyResultItem[] >; -export interface PublicSewageFacilityCommonInfo { +export interface PublicSewageFacilityCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; 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 4076e5f4f..454eb4446 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 @@ -1,4 +1,4 @@ -import type { SewerageStatusInfo } from "../statistics-interface"; +import type { SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; export interface SeweragePipeRequestParams { ctpvNm?: string; @@ -20,7 +20,7 @@ export interface VerifyResultItem { /** 필드명 -> 검증 결과 배열 */ export type SeweragePipeInputVerificationResult = Record; -export interface SeweragePipeInputCommonInfo { +export interface SeweragePipeInputCommonInfo extends StatisticsInputCommonInfoBase { statsYr: string; ctpvNm: string; sewerBzmnCd: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface.ts index 5744197f8..d58f3c41e 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface.ts @@ -1,7 +1,7 @@ -import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface"; +import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface"; // 하수도 보급률 데이터 항목 -export interface SewerageSupplyRateCommonInfo { +export interface SewerageSupplyRateCommonInfo extends StatisticsInputCommonInfoBase { sewerBzmnArea: number; sewerBzmnCd: string; sewerBzmnFrgnrWholPpltnCnt: number; diff --git a/apps/st/src/entities/statistics/model/statistics-excreta-treatment-facility-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-excreta-treatment-facility-review-interface.ts index b7981456d..ef4b618a8 100644 --- a/apps/st/src/entities/statistics/model/statistics-excreta-treatment-facility-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-excreta-treatment-facility-review-interface.ts @@ -44,7 +44,7 @@ export interface ExcretaTreatmentFacilityReviewItem { } export interface ExcretaTreatmentFacilityReviewResponseData { - excretaTreatmentFacilityList: ExcretaTreatmentFacilityReviewItem[]; + excretaTreatmentList: ExcretaTreatmentFacilityReviewItem[]; statsStatusInfo?: StatsStatusInfo; } @@ -65,7 +65,7 @@ export function extractExcretaTreatmentFacilityReviewList( ): ExcretaTreatmentFacilityReviewItem[] { if (!data) return []; if (Array.isArray(data)) return data; - return data.excretaTreatmentFacilityList ?? []; + return data.excretaTreatmentList ?? []; } export function extractExcretaTreatmentFacilityReviewStatsStatusInfo( diff --git a/apps/st/src/entities/statistics/model/statistics-interface.ts b/apps/st/src/entities/statistics/model/statistics-interface.ts index d63262e54..f748a5c04 100644 --- a/apps/st/src/entities/statistics/model/statistics-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-interface.ts @@ -49,6 +49,7 @@ export type StatisticsColumnGuideListResponse = ApiResponse; /** 통계 입력 기간 삭제 요청 */ export interface StatsInputPeriodDeleteRequest { + extSchdlId?: number; statsYr: string; seType: string; seCd: number; @@ -105,6 +109,11 @@ export interface StatsInputPeriodDeleteRequest { /** 통계 입력 기간 삭제 응답 */ export type StatsInputPeriodDeleteApiResponse = ApiResponse; +/** 입력 조회 `data.commonInfo` 공통 필드 */ +export interface StatisticsInputCommonInfoBase { + isVerificationFailed: boolean; +} + /** 하수 입력/보급률 등 조회 `data.statusInfo` 공통 (LAE 제출·담당자 정보) */ export interface SewerageStatusInfo { laePicNm: string | null; diff --git a/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts b/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts index 7ce2bc32a..4f4ad95d6 100644 --- a/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-pumping-station-review-interface.ts @@ -84,6 +84,6 @@ export interface MediationPumpStationService { sewerBzmnCd?: string; ctpvNm?: string; sggNm?: string; - pumpstNm?: string; + fcltNm?: string; }): Promise; } diff --git a/apps/st/src/shared/lib/stats-cd-input-navigation.ts b/apps/st/src/shared/lib/stats-cd-input-navigation.ts index 56b8c791d..891572ef9 100644 --- a/apps/st/src/shared/lib/stats-cd-input-navigation.ts +++ b/apps/st/src/shared/lib/stats-cd-input-navigation.ts @@ -3,9 +3,20 @@ import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { doNameList } from "@/components/util/column/input/DoList"; import { menuMatchesStatisticsTabId } from "@/shared/lib/statistics-menu-url"; import { + EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS, + INPUT_TAB_KEY_BY_STATS_CD, + PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS, + PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS, + PUMPING_STATION_INP_TAB_KEYS, + SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS, + SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS, SEWAGE_PIPE_INP_TAB_KEYS, SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS, + SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS, + SEWERAGE_FINANCE_INP_TAB_KEYS, SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS, + SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS, + WATER_REUSE_INP_TAB_KEYS, } from "@/components/util/MenuMatchList"; /** 검증 결과에서 입력 화면 자동 조회에 사용하는 조건 */ @@ -35,15 +46,21 @@ export function verificationSearchToFormValues( /** `statistics-page` 동적 import 경로의 파일명(확장자 제외)과 일치해야 함 */ export const STATISTICS_INPUT_URL_PATH = { + SEWERAGE_PENETRATION_RATE: "SeweragePenetrationRateInp", SEWAGE_PIPE: "SewagePipeInp", SEWAGE_PIPE_MAINTAIN: "SewagePipeMaintainInp", - SEWERAGE_PENETRATION_RATE: "SeweragePenetrationRateInp", + SEWAGE_RESERVOIR_PUMP: "SewageReservoirPumpInp", + PUMPING_STATION: "PumpingStationInp", PUBLIC_SEWAGE_FACILITY: "PublicSewageFacilityInp", + EXCRETA_TREATMENT_FACILITY: "ExcretaTreatmentFacilityInp", + PRIVATE_SEWAGE_FACILITY: "PrivateSewageFacilityInp", + SEWERAGE_FINANCE: "SewerageFinanceInp", + WATER_REUSE: "WaterReuseInp", + SEWAGE_EXCRETA_SLURGE: "SewageExcretaSlurgeInp", + SEWAGE_EXCRETA_RELATED: "SewageExcretaRelatedInp", + SEWERAGE_SERVICE_LEVEL: "SewerageServiceLevelInp", } as const; -/** `PublicSewageFacilityInp.tsx` — 공공하수처리시설 탭 */ -const PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS = ["ST00060001"] as const; - function normalizeSewagePipeTabKey(tabKey: string): string { if ( tabKey === "adminAreaConnectionInp" || @@ -54,20 +71,66 @@ function normalizeSewagePipeTabKey(tabKey: string): string { return tabKey; } +function resolveInputTabKey(urlPath: string, statsCd: string): string { + if (urlPath === STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE) { + return normalizeSewagePipeTabKey(statsCd); + } + return INPUT_TAB_KEY_BY_STATS_CD[statsCd] ?? statsCd; +} + const NAV_ROWS: { urlPath: string; tabKeys: readonly string[] }[] = [ - { urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE, tabKeys: SEWAGE_PIPE_INP_TAB_KEYS }, - { - urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN, - tabKeys: SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS, - }, { urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE, tabKeys: SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS, }, + { + urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE, + tabKeys: SEWAGE_PIPE_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN, + tabKeys: SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_RESERVOIR_PUMP, + tabKeys: SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.PUMPING_STATION, + tabKeys: PUMPING_STATION_INP_TAB_KEYS, + }, { urlPath: STATISTICS_INPUT_URL_PATH.PUBLIC_SEWAGE_FACILITY, tabKeys: PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS, }, + { + urlPath: STATISTICS_INPUT_URL_PATH.EXCRETA_TREATMENT_FACILITY, + tabKeys: EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.PRIVATE_SEWAGE_FACILITY, + tabKeys: PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_FINANCE, + tabKeys: SEWERAGE_FINANCE_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.WATER_REUSE, + tabKeys: WATER_REUSE_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_SLURGE, + tabKeys: SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_RELATED, + tabKeys: SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS, + }, + { + urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_SERVICE_LEVEL, + tabKeys: SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS, + }, ]; /** @@ -83,11 +146,10 @@ export function resolveInputPageNavigationFromStatsCd(statsCd: string): { for (const row of NAV_ROWS) { if (row.tabKeys.includes(trimmed)) { - const tabKey = - row.urlPath === STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE - ? normalizeSewagePipeTabKey(trimmed) - : trimmed; - return { urlPath: row.urlPath, tabKey }; + return { + urlPath: row.urlPath, + tabKey: resolveInputTabKey(row.urlPath, trimmed), + }; } } return null; diff --git a/apps/st/src/shared/lib/use-statistics-input-pending-navigation.ts b/apps/st/src/shared/lib/use-statistics-input-pending-navigation.ts new file mode 100644 index 000000000..9a901e558 --- /dev/null +++ b/apps/st/src/shared/lib/use-statistics-input-pending-navigation.ts @@ -0,0 +1,71 @@ +"use client"; + +import { useEffect, useLayoutEffect, useRef, useState } from "react"; + +import type { SearchFormValues } from "@/components/type/SearchForm.type"; +import { + verificationSearchToFormValues, + type StatisticsInputPendingSearch, +} from "@/shared/lib/stats-cd-input-navigation"; +import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store"; +import { useTabStore } from "@/store/tab-store"; + +type PendingNavigation = { + tabKey: string; + search: StatisticsInputPendingSearch | null; +}; + +type UseStatisticsInputPendingNavigationOptions = { + urlPath: string; + onPending?: (pending: PendingNavigation) => void; + onAutoSearch?: ( + values: SearchFormValues, + tabKey: string, + ) => void | Promise; +}; + +/** 이상치 선별 보고서 등에서 넘어온 입력 화면·탭·검색조건을 적용합니다. */ +export function useStatisticsInputPendingNavigation({ + urlPath, + onPending, + onAutoSearch, +}: UseStatisticsInputPendingNavigationOptions) { + const [verificationSearchDefaults, setVerificationSearchDefaults] = + useState(null); + const [searchFormRemountKey, setSearchFormRemountKey] = useState(0); + const autoSearchRef = useRef<{ + values: SearchFormValues; + tabKey: string; + } | null>(null); + const handlersRef = useRef({ onPending, onAutoSearch }); + handlersRef.current = { onPending, onAutoSearch }; + + const activeTabId = useTabStore((s) => s.activeTabId); + const inputFocusNonce = useStatisticsInputFocusStore((s) => s.nonce); + + useLayoutEffect(() => { + if (activeTabId !== urlPath) return; + const pending = useStatisticsInputFocusStore + .getState() + .takePendingForUrl(urlPath); + if (!pending) return; + + handlersRef.current.onPending?.(pending); + + if (pending.search) { + const values = verificationSearchToFormValues(pending.search); + setVerificationSearchDefaults(values); + autoSearchRef.current = { values, tabKey: pending.tabKey }; + setSearchFormRemountKey((k) => k + 1); + } + }, [activeTabId, inputFocusNonce, urlPath]); + + useEffect(() => { + const job = autoSearchRef.current; + if (!job) return; + autoSearchRef.current = null; + void handlersRef.current.onAutoSearch?.(job.values, job.tabKey); + }, [searchFormRemountKey]); + + return { verificationSearchDefaults, searchFormRemountKey }; +}