From e8b309460e76f31392cd27a1217a7fb257d79125 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Fri, 28 Aug 2026 17:08:20 +0900 Subject: [PATCH] =?UTF-8?q?[ST]=20input=20=ED=8A=B9=EA=B4=91=EC=97=AD?= =?UTF-8?q?=EC=8B=9C=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EC=9D=BC=EB=B0=98=EA=B2=80=EC=A6=9D=20=EC=97=91=EC=85=80=20?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=EB=AA=A8=EB=93=A0=20number=20=ED=83=80=EC=9E=85=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=EC=97=90=20footer=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/SearchForm.tsx | 85 +++++++--- .../module/SidoSggFacilityNameSearchForm.tsx | 22 ++- .../module/VerificationResultDialog.tsx | 2 +- .../input/ExcretaTreatmentFacilityInp.tsx | 100 ++++++------ .../input/PrivateSewageFacilityInp.tsx | 88 +++++----- .../input/PublicSewageFacilityInp.tsx | 119 ++++++++------ .../statistics/input/PumpingStationInp.tsx | 100 ++++++------ .../input/SewageExcretaRelatedInp.tsx | 103 +++++++----- .../input/SewageExcretaSlurgeInp.tsx | 116 ++++++++------ .../statistics/input/SewagePipeInp.tsx | 115 +++++++------ .../input/SewagePipeMaintainInp.tsx | 97 ++++++----- .../input/SewageReservoirPumpInp.tsx | 112 +++++++------ .../statistics/input/SewerageFinanceInp.tsx | 89 ++++++----- .../input/SeweragePenetrationRateInp.tsx | 93 ++++++----- .../input/SewerageServiceLevelInp.tsx | 142 ++++++++++------ .../input/StatisticsReviewHistory.tsx | 2 +- .../statistics/input/StatsInputStatusInp.tsx | 9 +- .../statistics/input/WaterReuseInp.tsx | 151 ++++++++++-------- .../statistics/management/LogHistory.tsx | 14 +- .../statistics/review/StatsInputStatusRvw.tsx | 9 +- .../GeneralVerificationManagement.tsx | 70 +++++++- .../st/src/components/type/SearchForm.type.ts | 4 +- .../column/input/ExcretaTreatFactInpColumn.ts | 8 +- .../PublicSewageFactColumn.ts | 7 +- .../SlurgeProcessingFacStatusInpColumn.ts | 12 +- .../sewagePipe/ConnectionStatusInpColumn.ts | 3 +- .../sewagePipe/FactInstallStatusInpColumn.ts | 5 +- .../RainfallPumpStationInpColumn.ts | 6 +- .../SewageReservoirInpColumn.ts | 2 +- .../RainFallUtilFactStatusInpColumn.ts | 2 +- .../RecycledWaterStatusInpColumn.ts | 2 +- .../api/general-verification-service.ts | 8 + apps/st/src/entities/statistics/index.ts | 14 ++ .../src/entities/statistics/lib/sido-gugun.ts | 133 +++++++++++++++ .../model/general-verification-interface.ts | 17 ++ ...ut-excreta-treatment-facility-interface.ts | 4 +- ...input-private-sewage-facility-interface.ts | 12 +- ...tistics-input-pumping-station-interface.ts | 4 +- ...-input-sewage-excreta-related-interface.ts | 2 +- ...s-input-sewage-excreta-slurge-interface.ts | 18 +-- ...cs-input-sewage-pipe-maintain-interface.ts | 8 +- ...s-input-sewage-reservoir-pump-interface.ts | 8 +- ...istics-input-sewerage-finance-interface.ts | 12 +- ...-input-sewerage-service-level-interface.ts | 8 +- .../statistics-input-water-reuse-interface.ts | 22 +-- ...-public-sewage-facility-input-interface.ts | 8 +- .../statistics-sewage-pipe-input-interface.ts | 14 +- ...tics-sewage-supply-rate-input-interface.ts | 2 +- apps/st/src/shared/lib/index.ts | 6 + .../shared/lib/stats-cd-input-navigation.ts | 12 +- .../lib/use-search-condition-sido-list.ts | 61 +++++++ ...use-statistics-input-pending-navigation.ts | 10 +- 52 files changed, 1344 insertions(+), 728 deletions(-) create mode 100644 apps/st/src/entities/statistics/lib/sido-gugun.ts create mode 100644 apps/st/src/shared/lib/use-search-condition-sido-list.ts diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index ea94e1f9b..50e7ae828 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -5,6 +5,11 @@ import { useEffect, useState } from "react"; import useSWR from "swr"; import { useAlertStore, useUserStore } from "@/shared/lib"; +import { + fetchSearchCondition, + fetchSearchConditionInput, + SEARCH_CONDITION_SWR_KEY, +} from "@/shared/lib/use-search-condition-sido-list"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import type { ApiResponse, @@ -23,28 +28,28 @@ import { SelectValue, } from "@common/components"; +import { doList } from "@/components/util/column/input/DoList"; + import { defaultSearchFormProps, type SearchFormClsfCd, type SearchFormProps, type SearchFormValues, } from "../type/SearchForm.type"; -import { doList } from "../util/column/input/DoList"; -const getSidoListFetcher = async ( - key: [string, string], -): Promise> => { - return statisticsApi.getSidoList({ instSeCd: key[1] }); -}; +function isDoListBsnm(bsnmCd: string | undefined): boolean { + return Boolean(bsnmCd && doList.includes(bsnmCd)); +} const getSggListFetcher = async ( - key: [string, string, string, string], + key: [string, string, string, "Y" | "N"], ): Promise> => { - const gugunSkipYn = doList.includes(key[3]) ? "N" : "Y"; - return statisticsApi.getSggList(key[1], { - instSeCd: key[2], - gugunSkipYn, - }); + const [, sidoName, instSeCd, gugunSkipYn] = key; + const params = { instSeCd, gugunSkipYn }; + if (gugunSkipYn === "N") { + return statisticsApi.getSggAllList(sidoName, params); + } + return statisticsApi.getSggList(sidoName, params); }; function buildSearchFormValues( @@ -59,9 +64,15 @@ function buildSearchFormValues( : "ALL"; if (d?.sido) { + const sidoItem = sidoList?.data?.sidoList?.find( + (item) => item.sido === d.sido, + ); + const gugun = + d.gugun || + (sidoItem && !isDoListBsnm(sidoItem.bsnmCd) ? "ALL" : ""); return { sido: d.sido, - gugun: d.gugun || "", + gugun, year: d.year || defaultYear.toString(), order: d.order || "", fctName: d.fctName || "", @@ -93,7 +104,8 @@ function buildSearchFormValues( return { sido: useFirstSido ? first.sido : "", - gugun: "", + gugun: + useFirstSido && first && !isDoListBsnm(first.bsnmCd) ? "ALL" : "", year: d?.year || defaultYear.toString(), order: d?.order || "", fctName: d?.fctName || "", @@ -117,10 +129,12 @@ const SearchForm = (props: SearchFormProps = {}) => { const currentYear = new Date().getFullYear(); const defaultYear = currentYear - 1; + const isInput = Boolean(props.isInput); + const sidoListKind = isInput ? "input" : "default"; const { data: sidoList } = useSWR>( - ["sido-list", instSeCd], - getSidoListFetcher, + SEARCH_CONDITION_SWR_KEY(instSeCd, sidoListKind), + isInput ? fetchSearchConditionInput : fetchSearchCondition, { revalidateOnFocus: false, revalidateOnReconnect: false, @@ -157,7 +171,7 @@ const SearchForm = (props: SearchFormProps = {}) => { : { ...prev, sido: first.sido, - gugun: "", + gugun: isDoListBsnm(first.bsnmCd) ? "" : "ALL", bsnmCd: first.bsnmCd, }, ); @@ -168,9 +182,18 @@ const SearchForm = (props: SearchFormProps = {}) => { mergedProps.sido, ]); - const sggKey: [string, string, string, string] | null = - values.sido && values.sido !== "ALL" - ? ["sgg-list", values.sido, instSeCd, values.bsnmCd || ""] + const selectedSido = sidoList?.data?.sidoList?.find( + (item) => item.sido === values.sido, + ); + const gugunSkipYn = selectedSido?.gugunSkipYn ?? "Y"; + const sggKey: [string, string, string, "Y" | "N"] | null = + values.sido && values.sido !== "ALL" && sidoList?.data + ? [ + gugunSkipYn === "N" ? "sgg-all" : "sgg-list", + values.sido, + instSeCd, + gugunSkipYn, + ] : null; const { data: sggList } = useSWR>( sggKey, @@ -195,12 +218,25 @@ const SearchForm = (props: SearchFormProps = {}) => { ); const sggOptions = sggList?.data ?? []; - // 시도 설정 후 시군구 목록이 오면 첫 번째 항목 자동 선택 + // 도(doList): 시군구 목록 첫 항목. 광역시·세종·제주: 전체 useEffect(() => { if (!mergedProps.gugun) return; if (props.defaultRegionAll) return; if (props.defaultValues?.gugun) return; if (!values.sido || values.sido === "ALL") return; + if (!selectedSido) return; + + if (!isDoListBsnm(selectedSido.bsnmCd)) { + setValues((prev) => { + if (prev.gugun) return prev; + return { + ...prev, + gugun: "ALL", + bsnmCd: selectedSido?.bsnmCd || prev.bsnmCd, + }; + }); + return; + } const firstSgg = sggList?.data?.[0]; if (!firstSgg) return; @@ -216,6 +252,7 @@ const SearchForm = (props: SearchFormProps = {}) => { }, [ sggList, values.sido, + selectedSido?.bsnmCd, mergedProps.gugun, props.defaultValues?.gugun, props.defaultRegionAll, @@ -237,10 +274,14 @@ const SearchForm = (props: SearchFormProps = {}) => { ? "" : sidoList?.data?.sidoList?.find((v) => v.sido === value) ?.bsnmCd || ""; + const gugunAll = + props.defaultRegionAll || + value === "ALL" || + !isDoListBsnm(selectedBsnm); setValues((prev) => ({ ...prev, sido: value, - gugun: props.defaultRegionAll || value === "ALL" ? "ALL" : "", + gugun: gugunAll ? "ALL" : "", bsnmCd: selectedBsnm, })); }; diff --git a/apps/st/src/components/module/SidoSggFacilityNameSearchForm.tsx b/apps/st/src/components/module/SidoSggFacilityNameSearchForm.tsx index 84bf949fe..0063d0d4c 100644 --- a/apps/st/src/components/module/SidoSggFacilityNameSearchForm.tsx +++ b/apps/st/src/components/module/SidoSggFacilityNameSearchForm.tsx @@ -8,6 +8,7 @@ import useSWR from "swr"; import { useAlertStore } from "@/shared/lib"; import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; +import { isGugunRequiredSido } from "@/entities/statistics"; import type { SeweragePipeFacilitiesListRequest, SeweragePipeFacilityAdminAreaListRequest, @@ -34,7 +35,6 @@ import { import { getUserInfo } from "../../../../../packages/common/components/src/api"; import type { ColumnInfo } from "../type/DataGrid.type"; import type { SearchFormValues } from "../type/SearchForm.type"; -import { doNameList } from "../util/column/input/DoList"; import CompactDataGrid from "./CompactDataGrid"; @@ -115,9 +115,9 @@ const getSidoListFetcher = async (): Promise> => { }; const getSggAllListFetcher = async ( - key: [string, string], + key: [string, string, "Y" | "N"], ): Promise> => { - return statisticsApi.getSggList(key[1]); + return statisticsApi.getSggList(key[1], { gugunSkipYn: key[2] }); }; type SidoSggFacilityNameSearchFormProps = { @@ -185,8 +185,14 @@ const SidoSggFacilityNameSearchForm = ({ const didAutoFetchRef = useRef(false); const isSavingConnectionRef = useRef(false); - const sggKey: [string, string] | null = - sido && sido !== "ALL" ? ["sgg-all", sido] : null; + const selectedSido = sidoList?.data?.sidoList?.find( + (item) => item.sido === sido, + ); + const gugunSkipYn = selectedSido?.gugunSkipYn ?? "Y"; + const sggKey: [string, string, "Y" | "N"] | null = + sido && sido !== "ALL" && sidoList?.data + ? ["sgg-all", sido, gugunSkipYn] + : null; const { data: sggList } = useSWR>( sggKey, getSggAllListFetcher, @@ -299,7 +305,11 @@ const SidoSggFacilityNameSearchForm = ({ swtrmtFcltCd, sewerBzmnCd: initialSearchValues?.bsnmCd || '', ctpvNm: initialSidoValue || '', - ...(doNameList.includes(initialSidoValue) ? { sggNm: initialGugunValue } : {}), + ...(isGugunRequiredSido(sidoList?.data?.sidoList, { + sido: initialSidoValue, + }) + ? { sggNm: initialGugunValue } + : {}), }; try { diff --git a/apps/st/src/components/module/VerificationResultDialog.tsx b/apps/st/src/components/module/VerificationResultDialog.tsx index 6cb6f0a7e..6cb02643e 100644 --- a/apps/st/src/components/module/VerificationResultDialog.tsx +++ b/apps/st/src/components/module/VerificationResultDialog.tsx @@ -130,7 +130,7 @@ const verificationSubmitResultColumnInfo: ColumnInfo = { export interface VerificationResultDialogQueryContext { sewerBzmnCd: string; ctpvNm: string; - sggNm: string; + sggNm?: string; statsYr: string; statsCd: string; } diff --git a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx index 36846d327..2a34333d8 100644 --- a/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/ExcretaTreatmentFacilityInp.tsx @@ -12,7 +12,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { ValueType } from "realgrid"; import type { RealGridReact } from "realgrid-react"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } 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"; @@ -35,7 +35,16 @@ import type { ImrStatusResponse, SewerageStatusInfo, Sgg, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + resolveNoApplicableForRequest, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -61,7 +70,6 @@ import type { ColumnConfig, } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { ExcretaTreatFactInpColumn } from "@/components/util/column/input/ExcretaTreatFactInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; @@ -94,10 +102,22 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); +} + +function buildNoApplicableForRequest( + values: SearchFormValues | null, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty); } function computeTabToolbarSnapshot( @@ -131,9 +151,8 @@ function buildExcretaTreatmentFacilitySearchParam( const fcltNm = values.fctName?.trim(); return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(fcltNm ? { fcltNm } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; @@ -315,6 +334,8 @@ const EXCRETA_TREATMENT_FACILITY_NAME_GUIDE_MESSAGE = // 하수도 통계 관리 > 07. 분뇨처리시설 const ExcretaTreatmentFacilityInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const { isAdminInfo } = useAdminStore(); @@ -470,7 +491,11 @@ const ExcretaTreatmentFacilityInp = () => { statusInfo: nextStatusInfo, excretaTreatmentFacilityList, } = response.data; - const list = excretaTreatmentFacilityList ?? []; + const list = filterBySelectedAdmdstCd( + excretaTreatmentFacilityList ?? [], + sidoListRef.current, + values, + ); const gridRows = mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm); setRowData(gridRows); @@ -587,12 +612,8 @@ const ExcretaTreatmentFacilityInp = () => { const userInfo = await getUserInfo(); const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD, @@ -628,7 +649,7 @@ const ExcretaTreatmentFacilityInp = () => { if (!(await confirm("저장 하시겠습니까?"))) return; commitGrid(); - const noApplicable = isNotApplicable; + const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0); const currentData = getCurrentRows(); const saveList = noApplicable ? [] @@ -639,7 +660,7 @@ const ExcretaTreatmentFacilityInp = () => { .filter((row) => String(row.fcltSn ?? "").trim() !== "") .map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions)); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -652,11 +673,7 @@ const ExcretaTreatmentFacilityInp = () => { { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: userInfo.userUnqId, ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), noApplicable, @@ -692,9 +709,9 @@ const ExcretaTreatmentFacilityInp = () => { return; } - const noApplicable = isNotApplicable; + const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0); const currentData = getCurrentRows(); - if (!noApplicable && currentData.length === 0) { + if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -719,7 +736,7 @@ const ExcretaTreatmentFacilityInp = () => { } commitGrid(); - const noApplicable = isNotApplicable; + const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0); const currentData = getCurrentRows(); const submitList = noApplicable ? [] @@ -730,19 +747,15 @@ const ExcretaTreatmentFacilityInp = () => { .filter((row) => String(row.fcltSn ?? "").trim() !== "") .map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions)); - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } const statsCdForSubmit = statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD; const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForSubmit, }; @@ -761,11 +774,7 @@ const ExcretaTreatmentFacilityInp = () => { { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: userInfo.userUnqId, laePicNm: submitInfo.laePicNm, laePicTelNo: submitInfo.laePicTelNo, @@ -795,7 +804,7 @@ const ExcretaTreatmentFacilityInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForSubmit, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: @@ -959,7 +968,7 @@ const ExcretaTreatmentFacilityInp = () => { } }; - const searchContextForToolbar = lastSearchedValues ?? searchValues; + const searchContextForToolbar = lastSearchedValues; const toolbarSnapshot = computeTabToolbarSnapshot( statusInfo, rowData.length > 0, @@ -979,23 +988,23 @@ const ExcretaTreatmentFacilityInp = () => { hasSearched && toolbarGroupAllowed && (showSaveInToolbar || showEnterableToolbarButtons); + const tabEnterableForGrid = + (computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable; + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const showNotApplicable = searchLoaded && hasSearched && rowData.length === 0 && - computeTabEnterable(statusInfo, inputPeriodEnterable); - - const tabEnterableForGrid = - (computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable; - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + computeTabEnterable(statusInfo, inputPeriodEnterable) && + submitOk; const verificationQueryContext = (() => { const ctx = lastSearchedValues ?? searchValues; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -1011,6 +1020,7 @@ const ExcretaTreatmentFacilityInp = () => { <>
{ void getLastYearDataFunc()} /> - + {submitOk && } )} - {statusInfo?.laeSbmsnSttsCd === "2" && ( + {statusInfo?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } diff --git a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx index 768a965fc..ed5d70218 100644 --- a/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PrivateSewageFacilityInp.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { useAlertStore, useGlobalProgressStore } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useSearchConditionSidoList } 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"; @@ -31,7 +31,15 @@ import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -50,7 +58,6 @@ import { VerificationResultDialog } from "@/components/module/VerificationResult import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { PrivateSewageDesigStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/PrivateSewageDesigStatusInpColumn"; import { SepticTankStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SepticTankStatusInpColumn"; import { SewageTreatFactStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SewageTreatFactStatusInpColumn"; @@ -102,9 +109,8 @@ function buildPrivateSewageFacilitySearchParam( ): SewageTreatFactStatusRequestParams { return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; } @@ -122,10 +128,14 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); } function computeTabToolbarSnapshot( @@ -161,8 +171,8 @@ function tabRowDataExists( function mapRegionalListToGridRows< T extends { verificationResult?: PrivateSewageFacilityVerificationResult }, ->(list: T[], ctpvNm: string): GridRowWithVerification[] { - return list.map((item) => { +>(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] { + return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const { verificationResult, ...rest } = item; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; if (verificationResult) row.verificationResult = verificationResult; @@ -314,6 +324,8 @@ function resolvePrivateSewageFacilityTab(tabKey: string) { // 하수도 통계 관리 > 08. 개인하수처리시설 const PrivateSewageFacilityInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const { isAdminInfo } = useAdminStore(); @@ -340,9 +352,12 @@ const PrivateSewageFacilityInp = () => { const [footerVerificationByTab, setFooterVerificationByTab] = useState< Map >(new Map()); - const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false); const [showSubmitDialog, setShowSubmitDialog] = useState(false); @@ -403,6 +418,13 @@ const PrivateSewageFacilityInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const applyRowMap = ( @@ -445,6 +467,7 @@ const PrivateSewageFacilityInp = () => { const gridRows = mapRegionalListToGridRows( (sewageTreatFactStatusList ?? []) as SewageTreatFactStatusItem[], commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -476,6 +499,7 @@ const PrivateSewageFacilityInp = () => { const gridRows = mapRegionalListToGridRows( (septicTankStatusList ?? []) as SepticTankStatusItem[], commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -507,6 +531,7 @@ const PrivateSewageFacilityInp = () => { const gridRows = mapRegionalListToGridRows( (privateSewageDesigStatusList ?? []) as PrivateSewageDesigStatusItem[], commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -539,6 +564,7 @@ const PrivateSewageFacilityInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; setLastSearchedValues(null); @@ -582,6 +608,7 @@ const PrivateSewageFacilityInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); } @@ -691,12 +718,8 @@ const PrivateSewageFacilityInp = () => { } const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -779,11 +802,7 @@ const PrivateSewageFacilityInp = () => { const submitInfoBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), laePicNm: submitInfo.laePicNm, laePicTelNo: submitInfo.laePicTelNo, laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, @@ -795,12 +814,8 @@ const PrivateSewageFacilityInp = () => { }; const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForTab, }; @@ -868,7 +883,7 @@ const PrivateSewageFacilityInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForTab, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2", @@ -956,11 +971,7 @@ const PrivateSewageFacilityInp = () => { const baseRequest = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), }; try { @@ -1062,9 +1073,8 @@ const PrivateSewageFacilityInp = () => { const statsCd = tabStatsCdMap.get(activeTab); if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -1095,19 +1105,20 @@ const PrivateSewageFacilityInp = () => { return map; }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]); + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabToolbar = - toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( activeTabStatus, tabRowDataExists(rowDataMap, activeTab), hasSearched, - lastSearchedValues, + searchContextForToolbar, inputPeriodEnterable, isAdminInfo, ); const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } = activeTabToolbar; + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const verificationCellStyle = ({ fieldName, @@ -1166,6 +1177,7 @@ const PrivateSewageFacilityInp = () => { <>
{ void lastYearDataFunc()} /> - + {submitOk && } )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } @@ -1225,7 +1237,7 @@ const PrivateSewageFacilityInp = () => { tabStatusInfoMap.get(tabKey) ?? null, inputPeriodEnterable, ) || isAdminInfo) && inputPeriodEnterable; - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; return ( diff --git a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx index d6b4152cb..4197084bb 100644 --- a/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx +++ b/apps/st/src/components/statistics/input/PublicSewageFacilityInp.tsx @@ -2,7 +2,7 @@ import { useConfirm } from '@frontend-opensource/use-react-hooks'; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from '@/shared/lib'; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } 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'; @@ -23,7 +23,9 @@ import { type SewerageSupplyRateVerificationResult, } from '@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface'; import type { StatisticsInputSubmitInfoFormData } from '@/entities/statistics/model/statistics-input-submit-info-schema'; -import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo } from '@/entities/statistics/model/statistics-interface'; +import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sido } from '@/entities/statistics/model/statistics-interface'; +import { filterBySelectedAdmdstCd, isGugunOkForToolbar, isSubmitOkForToolbar, resolveNoApplicableForRequest, toInputSearchRegion } from '@/entities/statistics'; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from '@common/components'; import { getUserInfo } from '../../../../../../packages/common/components/src/api'; @@ -45,7 +47,6 @@ import { VerificationResultDialog } from '@/components/module/VerificationResult import { VerificationResultHistoryDialog } from '@/components/module/VerificationResultHistoryDialog'; import type { CellStyleCallbackParams, ColumnInfo, ColumnInfoList } from '@/components/type/DataGrid.type'; 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 { fetchColumnGuidesRecord } from '@/components/util/grid/columnGuides'; import { toGridRows } from '@/components/util/grid/gridFunction'; @@ -66,10 +67,22 @@ type TabToolbarSnapshot = { showActionToolbarCard: boolean; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || '') - ? Boolean(values?.gugun && values.gugun !== 'ALL') - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); +} + +function buildNoApplicableForRequest( + values: SearchFormValues | null, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty); } const computeTabEnterable = ( @@ -181,6 +194,8 @@ function validatePrxyCtrtCnInRows(rows: Record[]): stri // 하수도 통계 관리 > 06. 공공하수처리시설 const PublicSewageFacilityInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const { isAdminInfo } = useAdminStore(); @@ -209,6 +224,9 @@ const PublicSewageFacilityInp = () => { /** 탭키별 `engColNm -> columnGuide` 캐시 */ const [columnGuidesByTab, setColumnGuidesByTab] = useState>>({}); const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState>(() => new Map()); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); const [periodCheckList, setPeriodCheckList] = useState([]); const [showDailog, setShowDailog] = useState(false); @@ -247,10 +265,10 @@ const PublicSewageFacilityInp = () => { alert('먼저 조회를 진행해주세요.'); return; } - const treatAsNoApplicable = getNoApplicableForRequest() || isNotApplicable; + const treatAsNoApplicable = getNoApplicableForRequest(); const currentData = st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? []; - if (!treatAsNoApplicable && (!currentData || currentData.length === 0)) { + if (!treatAsNoApplicable && (!currentData || currentData.length === 0) && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) { alert('해당사항 없음을 체크해주세요.'); return; } @@ -298,13 +316,14 @@ const PublicSewageFacilityInp = () => { }; const getNoApplicableForRequest = (): boolean => { - const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; - const searchContextForToolbar = lastSearchedValues ?? searchValues; - const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); - const enterable = computeTabEnterable(activeTabStatus, inputPeriodEnterable); - const showEnterableToolbarButtons = enterable && gugunOk; - const noApplicableVisible = showEnterableToolbarButtons && showIsNotApplicable; - return noApplicableVisible ? Boolean(isNotApplicable) : false; + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; + const currentData = + st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? []; + return buildNoApplicableForRequest( + searchContextForToolbar, + Boolean(isNotApplicable), + buildFacilitySaveList(currentData).length === 0, + ); }; const isCheckedRow = (row: Record): boolean => { @@ -349,7 +368,7 @@ const PublicSewageFacilityInp = () => { return; } const noApplicable = getNoApplicableForRequest(); - const treatAsNoApplicable = noApplicable || isNotApplicable; + const treatAsNoApplicable = noApplicable; const currentData = st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? []; @@ -360,7 +379,7 @@ const PublicSewageFacilityInp = () => { } const submitList = treatAsNoApplicable ? [] : buildFacilitySaveList(currentData); - if (!treatAsNoApplicable && submitList.length === 0) { + if (!treatAsNoApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) { alert('해당사항 없음을 체크해주세요.'); return; } @@ -373,8 +392,7 @@ const PublicSewageFacilityInp = () => { const requestData = { statsYr: lastSearchedValues.year ?? '', ctpvNm: lastSearchedValues.sido ?? '', - sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '', - sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: userInfo.userUnqId, noApplicable, laePicNm: submitInfo.laePicNm, @@ -407,7 +425,7 @@ const PublicSewageFacilityInp = () => { setGlobalProgressLabel('시계열 검증 진행 중...'); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? '', + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? '', stats_cd: statsCdForTab, stats_yr: lastSearchedValues.year ?? '', lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === '4' ? '4' : '2', @@ -435,9 +453,8 @@ const PublicSewageFacilityInp = () => { ); await openVerificationResultDialogIfNeeded( { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? '', - sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''), statsYr: lastSearchedValues.year ?? '', statsCd: statsCdForTab, }, @@ -452,9 +469,8 @@ const PublicSewageFacilityInp = () => { ); await openVerificationResultDialogIfNeeded( { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? '', - sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''), statsYr: lastSearchedValues.year ?? '', statsCd: statsCdForTab, }, @@ -476,9 +492,8 @@ const PublicSewageFacilityInp = () => { ); await openVerificationResultDialogIfNeeded( { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? '', - sggNm: lastSearchedValues.gugun === 'ALL' ? '' : (lastSearchedValues.gugun ?? ''), statsYr: lastSearchedValues.year ?? '', statsCd: statsCdForTab, }, @@ -508,7 +523,7 @@ const PublicSewageFacilityInp = () => { if (activeTab === 'ST00060002') { const statusCodeResponse = await statisticsApi.CheckStatusCode({ - sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', + sewerBzmnCd: toInputSearchRegion(sidoListRef.current, lastSearchedValues).sewerBzmnCd, statsCd: 'ST00060001', statsYr: lastSearchedValues.year ?? '' }); @@ -585,7 +600,7 @@ const PublicSewageFacilityInp = () => { mainFacilityMappingSaveDraftRef.current = { statsYr: lastSearchedValues.year ?? '', - sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', + sewerBzmnCd: toInputSearchRegion(sidoListRef.current, lastSearchedValues).sewerBzmnCd, admdstCd: String(selectedMain.admdstCd ?? ''), admdstNm: String(selectedMain.admdstNm ?? ''), mainFcltSn: String(selectedMain.fcltSn ?? ''), @@ -616,7 +631,7 @@ const PublicSewageFacilityInp = () => { if (activeTab === 'ST00060001') { const noApplicable = getNoApplicableForRequest(); - const treatAsNoApplicable = noApplicable || isNotApplicable; + const treatAsNoApplicable = noApplicable; const currentData = st60001GridGetRowsRef.current?.() ?? rowDataMap.get('ST00060001') ?? []; const prxyCtrtCnError = validatePrxyCtrtCnInRows(currentData); @@ -625,7 +640,7 @@ const PublicSewageFacilityInp = () => { return; } const saveList = treatAsNoApplicable ? [] : buildFacilitySaveList(currentData); - if (!treatAsNoApplicable && saveList.length === 0) { + if (!treatAsNoApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(activeTab) ?? lastSearchedValues)) { alert('해당사항 없음을 체크해주세요.'); return; } @@ -636,8 +651,7 @@ const PublicSewageFacilityInp = () => { const param: PublicSewageFacilitySaveRequest = { statsYr: lastSearchedValues.year ?? '', ctpvNm: lastSearchedValues.sido ?? '', - sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '', - sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: userInfo.userUnqId, ...(isAdminInfo ? { superAdmin: 'superAdmin' } : {}), noApplicable, @@ -724,12 +738,18 @@ const PublicSewageFacilityInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const buildPublicSewageFacilityParams = (values: SearchFormValues): PublicSewageFacilityRequestParams => ({ ctpvNm: values.sido, - sggNm: values.gugun === 'ALL' ? '' : values.gugun, - sewerBzmnCd: values.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, values), fcltNm: values.fctName, statsYr: values.year, }); @@ -759,7 +779,11 @@ const PublicSewageFacilityInp = () => { const statusInfo = response.data.statusInfo ?? null; const statsCd = response.data.commonInfo.statsCd || tabKey; const commonInfo = response.data.commonInfo; - const list = response.data.publicSewageFacilityList ?? []; + const list = filterBySelectedAdmdstCd( + response.data.publicSewageFacilityList ?? [], + sidoListRef.current, + searchContext, + ); const convertedData = list.map((item) => ({ ctpvNm: commonInfo.ctpvNm, ...item, @@ -872,8 +896,7 @@ const PublicSewageFacilityInp = () => { const request = { statsYr: lastSearchedValues.year ?? '', ctpvNm: lastSearchedValues.sido ?? '', - sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '', - sewerBzmnCd: lastSearchedValues.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: userInfo.userUnqId, }; @@ -1005,6 +1028,7 @@ const PublicSewageFacilityInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); } @@ -1069,6 +1093,7 @@ const PublicSewageFacilityInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setInputPeriodEnterable(true); periodEnterableRef.current = true; @@ -1095,9 +1120,8 @@ const PublicSewageFacilityInp = () => { const userInfo = await getUserInfo(); const statsCdForCancel = tabStatsCdMap.get(activeTab) ?? activeTab; const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues?.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues?.sido ?? '', - sggNm: lastSearchedValues?.gugun === 'ALL' ? '' : lastSearchedValues?.gugun ?? '', statsYr: lastSearchedValues?.year ?? '', mdfrId: userInfo.userUnqId, statsCd: statsCdForCancel, @@ -1155,7 +1179,9 @@ const PublicSewageFacilityInp = () => { const param: PublicSewageMappingTargetsRequestParams = { sewerBzmnCd: searchParamRef.current.sewerBzmnCd ?? '', ctpvNm: searchParamRef.current.ctpvNm ?? '', - sggNm: searchParamRef.current.sggNm ?? '', + ...(searchParamRef.current.sggNm != null + ? { sggNm: searchParamRef.current.sggNm } + : {}), statsYr: searchParamRef.current.statsYr ?? '', fcltSn, }; @@ -1195,8 +1221,9 @@ const PublicSewageFacilityInp = () => { return String(row.fcltSn ?? '') === selectedMainFacilitySn ? 'rg-cell-selected-row' : undefined; }; - const searchContextForToolbar = lastSearchedValues ?? searchValues; + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const gugunOkOrNotDoList = buildGugunOkForToolbar(searchContextForToolbar); + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabToolbar = computeTabToolbarSnapshot( activeTabStatus, @@ -1241,9 +1268,8 @@ const PublicSewageFacilityInp = () => { const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === 'ALL' ? '' : (ctx.gugun ?? ''), statsYr: ctx.year, statsCd, }; @@ -1264,6 +1290,7 @@ const PublicSewageFacilityInp = () => { <>
{ )} {showEnterableToolbarButtons && !isMainFacilityTab && ( <> - {showIsNotApplicable && } + {showIsNotApplicable && submitOk && } - + {submitOk && } )} - {!isMainFacilityTab && activeTabStatus?.laeSbmsnSttsCd === '2' && ( + {!isMainFacilityTab && activeTabStatus?.laeSbmsnSttsCd === '2' && submitOk && ( )} {showSaveInToolbar && } diff --git a/apps/st/src/components/statistics/input/PumpingStationInp.tsx b/apps/st/src/components/statistics/input/PumpingStationInp.tsx index 9f79b0e67..c48128627 100644 --- a/apps/st/src/components/statistics/input/PumpingStationInp.tsx +++ b/apps/st/src/components/statistics/input/PumpingStationInp.tsx @@ -12,7 +12,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { ValueType } from "realgrid"; import type { RealGridReact } from "realgrid-react"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } 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"; @@ -35,7 +35,16 @@ import type { ImrStatusResponse, SewerageStatusInfo, Sgg, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + resolveNoApplicableForRequest, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -61,7 +70,6 @@ import type { ColumnConfig, } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { PumpingStationInpColumn } from "@/components/util/column/input/PumpingStationInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat"; @@ -94,10 +102,22 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); +} + +function buildNoApplicableForRequest( + values: SearchFormValues | null, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty); } function computeTabToolbarSnapshot( @@ -131,9 +151,8 @@ function buildPumpingStationSearchParam( const fcltNm = values.fctName?.trim(); return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(fcltNm ? { fcltNm } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; @@ -309,6 +328,8 @@ const PUMPING_STATION_FACILITY_NAME_GUIDE_MESSAGE = // 하수도 통계 관리 > 05. 펌프장 (SewageReservoirPumpInp 단일 탭 패턴) const PumpingStationInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const { isAdminInfo } = useAdminStore(); @@ -460,7 +481,11 @@ const PumpingStationInp = () => { const { commonInfo, statusInfo: nextStatusInfo, pumpingStationList } = response.data; - const list = pumpingStationList ?? []; + const list = filterBySelectedAdmdstCd( + pumpingStationList ?? [], + sidoListRef.current, + values, + ); const gridRows = mapPumpingStationListToGridRows(list, commonInfo.ctpvNm); setPumpingStationDeleteList([]); @@ -578,12 +603,8 @@ const PumpingStationInp = () => { const userInfo = await getUserInfo(); const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCd || PUMPING_STATION_STATS_CD, @@ -618,9 +639,9 @@ const PumpingStationInp = () => { return; } - const noApplicable = isNotApplicable; + const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0); const currentData = getCurrentRows(); - if (!noApplicable && currentData.length === 0) { + if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -647,7 +668,7 @@ const PumpingStationInp = () => { if (!(await confirm("저장 하시겠습니까?"))) return; commitGrid(); - const noApplicable = isNotApplicable; + const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0); const currentData = getCurrentRows(); const saveList = noApplicable ? [] @@ -658,7 +679,7 @@ const PumpingStationInp = () => { .filter((row) => String(row.fcltSn ?? "").trim() !== "") .map((row) => buildPumpingStationSaveItem(row, sggOptions)); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -669,11 +690,7 @@ const PumpingStationInp = () => { const response = await statisticsInputPumpingStationApi.savePumpingStationList({ statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: userInfo.userUnqId, ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), noApplicable, @@ -707,7 +724,7 @@ const PumpingStationInp = () => { } commitGrid(); - const noApplicable = isNotApplicable; + const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0); const currentData = getCurrentRows(); const submitList = noApplicable ? [] @@ -716,19 +733,15 @@ const PumpingStationInp = () => { .filter((row) => String(row.fcltSn ?? "").trim() !== "") .map((row) => buildPumpingStationSaveItem(row, sggOptions)); - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } const statsCdForSubmit = statsCd || PUMPING_STATION_STATS_CD; const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForSubmit, }; @@ -745,11 +758,7 @@ const PumpingStationInp = () => { const response = await statisticsInputPumpingStationApi.submitPumpingStationList({ statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: userInfo.userUnqId, laePicNm: submitInfo.laePicNm, laePicTelNo: submitInfo.laePicTelNo, @@ -778,7 +787,7 @@ const PumpingStationInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForSubmit, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: @@ -942,7 +951,7 @@ const PumpingStationInp = () => { } }; - const searchContextForToolbar = lastSearchedValues ?? searchValues; + const searchContextForToolbar = lastSearchedValues; const toolbarSnapshot = computeTabToolbarSnapshot( statusInfo, rowData.length > 0, @@ -962,23 +971,23 @@ const PumpingStationInp = () => { hasSearched && toolbarGroupAllowed && (showSaveInToolbar || showEnterableToolbarButtons); + const tabEnterableForGrid = + (computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable; + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const showNotApplicable = searchLoaded && hasSearched && rowData.length === 0 && - computeTabEnterable(statusInfo, inputPeriodEnterable); - - const tabEnterableForGrid = - (computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable; - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + computeTabEnterable(statusInfo, inputPeriodEnterable) && + submitOk; const verificationQueryContext = (() => { const ctx = lastSearchedValues ?? searchValues; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -994,6 +1003,7 @@ const PumpingStationInp = () => { <>
{ void getLastYearDataFunc()} /> - + {submitOk && } )} - {statusInfo?.laeSbmsnSttsCd === "2" && ( + {statusInfo?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } diff --git a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx index 2e1be284d..82a023d6f 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaRelatedInp.tsx @@ -5,7 +5,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { ValueType } from "realgrid"; import type { RealGridReact } from "realgrid-react"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } 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"; @@ -34,7 +34,16 @@ import type { ImrStatusResponse, SewerageStatusInfo, Sgg, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + resolveNoApplicableForRequest, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -63,7 +72,6 @@ import type { ColumnInfo, } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { ExcretaRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/ExcretaRelatedIndustryInpColumn"; import { PrivateSewageTreatRelatedIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PrivateSewageTreatRelatedIndustryInpColumn"; import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn"; @@ -158,10 +166,22 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); +} + +function buildNoApplicableForRequest( + values: SearchFormValues | null, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty); } function computeTabToolbarSnapshot( @@ -543,9 +563,8 @@ function buildSewageExcretaRelatedSearchParam( ): SewageExcretaRelatedRequestParams { return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; } @@ -553,8 +572,9 @@ function buildSewageExcretaRelatedSearchParam( function mapListToGridRows( list: T[] | undefined, ctpvNm: string, + values?: SearchFormValues | null, ): GridRowWithVerification[] { - return (list ?? []).map((item) => { + return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification; if (item.verificationResult) { row.verificationResult = item.verificationResult; @@ -590,6 +610,8 @@ async function waitForImrJobCompleted( // 하수도 통계 관리 > 12. 하수 및 분뇨관련산업 const SewageExcretaRelatedInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const isAdminInfo = useAdminStore((state) => state.isAdminInfo); @@ -606,7 +628,7 @@ const SewageExcretaRelatedInp = () => { const [tabStatusInfoMap, setTabStatusInfoMap] = useState< Map >(new Map()); - const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); const [columnGuidesByTab, setColumnGuidesByTab] = useState< @@ -616,6 +638,9 @@ const SewageExcretaRelatedInp = () => { const [lastSearchedValues, setLastSearchedValues] = useState( null, ); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [hasSearched, setHasSearched] = useState(false); const [isLoading, setIsLoading] = useState(false); const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); @@ -739,6 +764,13 @@ const SewageExcretaRelatedInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const markTabLoaded = (tabKey: string) => { @@ -854,6 +886,7 @@ const SewageExcretaRelatedInp = () => { const gridRows = mapListToGridRows( publicInstitutionSewerIndustryList, commonInfo.ctpvNm, + searchContext, ); syncPublicInstitutionArticleTracking( tabKey, @@ -900,6 +933,7 @@ const SewageExcretaRelatedInp = () => { const gridRows = mapListToGridRows( publicInstitutionExcretaIndustryList, commonInfo.ctpvNm, + searchContext, ); syncPublicInstitutionArticleTracking( tabKey, @@ -942,7 +976,7 @@ const SewageExcretaRelatedInp = () => { return false; } const { commonInfo, statusInfo, excretaRelatedIndustryList } = response.data; - const gridRows = mapListToGridRows(excretaRelatedIndustryList, commonInfo.ctpvNm); + const gridRows = mapListToGridRows(excretaRelatedIndustryList, commonInfo.ctpvNm, searchContext); applyRowMap(mergeIntoExistingMap, (m) => { m.set(ExcretaRelatedIndustryInpColumn.tabKey, gridRows); }); @@ -979,6 +1013,7 @@ const SewageExcretaRelatedInp = () => { const gridRows = mapListToGridRows( privateSewageTreatRelatedIndustryList, commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { m.set(PrivateSewageTreatRelatedIndustryInpColumn.tabKey, gridRows); @@ -1010,6 +1045,7 @@ const SewageExcretaRelatedInp = () => { setRowDataMap(new Map()); setTabStatusInfoMap(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; setLastSearchedValues(null); @@ -1075,6 +1111,7 @@ const SewageExcretaRelatedInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setIsNotApplicableByTab(new Map()); setDeleteListByTab(new Map()); clearArticleOgnzSnTracking( @@ -1139,9 +1176,8 @@ const SewageExcretaRelatedInp = () => { const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey; const requestData: cancelSubmitRequest = { - sewerBzmnCd: ctx.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido ?? "", - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -1232,10 +1268,10 @@ const SewageExcretaRelatedInp = () => { } commitActiveGrid(); - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); if (NOT_APPLICABLE_TAB_KEYS.has(tabKey)) { - if (!noApplicable && currentData.length === 0) { + if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1269,18 +1305,14 @@ const SewageExcretaRelatedInp = () => { const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey); const isPublicInstitutionTab = ARTICLE_EDIT_TAB_KEYS.has(tabKey); - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const mapRows = rowDataMap.get(tabKey) ?? []; const searchContextBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), }; let submitList: @@ -1341,7 +1373,7 @@ const SewageExcretaRelatedInp = () => { ) .map((row) => buildPublicInstitutionIndustrySaveItem(row, list)); - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1448,7 +1480,7 @@ const SewageExcretaRelatedInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForTab, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: @@ -1527,18 +1559,14 @@ const SewageExcretaRelatedInp = () => { const isDistrictRowTab = DISTRICT_ROW_TAB_KEYS.has(tabKey); const isPublicInstitutionTab = ARTICLE_EDIT_TAB_KEYS.has(tabKey); - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const mapRows = rowDataMap.get(tabKey) ?? []; const baseSaveRequest = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ...(isPublicInstitutionTab && noApplicable ? { noApplicable: true as const } : {}), @@ -1621,7 +1649,7 @@ const SewageExcretaRelatedInp = () => { ) .map((row) => buildPublicInstitutionIndustrySaveItem(row, list)); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1885,11 +1913,10 @@ const SewageExcretaRelatedInp = () => { [publicInstitutionArticleCellStyleCallback], ); - const searchContextForToolbar = lastSearchedValues ?? searchValues; + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab); const activeTabToolbar = - toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( activeTabStatus, activeTabHasRowData, @@ -1907,10 +1934,13 @@ const SewageExcretaRelatedInp = () => { (activeTabToolbar.showActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab))); const showArticleEditButtons = ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons; + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const showNotApplicableForActiveTab = NOT_APPLICABLE_TAB_KEYS.has(activeTab) && loadedTabKeys.has(activeTab) && - shouldShowNotApplicable(activeTab, activeTabHasRowData, activeTabStatus); + shouldShowNotApplicable(activeTab, activeTabHasRowData, activeTabStatus) && + submitOk; const showSecondaryToolbarCard = hasSearched && toolbarGroupAllowed && @@ -1928,16 +1958,14 @@ const SewageExcretaRelatedInp = () => { }, [isNotApplicableByTab, setIsNotApplicableForTab], ); - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); const verificationQueryContext = (() => { const ctx = lastSearchedValues ?? searchValues; const statsCd = tabStatsCdMap.get(activeTab) ?? activeTab; if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -1958,6 +1986,7 @@ const SewageExcretaRelatedInp = () => { <>
{ void lastYearDataFunc()} /> - + {submitOk && } )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } diff --git a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx index 84f53ecc5..f4e618247 100644 --- a/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx +++ b/apps/st/src/components/statistics/input/SewageExcretaSlurgeInp.tsx @@ -13,7 +13,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { ValueType } from "realgrid"; import type { RealGridReact } from "realgrid-react"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } 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"; @@ -37,7 +37,15 @@ import { } from "@/entities/statistics/model/input/statistics-input-sewage-excreta-slurge-interface"; import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface"; import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema"; -import type { ApiResponse, cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sgg } from "@/entities/statistics/model/statistics-interface"; +import type { ApiResponse, cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sgg, Sido } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + resolveNoApplicableForRequest, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -67,7 +75,6 @@ import type { ColumnInfo, } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { DIGESTOR_OPERATING_STATUS_EN_YN_FIELD, DigestorOperatingStatusInpColumn, @@ -163,9 +170,8 @@ function buildSewageExcretaSlurgeSearchParam( ): SewageExcretaSlurgeRequestParams { return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), fcltNm: values.fctName?.trim() || undefined, ...(overrides?.mode ? { mode: overrides.mode } : {}), }; @@ -174,8 +180,9 @@ function buildSewageExcretaSlurgeSearchParam( function mapListToGridRows( list: T[] | undefined, ctpvNm: string, + values?: SearchFormValues | null, ): GridRowWithVerification[] { - return (list ?? []).map((item) => { + return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const row = toGridRows([{ ctpvNm, ...item }])[0] as GridRowWithVerification; if (item.verificationResult) { row.verificationResult = item.verificationResult; @@ -345,10 +352,22 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); +} + +function buildNoApplicableForRequest( + values: SearchFormValues | null, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty); } function computeTabToolbarSnapshot( @@ -491,7 +510,7 @@ function buildSlurgeProcessingFacStatusSaveItem( itslfTs1AltdQnt: toGridNumber(row.itslfTs1AltdQnt), itslfTs1DryQnt: toGridNumber(row.itslfTs1DryQnt), itslfTs1FuelQnt: toGridNumber(row.itslfTs1FuelQnt), - itslfTs2CmpstgQnt: toGridNumber(row.itslfTs2CmpstgQnt), + itslfTs1CmpstgQnt: toGridNumber(row.itslfTs1CmpstgQnt), cmpsPrcsFcltBzentyNm: nullableTrimmedString(row.cmpsPrcsFcltBzentyNm), incnrPrcsLfvlm: toGridNumber(row.incnrPrcsLfvlm), incnrPrcsPrdctRmtrlQnt: toGridNumber(row.incnrPrcsPrdctRmtrlQnt), @@ -644,6 +663,8 @@ async function waitForImrJobCompleted( // 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리 const SewageExcretaSlurgeInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { isAdminInfo } = useAdminStore(); const { confirm } = useConfirm(); const { setMessage: alert } = useAlertStore((state) => state); @@ -678,6 +699,9 @@ const SewageExcretaSlurgeInp = () => { const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [sggOptions, setSggOptions] = useState([]); const [loadedTabKeys, setLoadedTabKeys] = useState>(() => new Set()); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); @@ -790,6 +814,13 @@ const SewageExcretaSlurgeInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const getTabGridRows = (tabKey: string): Record[] => @@ -923,7 +954,7 @@ const SewageExcretaSlurgeInp = () => { return false; } const { commonInfo, statusInfo, slurgeProcessingFacStatusList } = response.data; - const gridRows = mapListToGridRows(slurgeProcessingFacStatusList, commonInfo.ctpvNm); + const gridRows = mapListToGridRows(slurgeProcessingFacStatusList, commonInfo.ctpvNm, searchContext); syncSlurgeFacStatusArticleFcltSnTracking(gridRows, fromPreYear); applyRowMap(mergeIntoExistingMap, (m) => { m.set(SlurgeProcessingFacStatusInpColumn.tabKey, gridRows); @@ -958,7 +989,7 @@ const SewageExcretaSlurgeInp = () => { return false; } const { commonInfo, statusInfo, digestorOperatingStatusList } = response.data; - const gridRows = mapListToGridRows(digestorOperatingStatusList, commonInfo.ctpvNm); + const gridRows = mapListToGridRows(digestorOperatingStatusList, commonInfo.ctpvNm, searchContext); if (!fromPreYear) { publicSewageFacilitiesAvailableRef.current = gridRows.length > 0; if (gridRows.length === 0 && overrides?.alertIfDigestorEmpty) { @@ -1002,6 +1033,7 @@ const SewageExcretaSlurgeInp = () => { const gridRows = mapListToGridRows( sewageSlurgeProcessingFacStatusList, commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { m.set(SewageSlurgeProcessingFacStatusInpColumn.tabKey, gridRows); @@ -1039,6 +1071,7 @@ const SewageExcretaSlurgeInp = () => { const gridRows = mapListToGridRows( excretaSlurgeProcessingStatusList, commonInfo.ctpvNm, + searchContext, ); if (!fromPreYear) { excretaTreatmentFacilitiesAvailableRef.current = gridRows.length > 0; @@ -1080,6 +1113,7 @@ const SewageExcretaSlurgeInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; setLastSearchedValues(null); @@ -1139,6 +1173,7 @@ const SewageExcretaSlurgeInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); loadedTabKeysRef.current.clear(); setLoadedTabKeys(new Set()); setDeleteListByTab(new Map()); @@ -1247,9 +1282,9 @@ const SewageExcretaSlurgeInp = () => { } commitActiveGrid(); - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); - if (!noApplicable && currentData.length === 0) { + if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1277,7 +1312,7 @@ const SewageExcretaSlurgeInp = () => { commitActiveGrid(); - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const mapRows = rowDataMap.get(tabKey) ?? []; @@ -1335,6 +1370,7 @@ const SewageExcretaSlurgeInp = () => { if ( !noApplicable && + buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues) && ((isSlurgeFacTab && slurgeSubmitList.length === 0) || (isDigestorTab && digestorSubmitList.length === 0) || (isSewageSlurgeFacTab && sewageSlurgeFacSubmitList.length === 0) || @@ -1356,12 +1392,8 @@ const SewageExcretaSlurgeInp = () => { : SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD); const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForSubmit, }; @@ -1390,11 +1422,7 @@ const SewageExcretaSlurgeInp = () => { const submitRequestBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: "", laePicNm: submitInfo.laePicNm, laePicTelNo: submitInfo.laePicTelNo, @@ -1455,7 +1483,7 @@ const SewageExcretaSlurgeInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForSubmit, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: @@ -1532,12 +1560,8 @@ const SewageExcretaSlurgeInp = () => { } const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -1585,7 +1609,7 @@ const SewageExcretaSlurgeInp = () => { commitActiveGrid(); - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const mapRows = rowDataMap.get(tabKey) ?? []; @@ -1603,11 +1627,7 @@ const SewageExcretaSlurgeInp = () => { const baseSaveRequest = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ...(noApplicable ? { noApplicable: true } : {}), }; @@ -1638,7 +1658,7 @@ const SewageExcretaSlurgeInp = () => { .filter((row) => isPersistedArticleRowForTab(tabKey, row)) .map((row) => buildSlurgeProcessingFacStatusSaveItem(row, list)); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1665,7 +1685,7 @@ const SewageExcretaSlurgeInp = () => { buildDigestorOperatingStatusSaveItem(row, list), ); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1691,7 +1711,7 @@ const SewageExcretaSlurgeInp = () => { .filter((row) => isPersistedArticleRowForTab(tabKey, row)) .map((row) => buildSewageSlurgeProcessingFacStatusSaveItem(row, list)); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1718,7 +1738,7 @@ const SewageExcretaSlurgeInp = () => { buildExcretaSlurgeProcessingStatusSaveItem(row, list), ); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1977,7 +1997,7 @@ const SewageExcretaSlurgeInp = () => { [digestorOperatingCellStyleCallback], ); - const searchContextForToolbar = lastSearchedValues ?? searchValues; + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabHasRowData = tabRowDataExists(rowDataMap, activeTab); const activeTabToolbar = computeTabToolbarSnapshot( @@ -1998,6 +2018,7 @@ const SewageExcretaSlurgeInp = () => { const showSaveInToolbar = baseShowSaveInToolbar && toolbarGroupAllowed; const showEnterableToolbarButtons = baseShowEnterableToolbarButtons && toolbarGroupAllowed; + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const showActionToolbarCard = toolbarGroupAllowed && (baseShowActionToolbarCard || (hasSearched && loadedTabKeys.has(activeTab))); @@ -2005,7 +2026,8 @@ const SewageExcretaSlurgeInp = () => { ARTICLE_EDIT_TAB_KEYS.has(activeTab) && showEnterableToolbarButtons; const showNotApplicableForActiveTab = loadedTabKeys.has(activeTab) && - shouldShowNotApplicable(activeTabHasRowData, activeTabStatus); + shouldShowNotApplicable(activeTabHasRowData, activeTabStatus) && + submitOk; const showSecondaryToolbarCard = hasSearched && toolbarGroupAllowed && @@ -2039,9 +2061,8 @@ const SewageExcretaSlurgeInp = () => { const statsCd = tabStatsCdMap.get(activeTab); if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -2062,6 +2083,7 @@ const SewageExcretaSlurgeInp = () => { <>
{ void lastYearDataFunc()} /> - + {submitOk && } )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } diff --git a/apps/st/src/components/statistics/input/SewagePipeInp.tsx b/apps/st/src/components/statistics/input/SewagePipeInp.tsx index 9a09d507d..3fed52ee6 100644 --- a/apps/st/src/components/statistics/input/SewagePipeInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeInp.tsx @@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect,useMemo, useRef, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib"; import { STATISTICS_INPUT_URL_PATH, verificationSearchToFormValues, @@ -40,7 +40,15 @@ import type { ImrStatusResponse, SewerageStatusInfo, SubmitExceptionRequest, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -61,7 +69,6 @@ import { VerificationResultDialog } from "@/components/module/VerificationResult import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import type { ColumnConfig, ColumnInfo, ColumnInfoList, ColumnLayoutItem } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { FactInstallStatusInpColumn } from "@/components/util/column/input/sewagePipe/FactInstallStatusInpColumn"; import { ManholeStatusInpColumn } from "@/components/util/column/input/sewagePipe/ManholeStatusInpColumn"; import { PipeDiameterStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeDiameterStatusInpColumn"; @@ -101,9 +108,8 @@ function buildSewagePipeSearchParam( ): SeweragePipeRequestParams { return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; } @@ -431,10 +437,14 @@ type TabToolbarSnapshot = { showActionToolbarCard: boolean; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); } /** @@ -466,6 +476,8 @@ function computeTabToolbarSnapshot( // 하수도 통계 관리 > 02. 하수관로 const SewagePipeInp = () => { + const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const [rowDataMap, setRowDataMap] = useState[]>>(new Map()); const { setMessage: alert } = useAlertStore((state) => state); const [activeTab, setActiveTab] = useState(SewagePipeInpColumnInfoList[0]?.tabKey ?? "ST00020001"); @@ -494,7 +506,10 @@ const SewagePipeInp = () => { /** checkInputPeriod — columnInfo useMemo가 입력기간 변경을 반영하도록 state로도 유지 */ const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true); /** 탭별 조회 시점의 상단 버튼 노출 조건(해당 탭을 다시 조회할 때만 그 탭 항목 갱신) */ - const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState>( + const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState>( + () => new Map(), + ); + const [searchContextByTab, setSearchContextByTab] = useState>( () => new Map(), ); const { confirm } = useConfirm(); @@ -539,6 +554,7 @@ const SewagePipeInp = () => { const inputFocusNonce = useStatisticsInputFocusStore((s) => s.nonce); useLayoutEffect(() => { if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE) return; + if (!isReady) return; const pending = useStatisticsInputFocusStore .getState() .takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE); @@ -547,12 +563,12 @@ const SewagePipeInp = () => { activeTabRef.current = tabKey; setActiveTab(tabKey); if (search) { - const values = verificationSearchToFormValues(search); + const values = verificationSearchToFormValues(search, sidoList); setVerificationSearchDefaults(values); postVerificationSearchRef.current = { values, tabKey }; setSearchFormRemountKey((k) => k + 1); } - }, [activeTabId, inputFocusNonce]); + }, [activeTabId, inputFocusNonce, isReady, sidoList]); useEffect(() => { if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE) return; @@ -592,6 +608,7 @@ const SewagePipeInp = () => { setHasSearched(false); setInputPeriodEnterable(true); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); searchParamRef.current = null; searchDoneRef.current = false; lazyTabLoggedRef.current.clear(); @@ -681,6 +698,13 @@ const SewagePipeInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; /** mergeIntoExistingMap: true면 기존 맵에 병합(다른 탭 rowData·검색 후 다른 탭 캐시 유지). false면 맵 전체 교체 */ @@ -694,7 +718,7 @@ const SewagePipeInp = () => { const searchConditionKey = JSON.stringify({ statsYr: searchParam.statsYr ?? "", ctpvNm: searchParam.ctpvNm ?? "", - sggNm: searchParam.sggNm ?? "", + sggNm: values?.gugun === "ALL" ? "" : (values?.gugun ?? searchParam.sggNm ?? ""), sewerBzmnCd: searchParam.sewerBzmnCd ?? "", }); const applyRowMap = (apply: (m: Map[]>) => void) => { @@ -710,12 +734,14 @@ const SewagePipeInp = () => { setRowDataMap(next); } }; + const filterFetched = (list: readonly T[] | null | undefined) => + filterBySelectedAdmdstCd(list, sidoListRef.current, values); if (tabKey === 'ST00020001') { const response = await statisticsInputSewPipeApiService.getRegionalFactStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = (response.data.regionalFactStatusList ?? []).map((d) => ({ + const convertedData = filterFetched(response.data.regionalFactStatusList).map((d) => ({ ctpvNm: commonInfo.ctpvNm, ...d, })); @@ -745,7 +771,7 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getFactInstallStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = (response.data.factInstallStatusList ?? []).map((d) => ({ + const convertedData = filterFetched(response.data.factInstallStatusList).map((d) => ({ ctpvNm: commonInfo.ctpvNm, ...d, })); @@ -792,7 +818,7 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getPipeTypeStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = (response.data.pipeTypeStatusList ?? []).map((d) => + const convertedData = filterFetched(response.data.pipeTypeStatusList).map((d) => normalizePipeTypeStatusRow(d, commonInfo.ctpvNm), ); applyRowMap((m) => m.set('ST00020003', toGridRows(convertedData))); @@ -825,7 +851,7 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getPipeDiameterStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = (response.data.pipeDiameterStatusList ?? []).map((d) => ({ + const convertedData = filterFetched(response.data.pipeDiameterStatusList).map((d) => ({ ctpvNm: commonInfo.ctpvNm, ...d, })); @@ -855,7 +881,7 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getManholeStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const convertedData = (response.data.manholeStatusList ?? []).map((d) => + const convertedData = filterFetched(response.data.manholeStatusList).map((d) => normalizeManholeStatusRow(d, commonInfo.ctpvNm), ); applyRowMap((m) => m.set('ST00020005', toGridRows(convertedData))); @@ -884,10 +910,10 @@ const SewagePipeInp = () => { const response = await statisticsInputSewPipeApiService.getConnectionStatus(searchParam); if (response.status === 'success') { const commonInfo = response.data.commonInfo; - const facilityConvertedData = (response.data.facilityConnectionList ?? []).map((d) => ({ + const facilityConvertedData = filterFetched(response.data.facilityConnectionList).map((d) => ({ ...d, })); - const adminConvertedData = (response.data.adminAreaConnectionList ?? []).map((d) => ({ + const adminConvertedData = filterFetched(response.data.adminAreaConnectionList).map((d) => ({ ...d, ctpvNm: d.ctpvNm || commonInfo.ctpvNm, })); @@ -958,6 +984,7 @@ const SewagePipeInp = () => { setFooterVerificationByTab(new Map()); setStatsCd(""); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); } lazyTabLoggedRef.current.clear(); @@ -1119,9 +1146,8 @@ const SewagePipeInp = () => { const params: SubmitExceptionRequest = { ...submitInfo, - sewerBzmnCd: searchValues?.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues?.sido ?? "", - sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ""), statsYr: searchValues?.year ?? "", userId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -1204,8 +1230,7 @@ const SewagePipeInp = () => { const submitBase = { statsYr: values.year ?? "", ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), userId: userInfo.userUnqId, laePicNm: submitInfo.laePicNm, laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, @@ -1463,7 +1488,7 @@ const SewagePipeInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: values.bsnmCd ?? "", + sewer_bzmn_cd: values?.bsnmCd ?? '', stats_cd: statsCdForTab, stats_yr: values.year ?? "", lae_sbmsn_stts_cd: statusForTab?.laeSbmsnSttsCd === "4" ? "4" : "2", @@ -1493,9 +1518,8 @@ const SewagePipeInp = () => { ); await openVerificationResultDialogIfNeeded( { - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), statsYr: values.year ?? "", statsCd: statsCdForTab, }, @@ -1512,9 +1536,8 @@ const SewagePipeInp = () => { ); await openVerificationResultDialogIfNeeded( { - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), statsYr: values.year ?? "", statsCd: statsCdForTab, }, @@ -1538,9 +1561,8 @@ const SewagePipeInp = () => { ); await openVerificationResultDialogIfNeeded( { - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), statsYr: values.year ?? "", statsCd: statsCdForTab, }, @@ -1567,9 +1589,8 @@ const SewagePipeInp = () => { const userInfo = await getUserInfo(); const requestData: cancelSubmitRequest = { - sewerBzmnCd: searchValues?.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues?.sido ?? '', - sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ""), statsYr: searchValues?.year ?? '', mdfrId: userInfo.userUnqId, statsCd: tabStatsCdMap.get(activeTab) ?? statsCd, @@ -1699,8 +1720,7 @@ const SewagePipeInp = () => { const param: SeweragePipeInputRegionalFactStatusSaveRequest = { statsYr: values.year ?? "", ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), userId: userInfo.userUnqId, ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), saveList, @@ -1779,8 +1799,7 @@ const SewagePipeInp = () => { const param: SeweragePipeInputFactInstallStatusSaveRequest = { statsYr: values.year ?? "", ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), userId: userInfo.userUnqId, ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), saveList, @@ -1864,8 +1883,7 @@ const SewagePipeInp = () => { const param: SeweragePipeInputPipeTypeStatusSaveRequest = { statsYr: values.year ?? "", ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), userId: userInfo.userUnqId, ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), saveList, @@ -1927,8 +1945,7 @@ const SewagePipeInp = () => { const param: SeweragePipeInputPipeDiameterStatusSaveRequest = { statsYr: values.year ?? "", ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), userId: userInfo.userUnqId, ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), saveList, @@ -2006,8 +2023,7 @@ const SewagePipeInp = () => { const param: SeweragePipeInputManholeStatusSaveRequest = { statsYr: values.year ?? "", ctpvNm: values.sido ?? "", - sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""), - sewerBzmnCd: values.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, values), userId: userInfo.userUnqId, ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), saveList, @@ -2036,12 +2052,12 @@ const SewagePipeInp = () => { } }; - const gugunOkOrNotDoList = buildGugunOkForToolbar(lastSearchedValues); + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; + const gugunOkOrNotDoList = buildGugunOkForToolbar(searchContextForToolbar); + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); - const searchContextForToolbar = lastSearchedValues ?? searchValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabToolbar = - toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( activeTabStatus, tabRowDataExists(rowDataMap, activeTab), @@ -2067,6 +2083,7 @@ const SewagePipeInp = () => { return ( <> { {showEnterableToolbarButtons && ( <> {activeTab !== 'ST00020002' && void getLastYearDataFunc()} />} - void submitFunc(false)} /> + {submitOk && void submitFunc(false)} />} )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } @@ -2306,9 +2323,8 @@ const SewagePipeInp = () => { searchValues?.bsnmCd && (tabStatsCdMap.get(activeTab) ?? statsCd) ? { - sewerBzmnCd: searchValues.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues.sido, - sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""), statsYr: searchValues.year, statsCd: tabStatsCdMap.get(activeTab) ?? statsCd, } @@ -2342,9 +2358,8 @@ const SewagePipeInp = () => { searchValues?.bsnmCd && (tabStatsCdMap.get(activeTab) ?? statsCd) ? { - sewerBzmnCd: searchValues.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues.sido, - sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""), statsYr: searchValues.year, statsCd: tabStatsCdMap.get(activeTab) ?? statsCd, } diff --git a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx index f51f46846..f3ff2c525 100644 --- a/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx +++ b/apps/st/src/components/statistics/input/SewagePipeMaintainInp.tsx @@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib"; import { STATISTICS_INPUT_URL_PATH, verificationSearchToFormValues, @@ -27,7 +27,15 @@ import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, @@ -52,7 +60,6 @@ import { VerificationResultDialog } from "@/components/module/VerificationResult import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import type { ColumnInfo } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -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 { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides"; @@ -74,9 +81,8 @@ function buildSewagePipeMaintainSearchParam( ): SewagePipeMaintainRequestParams { return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; } @@ -116,10 +122,14 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); } function computeTabToolbarSnapshot( @@ -263,6 +273,8 @@ function buildDredgingRowList( // 하수도 통계 관리 > 03. 하수관로 유지관리 const SewagePipeMaintainInp = () => { + const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { isAdminInfo } = useAdminStore(); const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate(); @@ -288,9 +300,12 @@ const SewagePipeMaintainInp = () => { const [footerVerificationByTab, setFooterVerificationByTab] = useState< Map >(new Map()); - const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false); const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false); const [showSubmitDialog, setShowSubmitDialog] = useState(false); @@ -320,6 +335,7 @@ const SewagePipeMaintainInp = () => { useLayoutEffect(() => { if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN) return; + if (!isReady) return; const pending = useStatisticsInputFocusStore .getState() .takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN); @@ -327,12 +343,12 @@ const SewagePipeMaintainInp = () => { const { tabKey, search } = pending; setActiveTab(tabKey); if (search) { - const values = verificationSearchToFormValues(search); + const values = verificationSearchToFormValues(search, sidoList); setVerificationSearchDefaults(values); postVerificationSearchRef.current = { values, tabKey }; setSearchFormRemountKey((k) => k + 1); } - }, [activeTabId, inputFocusNonce]); + }, [activeTabId, inputFocusNonce, isReady, sidoList]); useEffect(() => { if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN) return; @@ -377,6 +393,13 @@ const SewagePipeMaintainInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const applyRowMap = ( @@ -418,7 +441,11 @@ const SewagePipeMaintainInp = () => { return false; } const { commonInfo, statusInfo, repairStatusList } = response.data; - const convertedData = (repairStatusList ?? []).map((d) => ({ + const convertedData = filterBySelectedAdmdstCd( + repairStatusList ?? [], + sidoListRef.current, + searchContext, + ).map((d) => ({ ctpvNm: commonInfo.ctpvNm, ...d, })); @@ -457,7 +484,11 @@ const SewagePipeMaintainInp = () => { return false; } const { commonInfo, statusInfo, pipeDredgingStatusList } = response.data; - const convertedData = (pipeDredgingStatusList ?? []).map((d) => ({ + const convertedData = filterBySelectedAdmdstCd( + pipeDredgingStatusList ?? [], + sidoListRef.current, + searchContext, + ).map((d) => ({ ctpvNm: commonInfo.ctpvNm, ...d, })); @@ -581,11 +612,7 @@ const SewagePipeMaintainInp = () => { const submitInfoBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), laePicNm: submitInfo.laePicNm, laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, @@ -597,12 +624,8 @@ const SewagePipeMaintainInp = () => { }; const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForTab, }; @@ -647,7 +670,7 @@ const SewagePipeMaintainInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForTab, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2", @@ -727,11 +750,7 @@ const SewagePipeMaintainInp = () => { const baseRequest = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), }; try { @@ -833,12 +852,8 @@ const SewagePipeMaintainInp = () => { const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey; const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -871,6 +886,7 @@ const SewagePipeMaintainInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setActiveTab(firstTabKey); setLastSearchedValues(null); setHasSearched(false); @@ -917,6 +933,7 @@ const SewagePipeMaintainInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); } @@ -1012,10 +1029,9 @@ const SewagePipeMaintainInp = () => { return map; }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]); - const searchContextForToolbar = lastSearchedValues ?? searchValues; + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabToolbar = - toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( activeTabStatus, tabRowDataExists(rowDataMap, activeTab), @@ -1036,6 +1052,7 @@ const SewagePipeMaintainInp = () => { baseShowEnterableToolbarButtons && toolbarGroupAllowed; const showActionToolbarCard = baseShowActionToolbarCard && toolbarGroupAllowed; + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const openVerificationHistoryDialog = () => { const ctx = lastSearchedValues ?? searchValues; @@ -1052,9 +1069,8 @@ const SewagePipeMaintainInp = () => { const statsCd = tabStatsCdMap.get(activeTab); if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -1102,6 +1118,7 @@ const SewagePipeMaintainInp = () => { <>
{ void getLastYearDataFunc()} /> - + {submitOk && } )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } @@ -1161,7 +1178,7 @@ const SewagePipeMaintainInp = () => { tabStatusInfoMap.get(tabKey) ?? null, inputPeriodEnterable, ) || isAdminInfo) && inputPeriodEnterable; - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; return ( diff --git a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx index bb45347c8..237bb3762 100644 --- a/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx +++ b/apps/st/src/components/statistics/input/SewageReservoirPumpInp.tsx @@ -13,7 +13,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { ValueType } from "realgrid"; import type { RealGridReact } from "realgrid-react"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } 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"; @@ -38,7 +38,16 @@ import type { ImrStatusResponse, SewerageStatusInfo, Sgg, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + resolveNoApplicableForRequest, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -66,7 +75,6 @@ import type { ColumnInfoList, } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -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 { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides"; @@ -126,10 +134,22 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); +} + +function buildNoApplicableForRequest( + values: SearchFormValues | null, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty); } function computeTabToolbarSnapshot( @@ -171,9 +191,8 @@ function buildSewageReservoirSearchParam( const fcltNm = values.fctName?.trim(); return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(fcltNm ? { fcltNm } : {}), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; @@ -246,8 +265,9 @@ function buildSggNmColumn( function mapSewageReservoirListToGridRows( list: SewageReservoirItem[], ctpvNm: string, + values?: SearchFormValues | null, ): GridRowWithVerification[] { - return list.map((item) => { + return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const row = toGridRows([ { ...item, @@ -399,8 +419,9 @@ function buildRainfallPumpStationSaveItem( function mapRainfallPumpStationListToGridRows( list: RainfallPumpStationItem[], ctpvNm: string, + values?: SearchFormValues | null, ): GridRowWithVerification[] { - return list.map((item) => { + return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const row = toGridRows([ { ...item, @@ -447,6 +468,8 @@ async function waitForImrJobCompleted( // 하수도 통계 관리 > 04. 하수저류 시설 및 빗물 펌프장 const SewageReservoirPumpInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const { isAdminInfo } = useAdminStore(); @@ -478,9 +501,9 @@ const SewageReservoirPumpInp = () => { alert("시군구를 선택해주세요."); return; } - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getCurrentRowsForTab(tabKey).length === 0); const currentData = getCurrentRowsForTab(tabKey); - if (!noApplicable && currentData.length === 0) { + if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -505,12 +528,8 @@ const SewageReservoirPumpInp = () => { const userInfo = await getUserInfo(); const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -663,7 +682,7 @@ const SewageReservoirPumpInp = () => { if (!(await confirm("저장 하시겠습니까?"))) return; commitGridForTab(tabKey); - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getCurrentRowsForTab(tabKey).length === 0); const currentData = getCurrentRowsForTab(tabKey); const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD; const saveList = noApplicable @@ -682,7 +701,7 @@ const SewageReservoirPumpInp = () => { : rainfallPumpStationDeleteList .filter((row) => String(row.fcltSn ?? "").trim() !== "") .map((row) => buildRainfallPumpStationSaveItem(row, sggOptions)); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -690,11 +709,7 @@ const SewageReservoirPumpInp = () => { const requestBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), ...(noApplicable ? { noApplicable: true } : {}), }; @@ -754,7 +769,7 @@ const SewageReservoirPumpInp = () => { } commitGridForTab(tabKey); - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getCurrentRowsForTab(tabKey).length === 0); const currentData = getCurrentRowsForTab(tabKey); const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD; const submitList = noApplicable @@ -773,12 +788,8 @@ const SewageReservoirPumpInp = () => { const activeTabStatus = tabStatusInfoMap.get(tabKey) ?? null; const statsCdForTab = tabStatsCdMap.get(tabKey) ?? tabKey; const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForTab, }; @@ -792,7 +803,7 @@ const SewageReservoirPumpInp = () => { await loadActiveTabData(tabKey, lastSearchedValues, true, false); }; - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -800,11 +811,7 @@ const SewageReservoirPumpInp = () => { const submitRequestBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ...(noApplicable ? { noApplicable: true } : {}), laePicNm: submitInfo.laePicNm, laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm, @@ -847,7 +854,7 @@ const SewageReservoirPumpInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForTab, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: @@ -926,9 +933,12 @@ const SewageReservoirPumpInp = () => { const [tabStatusInfoMap, setTabStatusInfoMap] = useState< Map >(new Map()); - const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [tabStatsCdMap, setTabStatsCdMap] = useState>(new Map()); const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState< Map @@ -1024,6 +1034,13 @@ const SewageReservoirPumpInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const applyRowMap = ( @@ -1068,6 +1085,7 @@ const SewageReservoirPumpInp = () => { const gridRows = mapSewageReservoirListToGridRows( list, commonInfo.ctpvNm, + values, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -1116,6 +1134,7 @@ const SewageReservoirPumpInp = () => { const gridRows = mapRainfallPumpStationListToGridRows( list, commonInfo.ctpvNm, + values, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -1198,6 +1217,7 @@ const SewageReservoirPumpInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setLastSearchedValues(null); setSggOptions([]); sggOptionsSidoRef.current = null; @@ -1249,6 +1269,7 @@ const SewageReservoirPumpInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); loadedTabKeysRef.current.clear(); setActiveTab(firstTabKey); } @@ -1318,10 +1339,9 @@ const SewageReservoirPumpInp = () => { } }; - const searchContextForToolbar = lastSearchedValues ?? searchValues; + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabToolbar = - toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( activeTabStatus, tabRowDataExists(rowDataMap, activeTab), @@ -1342,6 +1362,7 @@ const SewageReservoirPumpInp = () => { baseShowEnterableToolbarButtons && toolbarGroupAllowed; const showActionToolbarCard = baseShowActionToolbarCard && toolbarGroupAllowed; + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const isReservoirOrRainfallTab = activeTab === SEWAGE_RESERVOIR_STATS_CD || activeTab === rainfallPumpTabKey; const showArticleToolbarCard = @@ -1356,7 +1377,8 @@ const SewageReservoirPumpInp = () => { activeTab, tabRowDataExists(rowDataMap, activeTab), tabStatusInfoMap.get(activeTab), - ); + ) && + submitOk; const setIsNotApplicableForActiveTab: Dispatch> = useCallback( (next) => { const tabKey = activeTabRef.current; @@ -1514,9 +1536,8 @@ const SewageReservoirPumpInp = () => { const statsCd = tabStatsCdMap.get(activeTab); if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -1537,6 +1558,7 @@ const SewageReservoirPumpInp = () => { <>
{ void getLastYearDataFunc()} /> - + {submitOk && } )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } @@ -1607,7 +1629,7 @@ const SewageReservoirPumpInp = () => { tabStatusInfoMap.get(tabKey) ?? null, inputPeriodEnterable, ) || isAdminInfo) && inputPeriodEnterable; - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; const isReservoirTab = tabKey === SEWAGE_RESERVOIR_STATS_CD; return ( diff --git a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx index 805eaab56..082f11ded 100644 --- a/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageFinanceInp.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { useAlertStore, useGlobalProgressStore } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useSearchConditionSidoList } 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"; @@ -35,7 +35,15 @@ import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -54,7 +62,6 @@ import { VerificationResultDialog } from "@/components/module/VerificationResult import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { CauserBurdenInpColumn } from "@/components/util/column/input/sewerageFinance/CauserBurdenInpColumn"; import { SewerageChargeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageChargeInpColumn"; import { SewerageFinanceSettleInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageFinanceSettleInpColumn"; @@ -101,9 +108,8 @@ function buildSewerageFinanceSearchParam( ): SewerageFinanceRequestParams { return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; } @@ -121,10 +127,14 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); } function computeTabToolbarSnapshot( @@ -160,8 +170,8 @@ function tabRowDataExists( function mapRegionalListToGridRows< T extends { verificationResult?: SewerageFinanceVerificationResult }, ->(list: T[], ctpvNm: string): GridRowWithVerification[] { - return list.map((item) => { +>(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] { + return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const { verificationResult, ...rest } = item; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; if (verificationResult) row.verificationResult = verificationResult; @@ -342,6 +352,8 @@ async function waitForImrJobCompleted( // 하수도 통계 관리 > 09. 하수도재정 const SewerageFinanceInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const { isAdminInfo } = useAdminStore(); @@ -369,9 +381,12 @@ const SewerageFinanceInp = () => { const [footerVerificationByTab, setFooterVerificationByTab] = useState< Map >(new Map()); - const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [columnGuidesByTab, setColumnGuidesByTab] = useState< Record> >({}); @@ -431,6 +446,13 @@ const SewerageFinanceInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const applyRowMap = ( @@ -471,6 +493,7 @@ const SewerageFinanceInp = () => { const gridRows = mapRegionalListToGridRows( (sewerageFinanceSettleList ?? []) as SewerageFinanceSettleItem[], commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -500,6 +523,7 @@ const SewerageFinanceInp = () => { const gridRows = mapRegionalListToGridRows( (sewerageChargeList ?? []) as SewerageChargeItem[], commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -529,6 +553,7 @@ const SewerageFinanceInp = () => { const gridRows = mapRegionalListToGridRows( (sewerageUsageFeeList ?? []) as SewerageUsageFeeItem[], commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -558,6 +583,7 @@ const SewerageFinanceInp = () => { const gridRows = mapRegionalListToGridRows( (causerBurdenList ?? []) as CauserBurdenItem[], commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -590,6 +616,7 @@ const SewerageFinanceInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; setLastSearchedValues(null); @@ -633,6 +660,7 @@ const SewerageFinanceInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); } @@ -742,12 +770,8 @@ const SewerageFinanceInp = () => { } const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -830,11 +854,7 @@ const SewerageFinanceInp = () => { const submitInfoBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), laePicNm: submitInfo.laePicNm, laePicTelNo: submitInfo.laePicTelNo, laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, @@ -846,12 +866,8 @@ const SewerageFinanceInp = () => { }; const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForTab, }; @@ -924,7 +940,7 @@ const SewerageFinanceInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForTab, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2", @@ -1012,11 +1028,7 @@ const SewerageFinanceInp = () => { const baseRequest = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), }; try { @@ -1137,9 +1149,8 @@ const SewerageFinanceInp = () => { const statsCd = tabStatsCdMap.get(activeTab); if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -1170,19 +1181,20 @@ const SewerageFinanceInp = () => { return map; }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]); + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabToolbar = - toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( activeTabStatus, tabRowDataExists(rowDataMap, activeTab), hasSearched, - lastSearchedValues, + searchContextForToolbar, inputPeriodEnterable, isAdminInfo, ); const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } = activeTabToolbar; + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const verificationCellStyle = ({ fieldName, @@ -1241,6 +1253,7 @@ const SewerageFinanceInp = () => { <>
{ void lastYearDataFunc()} /> - + {submitOk && } )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } @@ -1300,7 +1313,7 @@ const SewerageFinanceInp = () => { tabStatusInfoMap.get(tabKey) ?? null, inputPeriodEnterable, ) || isAdminInfo) && inputPeriodEnterable; - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; return ( diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index aba30eee3..43f4df908 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useSearchConditionSidoList, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; import { STATISTICS_INPUT_URL_PATH, verificationSearchToFormValues, @@ -25,8 +25,15 @@ import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, + Sido, SubmitExceptionRequest, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + toInputSearchRegion, +} from "@/entities/statistics"; import { LoadingWithAnimation } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -45,7 +52,6 @@ import SearchForm from "@/components/module/SearchForm"; import { VerificationResultDialog } from "@/components/module/VerificationResultDialog"; import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -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"; @@ -118,14 +124,15 @@ async function waitForImrJobCompleted( throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다."); } +const sidoListRef: { current: Sido[] } = { current: [] }; + function getSewerageSupplyRateParams( values: SearchFormValues, overrides?: { yr?: string; demoYr?: string; mode?: string } ): SewerageSupplyRateRequestParams { return { ctpvNm: values.sido, - sggNm: values.gugun === 'ALL' ? '' : values.gugun, - sewerBzmnCd: values.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, values), statsYr: overrides?.yr ?? values.year, ...(overrides?.mode && { mode: overrides.mode }), }; @@ -133,6 +140,8 @@ function getSewerageSupplyRateParams( // 하수도 통계 관리 > 01. 하수도 보급률 const SeweragePenetrationRateInp = () => { + const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const [rowData, setRowData] = useState[]>([]); const [isLoading, setIsLoading] = useState(false); @@ -174,15 +183,16 @@ const SeweragePenetrationRateInp = () => { if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) { return; } + if (!isReady) return; const pending = useStatisticsInputFocusStore .getState() .takePendingForUrl(STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE); if (!pending?.search) return; - const values = verificationSearchToFormValues(pending.search); + const values = verificationSearchToFormValues(pending.search, sidoList); setVerificationSearchDefaults(values); postVerificationSearchRef.current = values; setSearchFormRemountKey((k) => k + 1); - }, [activeTabId, inputFocusNonce]); + }, [activeTabId, inputFocusNonce, isReady, sidoList]); useEffect(() => { if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE) { @@ -269,14 +279,22 @@ const SeweragePenetrationRateInp = () => { setSearchFormRemountKey((k) => k + 1); }; - const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams, isEnterable: boolean) => { + const fetchSewerageSupplyRate = async ( + params: SewerageSupplyRateRequestParams, + isEnterable: boolean, + values?: SearchFormValues | null, + ) => { try { setIsLoading(true); const response = await statisticsInputSewSupplyRateApi.getSewerageSupplyRateList(params); if (response.status === 'success' && response.data) { const commonInfo = response.data.commonInfo; const statusInfo = response.data.statusInfo; - const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d: SewerageSupplyRateItem) => ({ + const convertedData: SewerageSupplyRateItem[] = filterBySelectedAdmdstCd( + response.data.sewerageSupplyRateList, + sidoListRef.current, + values, + ).map((d: SewerageSupplyRateItem) => ({ ...d, ctpvNm: commonInfo.ctpvNm, spcCtpnRgnCn: d.spcCtpnRgnCn === '' || d.spcCtpnRgnCn === null ? '해당없음' : d.spcCtpnRgnCn, @@ -346,7 +364,7 @@ const SeweragePenetrationRateInp = () => { setPeriodCheckList(periods); const checkEnterable = isInputPeriodCurrent(periods); setLastSearchedYear(values.year || null); - await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values), checkEnterable); + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values), checkEnterable, values); setLastSearchedValues(values); } else { alert(response.message); @@ -374,9 +392,8 @@ const SeweragePenetrationRateInp = () => { const params: SubmitExceptionRequest = { ...submitInfo, - sewerBzmnCd: searchValues?.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues?.sido ?? '', - sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''), statsYr: searchValues?.year ?? '', userId: userInfo.userUnqId, statsCd: statsCd, @@ -392,7 +409,7 @@ const SeweragePenetrationRateInp = () => { const response = await statisticsApi.submitException(params); if (response.status === 'success') { - await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false); + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false, searchValues ?? {}); alert('제출되었습니다.'); } else { alert(response.message || '데이터 제출 중 오류가 발생했습니다.'); @@ -416,7 +433,7 @@ const SeweragePenetrationRateInp = () => { return; } - await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }), true); + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' }), true, values); }; const submitFunc = async (isException: boolean) => { @@ -512,9 +529,8 @@ const SeweragePenetrationRateInp = () => { })); const requestData: SewerageSupplyRateSaveRequest = { - sewerBzmnCd: searchValues?.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues?.sido ?? '', - sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''), statsYr: searchValues?.year ?? '', userId: userInfo.userUnqId, submitList: convertedData, @@ -558,13 +574,12 @@ const SeweragePenetrationRateInp = () => { ); } setGlobalProgress(100); - await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {}); await openVerificationResultDialogIfNeeded( searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd ? { - sewerBzmnCd: searchValues.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues.sido, - sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""), statsYr: searchValues.year, statsCd, } @@ -573,14 +588,13 @@ const SeweragePenetrationRateInp = () => { { notify: alert }, ); } catch (e) { - await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {}); setIsLoading(false); await openVerificationResultDialogIfNeeded( searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd ? { - sewerBzmnCd: searchValues.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues.sido, - sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""), statsYr: searchValues.year, statsCd, } @@ -597,13 +611,12 @@ const SeweragePenetrationRateInp = () => { } } else { setGlobalProgress(100); - await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false); + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), false, searchValues ?? {}); await openVerificationResultDialogIfNeeded( searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd ? { - sewerBzmnCd: searchValues.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues.sido, - sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""), statsYr: searchValues.year, statsCd, } @@ -702,9 +715,8 @@ const SeweragePenetrationRateInp = () => { })); const requestData: SewerageSupplyRateSaveRequest = { - sewerBzmnCd: searchValues?.bsnmCd ?? '', // 하수도 사업자 코드 + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues?.sido ?? '', // 시도 - sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ''), statsYr: searchValues?.year ?? '', // 통계 연도 userId: userInfo.userUnqId, // 사용자 saveList: convertedData, // 저장 데이터 @@ -720,7 +732,7 @@ const SeweragePenetrationRateInp = () => { if (response.status === 'success') { setGlobalProgress(50); setGlobalProgress(100); - await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {}); alert('저장되었습니다.'); } else { alert(response.message || '데이터 저장 중 오류가 발생했습니다.'); @@ -747,9 +759,8 @@ const SeweragePenetrationRateInp = () => { const userInfo = await getUserInfo(); const requestData: cancelSubmitRequest = { - sewerBzmnCd: searchValues?.bsnmCd ?? '', + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues?.sido ?? '', - sggNm: searchValues?.gugun === "ALL" ? "" : (searchValues?.gugun ?? ""), statsYr: searchValues?.year ?? '', mdfrId: userInfo.userUnqId, statsCd: statsCd, @@ -758,7 +769,7 @@ const SeweragePenetrationRateInp = () => { const response = await statisticsApi.cancelSubmit(requestData); if (response.status === 'success') { - fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true); + fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true, searchValues ?? {}); alert('제출 취소되었습니다.'); } else { alert(response.message ?? '제출 취소 중 오류가 발생했습니다.'); @@ -777,9 +788,9 @@ const SeweragePenetrationRateInp = () => { setVerificationHistoryOpen(true); }; - const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || '') - ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') - : true; + const toolbarSearchContext = lastSearchedValues; + const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, toolbarSearchContext); + const submitOk = isSubmitOkForToolbar(sidoList, toolbarSearchContext); const inputPeriodCurrent = isInputPeriodCurrent(periodCheckList); const isSubmitted = statusInfo?.laeSbmsnSttsCd === '2'; @@ -802,6 +813,7 @@ const SeweragePenetrationRateInp = () => { <>
{ {showEnterableToolbarButtons && ( <> getLastYearDataFunc(searchValues ?? {})} /> - submitFunc(false)} /> + {submitOk && submitFunc(false)} />} )} - {statusInfo?.laeSbmsnSttsCd === '2' && } + {statusInfo?.laeSbmsnSttsCd === '2' && submitOk && } {showSaveInToolbar && }
@@ -836,7 +848,7 @@ const SeweragePenetrationRateInp = () => { 0) && isEnterable && (doList.includes(lastSearchedValues?.bsnmCd || '') ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== 'ALL') : true)} + isExcelImport={(rowData.length > 0) && isEnterable && gugunOkOrNotDoList} excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd} excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)} rowStartNum={3} @@ -892,9 +904,8 @@ const SeweragePenetrationRateInp = () => { queryContext={ searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd ? { - sewerBzmnCd: searchValues.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues.sido, - sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""), statsYr: searchValues.year, statsCd, } @@ -912,12 +923,8 @@ const SeweragePenetrationRateInp = () => { queryContext={ searchValues?.year && searchValues?.sido && searchValues?.bsnmCd && statsCd ? { - sewerBzmnCd: searchValues.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, searchValues), ctpvNm: searchValues.sido, - sggNm: - searchValues.gugun === "ALL" - ? "" - : (searchValues.gugun ?? ""), statsYr: searchValues.year, statsCd, } diff --git a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx index 5581c3a21..ed15d1ffe 100644 --- a/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx +++ b/apps/st/src/components/statistics/input/SewerageServiceLevelInp.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } 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"; @@ -25,7 +25,16 @@ import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + resolveNoApplicableForRequest, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -45,7 +54,6 @@ import { VerificationResultDialog } from "@/components/module/VerificationResult import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -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 { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides"; @@ -120,10 +128,22 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); +} + +function buildNoApplicableForRequest( + values: SearchFormValues | null, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty); } function computeTabToolbarSnapshot( @@ -155,17 +175,16 @@ function buildSewerageServiceLevelSearchParam( ): SewerageServiceLevelComplaintRequestParams { return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; } function mapServiceLevelListToGridRows< T extends { verificationResult?: SewerageServiceLevelVerificationResult }, ->(list: T[], ctpvNm: string): GridRowWithVerification[] { - return list.map((item) => { +>(list: T[], ctpvNm: string, values?: SearchFormValues | null): GridRowWithVerification[] { + return filterBySelectedAdmdstCd(list, sidoListRef.current, values).map((item) => { const { verificationResult, ...rest } = item; const row = toGridRows([{ ctpvNm, ...rest }])[0] as GridRowWithVerification; if (verificationResult) row.verificationResult = verificationResult; @@ -271,6 +290,8 @@ async function waitForImrJobCompleted( // 하수도 통계 관리 > 13. 하수도 서비스 수준 const SewerageServiceLevelInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const { isAdminInfo } = useAdminStore(); @@ -301,9 +322,12 @@ const SewerageServiceLevelInp = () => { const [footerVerificationByTab, setFooterVerificationByTab] = useState< Map >(new Map()); - const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [columnGuidesByTab, setColumnGuidesByTab] = useState< Record> >({}); @@ -366,6 +390,13 @@ const SewerageServiceLevelInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const applyRowMap = ( @@ -407,6 +438,7 @@ const SewerageServiceLevelInp = () => { const gridRows = mapServiceLevelListToGridRows( (complaintList ?? []) as SewerageServiceLevelComplaintItem[], commonInfo.ctpvNm, + searchContext, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -439,6 +471,7 @@ const SewerageServiceLevelInp = () => { const gridRows = mapServiceLevelListToGridRows( (residentFriendlyFactList ?? []) as SewerageServiceLevelResidentFriendlyFactItem[], commonInfo.ctpvNm, + searchContext, ); if (!fromPreYear) { publicSewageFacilitiesAvailableRef.current = gridRows.length > 0; @@ -483,6 +516,7 @@ const SewerageServiceLevelInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; setLastSearchedValues(null); @@ -529,6 +563,7 @@ const SewerageServiceLevelInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setIsNotApplicable(false); activeTabRef.current = firstTabKey; setActiveTab(firstTabKey); @@ -647,9 +682,19 @@ const SewerageServiceLevelInp = () => { const tabKey = activeTabRef.current; const isResidentTab = tabKey === ResidentFriendlyFactInpColumn.tabKey; const latestRows = getTabGridRows(tabKey); - const noApplicable = isResidentTab && isNotApplicable; + const noApplicable = + isResidentTab && + buildNoApplicableForRequest( + searchContextByTab.get(tabKey) ?? lastSearchedValues, + isNotApplicable, + latestRows.length === 0, + ); if (latestRows.length === 0) { - if (isResidentTab && !noApplicable) { + if ( + isResidentTab && + !noApplicable && + buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues) + ) { alert("해당사항 없음을 체크해주세요."); return; } @@ -690,7 +735,13 @@ const SewerageServiceLevelInp = () => { return next; }); - const noApplicable = isResidentTab && isNotApplicable; + const noApplicable = + isResidentTab && + buildNoApplicableForRequest( + searchContextByTab.get(tabKey) ?? lastSearchedValues, + isNotApplicable, + latestRows.length === 0, + ); const complaintSubmitList = buildComplaintSaveList(latestRows); const residentSubmitList = noApplicable ? [] @@ -700,7 +751,12 @@ const SewerageServiceLevelInp = () => { alert("제출할 데이터가 없습니다."); return; } - if (isResidentTab && !noApplicable && residentSubmitList.length === 0) { + if ( + isResidentTab && + !noApplicable && + residentSubmitList.length === 0 && + buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues) + ) { alert("해당사항 없음을 체크해주세요."); return; } @@ -715,11 +771,7 @@ const SewerageServiceLevelInp = () => { const submitInfoBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), laePicNm: submitInfo.laePicNm, laePicTelNo: submitInfo.laePicTelNo, laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, @@ -731,12 +783,8 @@ const SewerageServiceLevelInp = () => { }; const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForTab, }; @@ -784,7 +832,7 @@ const SewerageServiceLevelInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForTab, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2", @@ -865,15 +913,17 @@ const SewerageServiceLevelInp = () => { return next; }); - const noApplicable = isResidentTab && isNotApplicable; + const noApplicable = + isResidentTab && + buildNoApplicableForRequest( + searchContextByTab.get(tabKey) ?? lastSearchedValues, + isNotApplicable, + latestRows.length === 0, + ); const baseRequest = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), }; try { @@ -903,7 +953,11 @@ const SewerageServiceLevelInp = () => { const saveList = noApplicable ? [] : buildResidentFriendlyFactSaveList(latestRows); - if (!noApplicable && saveList.length === 0) { + if ( + !noApplicable && + saveList.length === 0 && + buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues) + ) { alert("해당사항 없음을 체크해주세요."); return; } @@ -963,12 +1017,8 @@ const SewerageServiceLevelInp = () => { } const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -1026,15 +1076,15 @@ const SewerageServiceLevelInp = () => { return map; }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab]); + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabRowData = rowDataMap.get(activeTab) ?? []; const activeTabToolbar = - toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( activeTabStatus, tabRowDataExists(rowDataMap, activeTab), hasSearched, - lastSearchedValues, + searchContextForToolbar, inputPeriodEnterable, isAdminInfo, ); @@ -1045,12 +1095,14 @@ const SewerageServiceLevelInp = () => { activeTabToolbar.showEnterableToolbarButtons && toolbarGroupAllowed; const showActionToolbarCard = activeTabToolbar.showActionToolbarCard && toolbarGroupAllowed; + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const showNotApplicableForResidentTab = activeTab === ResidentFriendlyFactInpColumn.tabKey && searchLoaded && hasSearched && activeTabRowData.length === 0 && - computeTabEnterable(activeTabStatus, inputPeriodEnterable); + computeTabEnterable(activeTabStatus, inputPeriodEnterable) && + submitOk; const showSecondaryToolbarCard = hasSearched && toolbarGroupAllowed && @@ -1061,9 +1113,8 @@ const SewerageServiceLevelInp = () => { const statsCd = tabStatsCdMap.get(activeTab); if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -1126,6 +1177,7 @@ const SewerageServiceLevelInp = () => { <>
{ void getLastYearDataFunc()} /> - + {submitOk && } )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } @@ -1197,7 +1249,7 @@ const SewerageServiceLevelInp = () => { tabStatusInfoMap.get(tabKey) ?? null, inputPeriodEnterable, ) || isAdminInfo) && inputPeriodEnterable; - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; return ( diff --git a/apps/st/src/components/statistics/input/StatisticsReviewHistory.tsx b/apps/st/src/components/statistics/input/StatisticsReviewHistory.tsx index c6a6b9412..bc89e8b35 100644 --- a/apps/st/src/components/statistics/input/StatisticsReviewHistory.tsx +++ b/apps/st/src/components/statistics/input/StatisticsReviewHistory.tsx @@ -70,7 +70,7 @@ const StatisticsReviewHistory = () => { return (
- + {isLoading && (
diff --git a/apps/st/src/components/statistics/input/StatsInputStatusInp.tsx b/apps/st/src/components/statistics/input/StatsInputStatusInp.tsx index 76cbeb014..7a4ac00bd 100644 --- a/apps/st/src/components/statistics/input/StatsInputStatusInp.tsx +++ b/apps/st/src/components/statistics/input/StatsInputStatusInp.tsx @@ -4,7 +4,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { RealGridReact } from "realgrid-react"; import useSWR, { useSWRConfig } from "swr"; -import { useActiveTabRegAuthorityAllowed, useAlertStore } from "@/shared/lib"; +import { useActiveTabRegAuthorityAllowed, useAlertStore, useSearchConditionSidoList } from "@/shared/lib"; import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service"; import { StatisticsInputStatusApi } from "@/entities/statistics/api/input/statistics-input-status-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; @@ -17,6 +17,7 @@ import { isInputPeriodCurrent, } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface"; import type { StatsInputStatusApproveItem } from "@/entities/statistics/model/statistics-interface"; +import { isGugunOkForToolbar } from "@/entities/statistics"; import { LoadingWithAnimation } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -30,7 +31,6 @@ import { import SearchForm from "@/components/module/SearchForm"; import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { StatsInputStatusInpColumn } from "@/components/util/column/input/StatsInputStatusInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; import { useTabStore } from "@/store/tab-store"; @@ -44,6 +44,7 @@ const getStatsInputStatusFetcher = async ( // 하수도 통계 관리 > 00. 통계 입력 현황 const StatsInputStatusInp = () => { + const { sidoList } = useSearchConditionSidoList(); const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const activeTabId = useTabStore((state) => state.activeTabId); @@ -254,9 +255,7 @@ const StatsInputStatusInp = () => { await mutate(["stats-input-status", searchParams]); }, [mutate, searchParams]); - const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || "") - ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== "ALL") - : true; + const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, lastSearchedValues); const allRowsLaeSbmsnApproved = useMemo( () => diff --git a/apps/st/src/components/statistics/input/WaterReuseInp.tsx b/apps/st/src/components/statistics/input/WaterReuseInp.tsx index 7bc053439..ff21ba2a4 100644 --- a/apps/st/src/components/statistics/input/WaterReuseInp.tsx +++ b/apps/st/src/components/statistics/input/WaterReuseInp.tsx @@ -13,7 +13,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { ValueType } from "realgrid"; import type { RealGridReact } from "realgrid-react"; -import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib"; +import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } 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"; @@ -48,7 +48,16 @@ import type { ImrStatusResponse, SewerageStatusInfo, Sgg, + Sido, } from "@/entities/statistics/model/statistics-interface"; +import { + filterBySelectedAdmdstCd, + isGugunOkForToolbar, + isSubmitOkForToolbar, + resolveNoApplicableForRequest, + toInputSearchRegion, +} from "@/entities/statistics"; + import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { getUserInfo } from "../../../../../../packages/common/components/src/api"; @@ -71,7 +80,6 @@ import { VerificationResultDialog } from "@/components/module/VerificationResult import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog"; import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { PublicSewageTreatWaterReuseInpColumn } from "@/components/util/column/input/waterReuse/PublicSewageTreatWaterReuseInpColumn"; import { RainFallUtilFactStatusInpColumn } from "@/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn"; import { RecycledWaterStatusInpColumn } from "@/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn"; @@ -159,10 +167,22 @@ const computeTabEnterable = ( return periodEnterable; }; +const sidoListRef: { current: Sido[] } = { current: [] }; + function buildGugunOkForToolbar(values: SearchFormValues | null): boolean { - return doList.includes(values?.bsnmCd || "") - ? Boolean(values?.gugun && values.gugun !== "ALL") - : true; + return isGugunOkForToolbar(sidoListRef.current, values); +} + +function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean { + return isSubmitOkForToolbar(sidoListRef.current, values); +} + +function buildNoApplicableForRequest( + values: SearchFormValues | null, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty); } function computeTabToolbarSnapshot( @@ -194,9 +214,8 @@ function buildWaterReuseSearchParam( ): WaterReuseRequestParams { return { statsYr: values.year, - sewerBzmnCd: values.bsnmCd, ctpvNm: values.sido, - sggNm: values.gugun === "ALL" ? "" : values.gugun, + ...toInputSearchRegion(sidoListRef.current, values), ...(overrides?.mode ? { mode: overrides.mode } : {}), }; } @@ -510,6 +529,8 @@ async function waitForImrJobCompleted( // 하수도 통계 관리 > 10. 물 재이용 const WaterReuseInp = () => { + const { sidoList } = useSearchConditionSidoList({ isInput: true }); + sidoListRef.current = sidoList; const { setMessage: alert } = useAlertStore((state) => state); const { confirm } = useConfirm(); const { isAdminInfo } = useAdminStore(); @@ -541,9 +562,12 @@ const WaterReuseInp = () => { const [footerVerificationByTab, setFooterVerificationByTab] = useState< Map >(new Map()); - const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< + const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState< Map >(new Map()); + const [searchContextByTab, setSearchContextByTab] = useState>( + () => new Map(), + ); const [columnGuidesByTab, setColumnGuidesByTab] = useState< Record> >({}); @@ -644,6 +668,13 @@ const WaterReuseInp = () => { ); return next; }); + if (searchContext) { + setSearchContextByTab((prev) => { + const next = new Map(prev); + next.set(tabKey, searchContext); + return next; + }); + } }; const applyRowMap = ( @@ -673,6 +704,8 @@ const WaterReuseInp = () => { ): Promise => { const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param; const fromPreYear = overrides?.mode === "PRE"; + const filterFetched = (list: T[] | undefined) => + filterBySelectedAdmdstCd(list, sidoListRef.current, searchContext); const applyNotApplicableForLoadedTab = ( gridRows: readonly Record[], @@ -704,7 +737,7 @@ const WaterReuseInp = () => { } const { commonInfo, statusInfo, publicSewageTreatWaterReuseList } = response.data; const gridRows = mapPublicSewageTreatWaterReuseListToGridRows( - (publicSewageTreatWaterReuseList ?? []) as PublicSewageTreatWaterReuseItem[], + filterFetched(publicSewageTreatWaterReuseList) as PublicSewageTreatWaterReuseItem[], commonInfo.ctpvNm, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -735,7 +768,7 @@ const WaterReuseInp = () => { } const { commonInfo, statusInfo, recycledWaterList } = response.data; const gridRows = mapRecycledWaterStatusListToGridRows( - (recycledWaterList ?? []) as RecycledWaterStatusItem[], + filterFetched(recycledWaterList) as RecycledWaterStatusItem[], commonInfo.ctpvNm, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -766,7 +799,7 @@ const WaterReuseInp = () => { } const { commonInfo, statusInfo, rainFallUtilFactStatusList } = response.data; const gridRows = mapRainFallUtilFactStatusListToGridRows( - (rainFallUtilFactStatusList ?? []) as RainFallUtilFactStatusItem[], + filterFetched(rainFallUtilFactStatusList) as RainFallUtilFactStatusItem[], commonInfo.ctpvNm, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -798,8 +831,7 @@ const WaterReuseInp = () => { } const { commonInfo, statusInfo, treatWaterReuseDesignConstStatusList } = response.data; const gridRows = mapTreatWaterReuseStatusListToGridRows( - (treatWaterReuseDesignConstStatusList ?? - []) as TreatWaterReuseDesignConstStatusItem[], + filterFetched(treatWaterReuseDesignConstStatusList) as TreatWaterReuseDesignConstStatusItem[], commonInfo.ctpvNm, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -832,8 +864,7 @@ const WaterReuseInp = () => { const { commonInfo, statusInfo, treatWaterReuseBusinessApprStatusList } = response.data; const gridRows = mapTreatWaterReuseStatusListToGridRows( - (treatWaterReuseBusinessApprStatusList ?? - []) as TreatWaterReuseBusinessApprStatusItem[], + filterFetched(treatWaterReuseBusinessApprStatusList) as TreatWaterReuseBusinessApprStatusItem[], commonInfo.ctpvNm, ); applyRowMap(mergeIntoExistingMap, (m) => { @@ -906,6 +937,7 @@ const WaterReuseInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setActiveTab(firstTabKey); activeTabRef.current = firstTabKey; setLastSearchedValues(null); @@ -954,6 +986,7 @@ const WaterReuseInp = () => { setIsVerificationFailedByTab(new Map()); setFooterVerificationByTab(new Map()); setToolbarSnapshotByTab(new Map()); + setSearchContextByTab(new Map()); setDeleteListByTab(new Map()); loadedTabKeysRef.current.clear(); activeTabRef.current = firstTabKey; @@ -1076,9 +1109,9 @@ const WaterReuseInp = () => { return; } - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); - if (!noApplicable && currentData.length === 0) { + if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1111,11 +1144,7 @@ const WaterReuseInp = () => { const submitRequestBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), laePicNm: submitInfo.laePicNm, laePicTelNo: submitInfo.laePicTelNo, laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm, @@ -1135,12 +1164,8 @@ const WaterReuseInp = () => { WATER_REUSE_COLUMN_GUIDE_STATS_CD_BY_TAB_KEY[tabKey] ?? tabKey; const verificationQuery = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", statsCd: statsCdForTab, }; @@ -1174,12 +1199,12 @@ const WaterReuseInp = () => { }; if (isPublicSewageTab) { - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const submitList = noApplicable ? [] : currentData.map(buildPublicSewageTreatWaterReuseSaveItem); - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1191,7 +1216,7 @@ const WaterReuseInp = () => { submitList, }); } else { - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const sido = lastSearchedValues.sido ?? ""; @@ -1222,7 +1247,7 @@ const WaterReuseInp = () => { .filter((row) => isPersistedArticleRow(tabKey, row)) .map((row) => buildRecycledWaterStatusSaveItem(row, list)); - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1242,7 +1267,7 @@ const WaterReuseInp = () => { .filter((row) => isPersistedArticleRow(tabKey, row)) .map((row) => buildRainFallUtilFactStatusSaveItem(row, list)); - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1264,7 +1289,7 @@ const WaterReuseInp = () => { buildTreatWaterReuseDesignConstStatusSaveItem(row, list), ); - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1289,7 +1314,7 @@ const WaterReuseInp = () => { buildTreatWaterReuseBusinessApprStatusSaveItem(row, list), ); - if (!noApplicable && submitList.length === 0) { + if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1321,7 +1346,7 @@ const WaterReuseInp = () => { setGlobalProgressLabel("시계열 검증 진행 중..."); const imrParam = { user_id: userInfo.userUnqId, - sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? "", + sewer_bzmn_cd: lastSearchedValues?.bsnmCd ?? "", stats_cd: statsCdForTab, stats_yr: lastSearchedValues.year ?? "", lae_sbmsn_stts_cd: @@ -1396,11 +1421,7 @@ const WaterReuseInp = () => { const requestBase = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ...(isAdminInfo ? { superAdmin: "superAdmin" } : {}), }; @@ -1418,12 +1439,12 @@ const WaterReuseInp = () => { }; if (tabKey === PublicSewageTreatWaterReuseInpColumn.tabKey) { - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const saveList = noApplicable ? [] : currentData.map(buildPublicSewageTreatWaterReuseSaveItem); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1454,7 +1475,7 @@ const WaterReuseInp = () => { } if (tabKey === RecycledWaterStatusInpColumn.tabKey) { - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const sido = lastSearchedValues.sido ?? ""; @@ -1477,7 +1498,7 @@ const WaterReuseInp = () => { .filter((row) => isPersistedArticleRow(tabKey, row)) .map((row) => buildRecycledWaterStatusSaveItem(row, list)); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1514,7 +1535,7 @@ const WaterReuseInp = () => { } if (tabKey === RainFallUtilFactStatusInpColumn.tabKey) { - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const sido = lastSearchedValues.sido ?? ""; @@ -1537,7 +1558,7 @@ const WaterReuseInp = () => { .filter((row) => isPersistedArticleRow(tabKey, row)) .map((row) => buildRainFallUtilFactStatusSaveItem(row, list)); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1575,7 +1596,7 @@ const WaterReuseInp = () => { } if (tabKey === TreatWaterReuseDesignConstStatusInpColumn.tabKey) { - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const sido = lastSearchedValues.sido ?? ""; @@ -1600,7 +1621,7 @@ const WaterReuseInp = () => { buildTreatWaterReuseDesignConstStatusSaveItem(row, list), ); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1640,7 +1661,7 @@ const WaterReuseInp = () => { } if (tabKey === TreatWaterReuseBusinessApprStatusInpColumn.tabKey) { - const noApplicable = isNotApplicableByTab.get(tabKey) ?? false; + const noApplicable = buildNoApplicableForRequest(searchContextByTab.get(tabKey) ?? lastSearchedValues, isNotApplicableByTab.get(tabKey) ?? false, getTabGridRows(tabKey).length === 0); const currentData = getTabGridRows(tabKey); const sido = lastSearchedValues.sido ?? ""; @@ -1665,7 +1686,7 @@ const WaterReuseInp = () => { buildTreatWaterReuseBusinessApprStatusSaveItem(row, list), ); - if (!noApplicable && saveList.length === 0) { + if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(searchContextByTab.get(tabKey) ?? lastSearchedValues)) { alert("해당사항 없음을 체크해주세요."); return; } @@ -1719,11 +1740,7 @@ const WaterReuseInp = () => { const request = { statsYr: lastSearchedValues.year ?? "", ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), userId: userInfo.userUnqId, }; @@ -1881,12 +1898,8 @@ const WaterReuseInp = () => { } const requestData: cancelSubmitRequest = { - sewerBzmnCd: lastSearchedValues.bsnmCd ?? "", + ...toInputSearchRegion(sidoListRef.current, lastSearchedValues), ctpvNm: lastSearchedValues.sido ?? "", - sggNm: - lastSearchedValues.gugun === "ALL" - ? "" - : (lastSearchedValues.gugun ?? ""), statsYr: lastSearchedValues.year ?? "", mdfrId: userInfo.userUnqId, statsCd: statsCdForTab, @@ -1963,14 +1976,14 @@ const WaterReuseInp = () => { return map; }, [tabStatusInfoMap, inputPeriodEnterable, isAdminInfo, columnGuidesByTab, sggOptions]); + const searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues; const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null; const activeTabToolbar = - toolbarSnapshotByTab.get(activeTab) ?? computeTabToolbarSnapshot( activeTabStatus, tabRowDataExists(rowDataMap, activeTab), hasSearched, - lastSearchedValues, + searchContextForToolbar, inputPeriodEnterable, isAdminInfo, ); @@ -1981,6 +1994,7 @@ const WaterReuseInp = () => { activeTabToolbar.showEnterableToolbarButtons && toolbarGroupAllowed; const showActionToolbarCard = activeTabToolbar.showActionToolbarCard && toolbarGroupAllowed; + const submitOk = buildSubmitOkForToolbar(searchContextForToolbar); const showSecondaryToolbarCard = hasSearched && toolbarGroupAllowed && @@ -1995,7 +2009,8 @@ const WaterReuseInp = () => { activeTab, tabRowDataExists(rowDataMap, activeTab), activeTabStatus, - ); + ) && + submitOk; const setIsNotApplicableForActiveTab: Dispatch> = useCallback( (next) => { const tabKey = activeTabRef.current; @@ -2014,9 +2029,8 @@ const WaterReuseInp = () => { const statsCd = tabStatsCdMap.get(activeTab); if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null; return { - sewerBzmnCd: ctx.bsnmCd, + ...toInputSearchRegion(sidoListRef.current, ctx), ctpvNm: ctx.sido, - sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""), statsYr: ctx.year, statsCd, }; @@ -2079,6 +2093,7 @@ const WaterReuseInp = () => { <>
{ void getLastYearDataFunc()} /> - + {submitOk && } )} - {activeTabStatus?.laeSbmsnSttsCd === "2" && ( + {activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && ( )} {showSaveInToolbar && } @@ -2161,7 +2176,7 @@ const WaterReuseInp = () => { tabStatusInfoMap.get(tabKey) ?? null, inputPeriodEnterable, ) || isAdminInfo) && inputPeriodEnterable; - const gugunOk = buildGugunOkForToolbar(lastSearchedValues); + const gugunOk = buildGugunOkForToolbar(searchContextForToolbar); const tabStatusInfo = tabStatusInfoMap.get(tabKey) ?? null; return ( diff --git a/apps/st/src/components/statistics/management/LogHistory.tsx b/apps/st/src/components/statistics/management/LogHistory.tsx index 184a17dce..f4b820ddf 100644 --- a/apps/st/src/components/statistics/management/LogHistory.tsx +++ b/apps/st/src/components/statistics/management/LogHistory.tsx @@ -130,9 +130,9 @@ const getSidoListFetcher = async (): Promise> => { }; const getSggListFetcher = async ( - key: [string, string], + key: [string, string, "Y" | "N"], ): Promise> => { - return statisticsApi.getSggList(key[1]); + return statisticsApi.getSggList(key[1], { gugunSkipYn: key[2] }); }; const LogHistory = () => { @@ -214,8 +214,14 @@ const LogHistory = () => { }, ); - const sggKey: [string, string] | null = - sido && sido !== "ALL" ? ["log-history-sgg-list", sido] : null; + const selectedSido = sidoList?.data?.sidoList?.find( + (item) => item.sido === sido, + ); + const gugunSkipYn = selectedSido?.gugunSkipYn ?? "Y"; + const sggKey: [string, string, "Y" | "N"] | null = + sido && sido !== "ALL" && sidoList?.data + ? ["log-history-sgg-list", sido, gugunSkipYn] + : null; const { data: sggList } = useSWR>( sggKey, getSggListFetcher, diff --git a/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx b/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx index 53f520340..88475de7f 100644 --- a/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx +++ b/apps/st/src/components/statistics/review/StatsInputStatusRvw.tsx @@ -3,7 +3,7 @@ import { useCallback, useMemo, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { useAlertStore, useUserStore } from "@/shared/lib"; +import { useAlertStore, useSearchConditionSidoList, useUserStore } from "@/shared/lib"; import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service"; import { statisticsApi } from "@/entities/statistics/api/statistics-service"; import type { @@ -14,6 +14,7 @@ import type { ReviewerInfoRequest, StatsInputStatusApproveItem, } from "@/entities/statistics/model/statistics-interface"; +import { isGugunOkForToolbar } from "@/entities/statistics"; import { Button, DataTable, @@ -33,7 +34,6 @@ import SearchForm from "@/components/module/SearchForm"; import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog"; import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doList } from "@/components/util/column/input/DoList"; import { StatsInputStatusInfoList } from "@/components/util/column/review/StatsInputStatusRvwColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; @@ -60,6 +60,7 @@ const mapSewerageListToGridRows = (rawList: StatsInputStatusReviewItem[]) => // 하수도 통계 검토 00. 통계 입력 현황 const StatsInputStatusRvw = () => { + const { sidoList } = useSearchConditionSidoList(); const { setMessage: alert } = useAlertStore((state) => state); const userInfo = useUserStore((state) => state.userInfo); const { confirm } = useConfirm(); @@ -232,9 +233,7 @@ const StatsInputStatusRvw = () => { } }; - const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || "") - ? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== "ALL") - : true; + const gugunOkOrNotDoList = isGugunOkForToolbar(sidoList, lastSearchedValues); const allSubmitStatusApproved = useMemo( () => diff --git a/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx b/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx index aeb9b7bec..65fc5b340 100644 --- a/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx +++ b/apps/st/src/components/statistics/verification/GeneralVerificationManagement.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from "react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import * as XLSX from "xlsx"; import { useAlertStore } from "@/shared/lib"; import { NoData } from "@/shared/ui/no-data"; @@ -43,6 +44,7 @@ const GeneralVerificationManagement = () => { {}, ); const [isLoading, setIsLoading] = useState(false); + const [isExporting, setIsExporting] = useState(false); const [selectedVrfcRow, setSelectedVrfcRow] = useState(null); const [showRegistForm, setShowRegistForm] = useState(false); @@ -108,6 +110,47 @@ const GeneralVerificationManagement = () => { setShowRegistForm(false); }; + const handleExportExcel = async () => { + try { + setIsExporting(true); + const response = await generalverificationApiService.getVerifyExcelList(); + if (response.status !== "success") { + alert(response.message || "엑셀 데이터 조회 중 오류가 발생했습니다."); + return; + } + const list = response.data ?? []; + if (list.length === 0) { + alert("내보낼 데이터가 없습니다."); + return; + } + + const header = [ + "지표명", + "검증명", + "검증타입", + "소수점 자리수", + "소수점 자리 처리 방식", + "검증식", + ]; + const rows = list.map((item) => [ + item.trgtStatsNm, + item.vrfcNm, + item.vrfcClsfNm, + item.dcptDigtCnt, + item.dcptPrcsMthNm, + item.vrfcEqtnCn, + ]); + const worksheet = XLSX.utils.aoa_to_sheet([header, ...rows]); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, "일반검증목록"); + XLSX.writeFile(workbook, "일반검증목록.xlsx"); + } catch { + alert("엑셀 내보내기 중 오류가 발생했습니다."); + } finally { + setIsExporting(false); + } + }; + const handleReset = () => { const resetValues = { statsCd: "", useYn: "", vrfcClsfCd: "" }; setSearchValues(resetValues); @@ -210,16 +253,27 @@ const GeneralVerificationManagement = () => {

일반 검증 목록

- void handleSearch()} - > - - +
+ + void handleSearch()} + > + + +
- {isLoading && ( + {(isLoading || isExporting) && (
diff --git a/apps/st/src/components/type/SearchForm.type.ts b/apps/st/src/components/type/SearchForm.type.ts index 72c27dab5..f0057493c 100644 --- a/apps/st/src/components/type/SearchForm.type.ts +++ b/apps/st/src/components/type/SearchForm.type.ts @@ -36,10 +36,12 @@ export type SearchFormProps = { isReset?: boolean; /** true면 시도·시군구 기본값을 계정 첫 항목이 아니라 「전체」로 둠 (집계표 등) */ defaultRegionAll?: boolean; + /** true면 입력 화면용 시도 목록 API(`getSidoListInput`)를 사용 */ + isInput?: boolean; }; export const defaultSearchFormProps: Required< - Omit + Omit > = { sido: true, gugun: true, diff --git a/apps/st/src/components/util/column/input/ExcretaTreatFactInpColumn.ts b/apps/st/src/components/util/column/input/ExcretaTreatFactInpColumn.ts index f30ab57c2..73f2a1da7 100644 --- a/apps/st/src/components/util/column/input/ExcretaTreatFactInpColumn.ts +++ b/apps/st/src/components/util/column/input/ExcretaTreatFactInpColumn.ts @@ -42,15 +42,15 @@ export const ExcretaTreatFactInpColumn = { }, // 사업비/용량 정보 - { name: 'wholPjtcoAmt', fieldName: 'wholPjtcoAmt', header: { text: '사업비(백만원)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '시설용량(m³/일)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'wholPjtcoAmt', fieldName: 'wholPjtcoAmt', header: { text: '사업비(백만원)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '시설용량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, { name: 'cpct', fieldName: 'cpct', header: { text: '처리량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, { name: 'oprtngDayCnt', fieldName: 'oprtngDayCnt', header: { text: '가동일수(일/년)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 처리 공법 및 연계 { name: 'prcsMthdExpln', fieldName: 'prcsMthdExpln', header: { text: '처리공법' } }, { name: 'linkTmplntCn', fieldName: 'linkTmplntCn', header: { text: '연계처리장명' } }, - { name: 'linkPrcsQnt', fieldName: 'linkPrcsQnt', header: { text: '연계처리량(m³/일)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'linkPrcsQnt', fieldName: 'linkPrcsQnt', header: { text: '연계처리량(m³/일)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 유입수질 { name: 'inflwBodWqVl', fieldName: 'inflwBodWqVl', header: { text: 'BOD' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, @@ -77,7 +77,7 @@ export const ExcretaTreatFactInpColumn = { // 운영 정보 { name: 'operMthdCdCn', fieldName: 'operMthdCdCn', header: { text: '운영주체(자체/공기업/민간위탁)' } }, { name: 'wholNope', fieldName: 'wholNope', header: { text: '직원총수(명)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, - { name: 'sftyAcdntEvntcsCnt', fieldName: 'sftyAcdntEvntcsCnt', header: { text: '안전사고건수(건/인/년)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'sftyAcdntEvntcsCnt', fieldName: 'sftyAcdntEvntcsCnt', header: { text: '안전사고건수(건/인/년)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 비고 { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, diff --git a/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts b/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts index 623c8efc0..6efaed0c1 100644 --- a/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts +++ b/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts @@ -1,6 +1,6 @@ import { ValueType } from "realgrid"; -import { footerDecimal1, footerDecimal3,footerInt } from "@/components/util/grid/footerSummary"; +import { footerAvgDecimal, footerDecimal1, footerDecimal3, footerInt } from "@/components/util/grid/footerSummary"; /** * 하수도 통계 관리 > 06. 공공하수처리시설 @@ -97,7 +97,7 @@ export const PublicSewageFactColumn = { { name: 'telNo', fieldName: 'telNo', header: { text: '전화번호' }, editable: false, styleName: 'rg-cell-readonly' }, { name: 'cmptnStrtYmd', fieldName: 'cmptnStrtYmd', header: { text: '준공일' }, editable: false, styleName: 'rg-cell-readonly' }, { name: 'oprtngStrtYmd', fieldName: 'oprtngStrtYmd', header: { text: '가동개시일' }, editable: false, styleName: 'rg-cell-readonly' }, - { name: 'wholBizVl', fieldName: 'wholBizVl', header: { text: '사업비' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, + { name: 'wholBizVl', fieldName: 'wholBizVl', header: { text: '사업비' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, // 방류수 소독방법 { name: 'eflntDsnfMthdExpln', fieldName: 'eflntDsnfMthdExpln', header: { text: '방류수 소독방법' }, editable: false, styleName: 'rg-cell-readonly' }, @@ -118,6 +118,7 @@ export const PublicSewageFactColumn = { fieldName: 'engySrlneRt', header: { text: '에너지자립률(%)' }, editable: false, + footer: footerAvgDecimal, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-total', @@ -163,7 +164,7 @@ export const PublicSewageFactColumn = { // 기타 { name: 'intgPrcsFcltCn', fieldName: 'intgPrcsFcltCn', header: { text: '중심처리시설' }, editable: false, styleName: 'rg-cell-readonly' }, { name: 'wholNope', fieldName: 'wholNope', header: { text: '직원총수(명)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' }, - { name: 'sftyAcdntEvntcsCnt', fieldName: 'sftyAcdntEvntcsCnt', header: { text: '안전사고건수' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'sftyAcdntEvntcsCnt', fieldName: 'sftyAcdntEvntcsCnt', header: { text: '안전사고건수' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, { name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, editable: false, styleName: 'rg-cell-readonly' }, { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false, editable: false, styleName: 'rg-cell-readonly' }, diff --git a/apps/st/src/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn.ts index 8362026b7..9de7acf7c 100644 --- a/apps/st/src/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn.ts @@ -1,6 +1,6 @@ import { ValueType } from "realgrid"; -import { footerDecimal1 } from "@/components/util/grid/footerSummary"; +import { footerAvgDecimal, footerDecimal1 } from "@/components/util/grid/footerSummary"; // 하수도 통계 관리 > 11. 하수 및 분뇨찌꺼기(슬러지)처리 > tab 1: 찌꺼기(슬러지)처리시설현황 export const SlurgeProcessingFacStatusInpColumn = { @@ -37,7 +37,7 @@ export const SlurgeProcessingFacStatusInpColumn = { { name: 'transfPrcsQnt', fieldName: 'transfPrcsQnt', header: { text: '이송처리량(m³/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 함수율 - { name: 'srpFncRt', fieldName: 'srpFncRt', header: { text: '함수율(%, 탈수기준)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'srpFncRt', fieldName: 'srpFncRt', header: { text: '함수율(%, 탈수기준)' }, footer: footerAvgDecimal, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 찌꺼기처리량합계 { @@ -56,7 +56,7 @@ export const SlurgeProcessingFacStatusInpColumn = { 'itslfTs1AltdQnt', 'itslfTs1DryQnt', 'itslfTs1FuelQnt', - 'itslfTs2CmpstgQnt', + 'itslfTs1CmpstgQnt', 'otsdCnsgnRclmQnt', 'otsdCnsgnIncnrQnt', 'otsdCnsgnFuelPwplQnt', @@ -86,7 +86,7 @@ export const SlurgeProcessingFacStatusInpColumn = { 'itslfTs1AltdQnt', 'itslfTs1DryQnt', 'itslfTs1FuelQnt', - 'itslfTs2CmpstgQnt', + 'itslfTs1CmpstgQnt', ], type: 'sum' as const, } @@ -203,7 +203,7 @@ export const SlurgeProcessingFacStatusInpColumn = { { name: 'fuelPrcsFcltNm', fieldName: 'fuelPrcsFcltNm', header: { text: '시설(업체명)' } }, // 자체 찌꺼기 처리량 > 퇴비화 - 쿼리에 없음 (임의 생성) - { name: 'itslfTs2CmpstgQnt', fieldName: 'itslfTs2CmpstgQnt', header: { text: '퇴비화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'itslfTs1CmpstgQnt', fieldName: 'itslfTs1CmpstgQnt', header: { text: '퇴비화' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 자체 찌꺼기 처리량 > 퇴비화 > 시설(업체명) - 쿼리에 없음 (임의 생성) { name: 'cmpsPrcsFcltBzentyNm', fieldName: 'cmpsPrcsFcltBzentyNm', header: { text: '시설(업체명)' } }, @@ -329,7 +329,7 @@ export const SlurgeProcessingFacStatusInpColumn = { 'fuelPrcsFcltNm' ] }, - 'itslfTs2CmpstgQnt', + 'itslfTs1CmpstgQnt', 'cmpsPrcsFcltBzentyNm' ] }, diff --git a/apps/st/src/components/util/column/input/sewagePipe/ConnectionStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewagePipe/ConnectionStatusInpColumn.ts index 75d6c021b..85d322761 100644 --- a/apps/st/src/components/util/column/input/sewagePipe/ConnectionStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewagePipe/ConnectionStatusInpColumn.ts @@ -1,6 +1,7 @@ import { ValueType } from "realgrid"; import type { GridTopButtonConfig } from "@/components/type/DataGrid.type"; +import { footerInt } from "@/components/util/grid/footerSummary"; // 하수도 통계 관리 > 02.하수관로 > tab 6. 시설별 현황관리 // Tab 6-1: 행정구역별 연결 현황 (오른쪽 그리드) @@ -51,7 +52,7 @@ export const TreatPlantConnectionInpColumn = { columns: [ // 하수처리장 정보 { name: 'fcltNm', fieldName: 'fcltNm', header: { text: '하수처리장명' }, editable: false }, - { name: 'fcltCpct', fieldName: 'fcltCpct', header: { text: '용량' }, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false }, + { name: 'fcltCpct', fieldName: 'fcltCpct', header: { text: '용량' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false }, { name: 'fcltSn', fieldName: 'fcltSn', header: { text: '하수처리장코드' }, editable: false }, // 연결여부 { name: 'isConnection', fieldName: 'isConnection', header: { text: '연결여부' }, editable: false }, diff --git a/apps/st/src/components/util/column/input/sewagePipe/FactInstallStatusInpColumn.ts b/apps/st/src/components/util/column/input/sewagePipe/FactInstallStatusInpColumn.ts index 0cadfdf27..1d06827ab 100644 --- a/apps/st/src/components/util/column/input/sewagePipe/FactInstallStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewagePipe/FactInstallStatusInpColumn.ts @@ -1,6 +1,6 @@ import { ValueType } from "realgrid"; -import { footerDecimal2, footerDecimal3,footerInt } from "@/components/util/grid/footerSummary"; +import { footerAvgDecimal, footerDecimal2, footerDecimal3, footerInt } from "@/components/util/grid/footerSummary"; // 하수도 통계 관리 > 02.하수관로 > tab 2. 시설별 설치현황 // SeweragePipeInputFactInstallStatusItem + commonInfo.ctpvNm (그리드 행에 병합) @@ -14,7 +14,7 @@ export const FactInstallStatusInpColumn = { // 하수처리장 정보 { name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' }, { name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' }, - { name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리시설용량(m³/일)' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, + { name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리시설용량(m³/일)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 처리구역면적(km²) { @@ -37,6 +37,7 @@ export const FactInstallStatusInpColumn = { fieldName: 'sprsysVartnRt', header: { text: '분류식화율' }, editable: false, + footer: footerAvgDecimal, styleName: 'rg-cell-total', valueType: ValueType.NUMBER, dataFormat: '#,##0.0', diff --git a/apps/st/src/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn.ts b/apps/st/src/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn.ts index 45aa5f6d1..8075db2f8 100644 --- a/apps/st/src/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn.ts @@ -64,10 +64,10 @@ export const RainfallPumpStationInpColumn = { { name: 'rsvrYn', fieldName: 'rsvrYn', header: { text: '유수지유무(유,무)' } }, RAINFALL_PUMP_YN_VALUES, ), - { name: 'rsvrImpndQnt', fieldName: 'rsvrImpndQnt', header: { text: '저류랑(m³)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'rsvrImpndQnt', fieldName: 'rsvrImpndQnt', header: { text: '저류랑(m³)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 흡수정 - { name: 'abstnRgnImpdQnt', fieldName: 'abstnRgnImpdQnt', header: { text: '저류랑(m³)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'abstnRgnImpdQnt', fieldName: 'abstnRgnImpdQnt', header: { text: '저류랑(m³)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 펌프시설 > 총양수량(m³/min) { @@ -145,7 +145,7 @@ export const RainfallPumpStationInpColumn = { { name: 'ctmnLastDspsMthdExpln', fieldName: 'ctmnLastDspsMthdExpln', header: { text: '협잡물' } }, // 운영실적 - { name: 'oprtngDayCnt', fieldName: 'oprtngDayCnt', header: { text: '가동일수(일/년)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 입력값 3자리만 가능 + { name: 'oprtngDayCnt', fieldName: 'oprtngDayCnt', header: { text: '가동일수(일/년)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 입력값 3자리만 가능 { name: 'grssMltvQnt', fieldName: 'grssMltvQnt', header: { text: '총배수량(m³/년)' }, footer: footerDecimal2, valueType: ValueType.NUMBER, dataFormat: '#,##0.00' }, // 비고 diff --git a/apps/st/src/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn.ts b/apps/st/src/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn.ts index 924af4009..7dd1c043e 100644 --- a/apps/st/src/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn.ts +++ b/apps/st/src/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn.ts @@ -221,7 +221,7 @@ export const SewageReservoirInpColumn = { { name: 'ctmnLastDspsMthdExpln', fieldName: 'ctmnLastDspsMthdExpln', header: { text: '협잡물' } }, // 운영실적 - { name: 'oprtngDayCnt', fieldName: 'oprtngDayCnt', header: { text: '가동일수(일/년)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 3자리 수만 입력 가능 + { name: 'oprtngDayCnt', fieldName: 'oprtngDayCnt', header: { text: '가동일수(일/년)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 3자리 수만 입력 가능 { name: 'grssMltvQnt', fieldName: 'grssMltvQnt', header: { text: '총배수량(m³/년)' }, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' }, // 비고 diff --git a/apps/st/src/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn.ts b/apps/st/src/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn.ts index 28467308a..75778e6bf 100644 --- a/apps/st/src/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/waterReuse/RainFallUtilFactStatusInpColumn.ts @@ -100,7 +100,7 @@ export const RainFallUtilFactStatusInpColumn = { }, // 설치비(백만원) - { name: 'instlAmt', fieldName: 'instlAmt', header: { text: '설치비(백만원)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'instlAmt', fieldName: 'instlAmt', header: { text: '설치비(백만원)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 집수면 종류 { name: 'rcptMynCn', fieldName: 'rcptMynCn', header: { text: '집수면 종류' } }, diff --git a/apps/st/src/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn.ts b/apps/st/src/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn.ts index 0028ae8d9..660dd965b 100644 --- a/apps/st/src/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn.ts +++ b/apps/st/src/components/util/column/input/waterReuse/RecycledWaterStatusInpColumn.ts @@ -143,7 +143,7 @@ export const RecycledWaterStatusInpColumn = { ), // 연간운영비(백만원/년) - { name: 'ruswtrOperAmt', fieldName: 'ruswtrOperAmt', header: { text: '연간운영비(백만원/년)' }, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'ruswtrOperAmt', fieldName: 'ruswtrOperAmt', header: { text: '연간운영비(백만원/년)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 비고 { name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } }, 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 7ff4b3c98..91c7814d0 100644 --- a/apps/st/src/entities/statistics/api/general-verification-service.ts +++ b/apps/st/src/entities/statistics/api/general-verification-service.ts @@ -21,6 +21,7 @@ import type { VerifyDetailUpdateRequest, VerifyResultListRequest, VerifyResultListResponse, + VerifyExcelDataResponse, VrfcOperatorsResponse, } from "../model/general-verification-interface"; @@ -176,6 +177,13 @@ export class GeneralverificationApiService implements GeneralverificationService ); return response.data; } + + public async getVerifyExcelList(): Promise { + const response = await stClient.get( + `${ENDPOINT.ST_SERVICE.INPUTVERIFY}/excel-list`, + ); + return response.data; + } } export const generalverificationApiService = diff --git a/apps/st/src/entities/statistics/index.ts b/apps/st/src/entities/statistics/index.ts index 8123b72f4..390dfb71c 100644 --- a/apps/st/src/entities/statistics/index.ts +++ b/apps/st/src/entities/statistics/index.ts @@ -10,6 +10,18 @@ export { dashboardApi } from "./api/dashboard/dashboard-service"; export { useScheduleInfo } from "./api/dashboard/schedule-info-queries"; export { scheduleInfoApi } from "./api/dashboard/schedule-info-service"; export { imrApi } from "./api/imr-api"; +export { + filterBySelectedAdmdstCd, + findSidoBySearch, + isDoListSido, + isGugunOkForToolbar, + isGugunRequiredSido, + isSubmitOkForToolbar, + resolveNoApplicableForRequest, + toInputSearchRegion, + type InputSearchRegion, + type SidoGugunLookup, +} from "./lib/sido-gugun"; export { noticeTalkSendingApi } from "./api/notice-talk/notice-talk-sending-service"; export { useNtftalkTemplateList, @@ -131,6 +143,8 @@ export { type RejectRequest, } from "./model/statistics-approval-interface"; export { + type SearchCondition, + type Sido, type StatsInputStatusApproveBatchRequest, type StatsInputStatusApproveItem, type StatsInputStatusApproveKind, diff --git a/apps/st/src/entities/statistics/lib/sido-gugun.ts b/apps/st/src/entities/statistics/lib/sido-gugun.ts new file mode 100644 index 000000000..ee0510f3f --- /dev/null +++ b/apps/st/src/entities/statistics/lib/sido-gugun.ts @@ -0,0 +1,133 @@ +import { doList } from "@/components/util/column/input/DoList"; + +import type { Sido } from "../model/statistics-interface"; + +export type SidoGugunLookup = { + sido?: string | null; + bsnmCd?: string | null; + gugun?: string | null; +}; + +export function findSidoBySearch( + sidoList: readonly Sido[] | undefined, + query: SidoGugunLookup | null | undefined, +): Sido | undefined { + if (!query) return undefined; + const list = sidoList ?? []; + const sidoName = query.sido?.trim() ?? ""; + if (sidoName && sidoName !== "ALL") { + const byName = list.find((item) => item.sido === sidoName); + if (byName) return byName; + } + const bsnmCd = query.bsnmCd?.trim() ?? ""; + if (bsnmCd) { + return list.find((item) => item.bsnmCd === bsnmCd); + } + return undefined; +} + +/** `gugunSkipYn === "N"` 이면 시군구 선택 필수 (검색폼·시군구 API용) */ +export function isGugunRequiredSido( + sidoList: readonly Sido[] | undefined, + query: SidoGugunLookup | null | undefined, +): boolean { + return findSidoBySearch(sidoList, query)?.gugunSkipYn === "N"; +} + +function hasSpecificGugun(values: SidoGugunLookup | null | undefined): boolean { + const gugun = values?.gugun?.trim() ?? ""; + return Boolean(gugun && gugun !== "ALL"); +} + +/** 시도 단위 사업자코드가 `doList`(도)에 해당하는지. 시군구 코드는 쓰지 않는다. */ +export function isDoListSido( + sidoList: readonly Sido[] | undefined, + values: SidoGugunLookup | null | undefined, +): boolean { + const sido = findSidoBySearch(sidoList, { sido: values?.sido }); + if (sido?.bsnmCd) return doList.includes(sido.bsnmCd); + if (hasSpecificGugun(values)) return false; + return doList.includes((values?.bsnmCd ?? "").trim()); +} + +/** 도: 시군구 필수. 광역시·세종·제주: 시군구와 무관하게 통과 */ +export function isGugunOkForToolbar( + sidoList: readonly Sido[] | undefined, + values: SidoGugunLookup | null | undefined, +): boolean { + if (!isDoListSido(sidoList, values)) return true; + return hasSpecificGugun(values); +} + +/** 도: 시군구 필수. 광역시·세종·제주: 시군구가 선택돼 있으면 제출/제출취소·해당사항 없음 숨김 */ +export function isSubmitOkForToolbar( + sidoList: readonly Sido[] | undefined, + values: SidoGugunLookup | null | undefined, +): boolean { + const hasGugun = hasSpecificGugun(values); + if (isDoListSido(sidoList, values)) return hasGugun; + return !hasGugun; +} + +/** + * 저장·제출 `noApplicable`. + * 해당사항 없음 버튼이 보이는 경우(도, 또는 비도+시군구 전체)는 체크 여부. + * 버튼이 숨겨진 비도+시군구에서는 saveList가 비면 true. + */ +export function resolveNoApplicableForRequest( + sidoList: readonly Sido[] | undefined, + values: SidoGugunLookup | null | undefined, + checked: boolean, + isSaveListEmpty: boolean, +): boolean { + if (isSubmitOkForToolbar(sidoList, values)) return checked; + return isSaveListEmpty; +} + +export type InputSearchRegion = { + sewerBzmnCd: string; + sggNm?: string; +}; + +/** + * 입력 화면 조회·저장·제출용 지역 조건. + * `doList`에 없으면 시군구와 무관하게 시도 `sewerBzmnCd`만 쓰고 `sggNm`은 생략한다. + */ +export function toInputSearchRegion( + sidoList: readonly Sido[] | undefined, + values: SidoGugunLookup | null | undefined, +): InputSearchRegion { + const sido = findSidoBySearch(sidoList, { sido: values?.sido }); + if (!isDoListSido(sidoList, values)) { + return { + sewerBzmnCd: (sido?.bsnmCd ?? "").trim(), + }; + } + const gugun = values?.gugun?.trim() ?? ""; + return { + sewerBzmnCd: (values?.bsnmCd || sido?.bsnmCd || "").trim(), + sggNm: gugun && gugun !== "ALL" ? gugun : "", + }; +} + +/** + * `doList`가 아닌 시도에서 시군구가 선택된 경우, + * 조회 결과를 `admdstCd`가 선택 시군구 코드와 같은 행만 남긴다. + */ +export function filterBySelectedAdmdstCd( + rows: readonly T[] | null | undefined, + sidoList: readonly Sido[] | undefined, + values: SidoGugunLookup | null | undefined, +): T[] { + const list = rows ? [...rows] : []; + if (isDoListSido(sidoList, values)) return list; + if (!hasSpecificGugun(values)) return list; + const selectedCd = (values?.bsnmCd ?? "").trim(); + if (!selectedCd) return list; + return list.filter((row) => { + const cd = String( + (row as { admdstCd?: unknown }).admdstCd ?? "", + ).trim(); + return cd === selectedCd; + }); +} 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 f71c0d932..c8bd2a86d 100644 --- a/apps/st/src/entities/statistics/model/general-verification-interface.ts +++ b/apps/st/src/entities/statistics/model/general-verification-interface.ts @@ -264,6 +264,22 @@ export interface VerifyResultListResponse { httpStatus?: number; } +/** 검증 엑셀 데이터 조회 — 요청 파라미터 없음 */ +export interface VerifyExcelDataItem { + trgtStatsNm: string; + vrfcNm: string; + vrfcClsfNm: string; + dcptDigtCnt: number; + dcptPrcsMthNm: string; + vrfcEqtnCn: string; +} + +export interface VerifyExcelDataResponse { + status: string; + message?: string; + data: VerifyExcelDataItem[]; +} + export interface GeneralverificationService { getGeneralverificationList( param: GeneralverificationRequest, @@ -305,4 +321,5 @@ export interface GeneralverificationService { getVerifyResultList( param: VerifyResultListRequest, ): Promise; + getVerifyExcelList(): Promise; } 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 d65b3ff32..c6b572c30 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 @@ -136,7 +136,7 @@ export type ExcretaTreatmentFacilitySaveItem = Omit< export interface ExcretaTreatmentFacilitySaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -157,7 +157,7 @@ export type ExcretaTreatmentFacilitySubmitItem = ExcretaTreatmentFacilitySaveIte export interface ExcretaTreatmentFacilitySubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: 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 10c712d7a..00e271646 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 @@ -230,7 +230,7 @@ export interface SewageTreatFactStatusSaveItem { export interface SewageTreatFactStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -248,7 +248,7 @@ export interface SewageTreatFactStatusSaveResponse { export interface SewageTreatFactStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -316,7 +316,7 @@ export interface SepticTankStatusSaveItem { export interface SepticTankStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -334,7 +334,7 @@ export interface SepticTankStatusSaveResponse { export interface SepticTankStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -379,7 +379,7 @@ export interface PrivateSewageDesigStatusSaveItem { export interface PrivateSewageDesigStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -397,7 +397,7 @@ export interface PrivateSewageDesigStatusSaveResponse { export interface PrivateSewageDesigStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: 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 b6abe3d44..7a13385e1 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 @@ -129,7 +129,7 @@ export type PumpingStationSaveItem = Omit< export interface PumpingStationSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -150,7 +150,7 @@ export type PumpingStationSubmitItem = PumpingStationSaveItem; export interface PumpingStationSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: 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 891b8fb06..b85d2839f 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 @@ -228,7 +228,7 @@ export type PrivateSewageTreatRelatedIndustrySaveItem = Omit< export interface SewageExcretaRelatedSaveRequestBase { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: 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 c8518b065..41c619a41 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 @@ -90,7 +90,7 @@ export interface SlurgeProcessingFacStatusItem { fuelPrcsIncnrQnt: number; fuelPrcsFuelQnt: number; fuelPrcsFcltNm: string; - itslfTs2CmpstgQnt: number; + itslfTs1CmpstgQnt: number; cmpsPrcsFcltBzentyNm: string; otsdCnsgnWhol: number; otsdCnsgnRclmQnt: number; @@ -161,7 +161,7 @@ export type SlurgeProcessingFacStatusSaveItem = Omit< export interface SlurgeProcessingFacStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -180,7 +180,7 @@ export type SlurgeProcessingFacStatusSubmitItem = SlurgeProcessingFacStatusSaveI export interface SlurgeProcessingFacStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -271,7 +271,7 @@ export type DigestorOperatingStatusSaveItem = Omit< export interface DigestorOperatingStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -290,7 +290,7 @@ export type DigestorOperatingStatusSubmitItem = DigestorOperatingStatusSaveItem; export interface DigestorOperatingStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -374,7 +374,7 @@ export type SewageSlurgeProcessingFacStatusSaveItem = Omit< export interface SewageSlurgeProcessingFacStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -394,7 +394,7 @@ export type SewageSlurgeProcessingFacStatusSubmitItem = export interface SewageSlurgeProcessingFacStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -483,7 +483,7 @@ export type ExcretaSlurgeProcessingStatusSaveItem = Omit< export interface ExcretaSlurgeProcessingStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -501,7 +501,7 @@ export type ExcretaSlurgeProcessingStatusSubmitItem = ExcretaSlurgeProcessingSta export interface ExcretaSlurgeProcessingStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: 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 ca7657561..d84c19f09 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 @@ -122,7 +122,7 @@ export interface SewagePipeMaintainRepairSaveItem { export interface SewagePipeMaintainRepairSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -139,7 +139,7 @@ export interface SewagePipeMaintainRepairSaveResponse { export interface SewagePipeMaintainRepairSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -193,7 +193,7 @@ export interface SewagePipeMaintainDredgingSaveItem { export interface SewagePipeMaintainDredgingSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -210,7 +210,7 @@ export interface SewagePipeMaintainDredgingSaveResponse { export interface SewagePipeMaintainDredgingSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: 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 2ab79fcbd..338acf26a 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 @@ -211,7 +211,7 @@ export type SewageReservoirSaveItem = Omit< export interface SewageReservoirSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -230,7 +230,7 @@ export interface SewageReservoirSaveResponse { export interface SewageReservoirSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -301,7 +301,7 @@ export type RainfallPumpStationSaveItem = Omit< export interface RainfallPumpStationSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -317,7 +317,7 @@ export type RainfallPumpStationSaveResponse = SewageReservoirSaveResponse; export interface RainfallPumpStationSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; diff --git a/apps/st/src/entities/statistics/model/input/statistics-input-sewerage-finance-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-input-sewerage-finance-interface.ts index a1da3c2a3..2625d943e 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-input-sewerage-finance-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-input-sewerage-finance-interface.ts @@ -130,7 +130,7 @@ export interface SewerageFinanceSettleSaveItem { export interface SewerageFinanceSettleSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -147,7 +147,7 @@ export interface SewerageFinanceSettleSaveResponse { export interface SewerageFinanceSettleSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -175,7 +175,7 @@ export interface SewerageFinanceSettleSubmitResponse { export interface SewerageFinanceSubmitRequestBase { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -268,7 +268,7 @@ export interface SewerageChargeSaveItem { export interface SewerageChargeSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -392,7 +392,7 @@ export interface SewerageUsageFeeSaveItem { export interface SewerageUsageFeeSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -486,7 +486,7 @@ export interface CauserBurdenSaveItem { export interface CauserBurdenSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: 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 4508e817c..83c2b1647 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 @@ -147,7 +147,7 @@ export interface SewerageServiceLevelComplaintSaveItem { export interface SewerageServiceLevelComplaintSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -183,7 +183,7 @@ export interface SewerageServiceLevelResidentFriendlyFactSaveItem { export interface SewerageServiceLevelResidentFriendlyFactSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -204,7 +204,7 @@ export type SewerageServiceLevelComplaintSubmitItem = SewerageServiceLevelCompla export interface SewerageServiceLevelComplaintSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -227,7 +227,7 @@ export type SewerageServiceLevelResidentFriendlyFactSubmitItem = export interface SewerageServiceLevelResidentFriendlyFactSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: 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 dc5796286..f53f02ed3 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 @@ -127,7 +127,7 @@ export interface PublicSewageTreatWaterReuseSaveItem { export interface PublicSewageTreatWaterReuseSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -145,7 +145,7 @@ export type PublicSewageTreatWaterReuseSubmitItem = PublicSewageTreatWaterReuseS export interface PublicSewageTreatWaterReuseSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -238,7 +238,7 @@ export type RecycledWaterStatusSaveItem = Omit< export interface RecycledWaterStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -257,7 +257,7 @@ export type RecycledWaterStatusSubmitItem = RecycledWaterStatusSaveItem; export interface RecycledWaterStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -353,7 +353,7 @@ export type RainFallUtilFactStatusSaveItem = Omit< export interface RainFallUtilFactStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -372,7 +372,7 @@ export type RainFallUtilFactStatusSubmitItem = RainFallUtilFactStatusSaveItem; export interface RainFallUtilFactStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -452,7 +452,7 @@ export type TreatWaterReuseDesignConstStatusSaveItem = Omit< export interface TreatWaterReuseDesignConstStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -472,7 +472,7 @@ export type TreatWaterReuseDesignConstStatusSubmitItem = export interface TreatWaterReuseDesignConstStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -552,7 +552,7 @@ export type TreatWaterReuseBusinessApprStatusSaveItem = Omit< export interface TreatWaterReuseBusinessApprStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -572,7 +572,7 @@ export type TreatWaterReuseBusinessApprStatusSubmitItem = export interface TreatWaterReuseBusinessApprStatusSubmitRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -601,7 +601,7 @@ export interface TreatWaterReuseBusinessApprStatusSubmitResponse { export interface WaterReuseOperationDataUpdateRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: 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 6b8230e93..96b1a6abc 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 @@ -143,7 +143,7 @@ export interface PublicSewageMainFacilityResponse { export interface PublicSewageMappingTargetsRequestParams { sewerBzmnCd: string; ctpvNm: string; - sggNm: string; + sggNm?: string; statsYr: string; fcltSn: string; } @@ -229,7 +229,7 @@ export interface PublicSewageFacilitySaveItem { export interface PublicSewageFacilitySaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -253,7 +253,7 @@ export interface PublicSewageFacilitySaveResponse { export interface PublicSewageFacilitySubmitRequestBase { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; noApplicable?: boolean; @@ -284,7 +284,7 @@ export interface PublicSewageFacilitySubmitResponse { export interface PublicSewageFacilityOperationDataUpdateRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: 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 9854acce2..81b8cf17c 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 @@ -129,7 +129,7 @@ export type SeweragePipeInputFactInstallStatusSaveItem = Omit< export interface SeweragePipeInputRegionalFactStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -146,7 +146,7 @@ export interface SeweragePipeInputRegionalFactStatusSaveResponse { export interface SeweragePipeInputSubmitRequestBase { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; laePicNm: string; @@ -176,7 +176,7 @@ export interface SeweragePipeInputRegionalFactStatusSubmitResponse { export interface SeweragePipeInputFactInstallStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -317,7 +317,7 @@ export interface SeweragePipeInputPipeTypeStatusSaveItem { export interface SeweragePipeInputPipeTypeStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -389,7 +389,7 @@ export interface SeweragePipeInputPipeDiameterStatusSaveItem { export interface SeweragePipeInputPipeDiameterStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -487,7 +487,7 @@ export interface SeweragePipeInputManholeStatusSaveItem { export interface SeweragePipeInputManholeStatusSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; superAdmin?: string; @@ -602,7 +602,7 @@ export interface SeweragePipeFactConnectionSaveItem { /** 시설별 연결관계 저장 요청 */ export interface SeweragePipeFactConnectionSaveRequest { ctpvNm: string; - sggNm: string; + sggNm?: string; fcltSn: string; statsYr: string; swtrmtFcltCd: 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 5ca20a97f..141e3b992 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 @@ -120,7 +120,7 @@ export interface SewerageSupplyRateSaveItem { export interface SewerageSupplyRateSaveRequest { statsYr: string; ctpvNm: string; - sggNm: string; + sggNm?: string; sewerBzmnCd: string; userId: string; sewerBzmnArea?: number; diff --git a/apps/st/src/shared/lib/index.ts b/apps/st/src/shared/lib/index.ts index 45383bf3a..85face19e 100644 --- a/apps/st/src/shared/lib/index.ts +++ b/apps/st/src/shared/lib/index.ts @@ -11,4 +11,10 @@ export { useActiveTabRegAuthorityAllowed, useStatisticsInputToolbarGroupGate, } from "./use-statistics-input-toolbar-group-gate"; +export { + fetchSearchCondition, + fetchSearchConditionInput, + SEARCH_CONDITION_SWR_KEY, + useSearchConditionSidoList, +} from "./use-search-condition-sido-list"; export { useUserStore } from "./use-user-store"; 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 85fb17bfc..b64ca0813 100644 --- a/apps/st/src/shared/lib/stats-cd-input-navigation.ts +++ b/apps/st/src/shared/lib/stats-cd-input-navigation.ts @@ -1,8 +1,9 @@ import { menuMatchesStatisticsTabId } from "@/shared/lib/statistics-menu-url"; import type { MenuItem } from "@/entities/menu"; +import { isGugunRequiredSido } from "@/entities/statistics"; +import type { Sido } from "@/entities/statistics/model/statistics-interface"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import { doNameList } from "@/components/util/column/input/DoList"; import { EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS, INPUT_TAB_KEY_BY_STATS_CD, @@ -30,12 +31,17 @@ export type StatisticsInputPendingSearch = { export function verificationSearchToFormValues( s: StatisticsInputPendingSearch, + sidoList?: readonly Sido[], ): SearchFormValues { const ctpvNmTrimmed = s.ctpvNm?.trim() ?? ""; const sggNmTrimmed = s.sggNm?.trim() ?? ""; - /** 시군구 필수 도(`DoList.ts`의 `doNameList`)일 때만 API `sggNm`을 검색폼에 반영 */ + /** 시군구 필수 시도(`gugunSkipYn === "N"`)일 때만 API `sggNm`을 검색폼에 반영 */ const useSggNm = - sggNmTrimmed.length > 0 && doNameList.includes(ctpvNmTrimmed); + sggNmTrimmed.length > 0 && + isGugunRequiredSido(sidoList, { + sido: ctpvNmTrimmed, + bsnmCd: s.sewerBzmnCd, + }); return { year: s.statsYr, diff --git a/apps/st/src/shared/lib/use-search-condition-sido-list.ts b/apps/st/src/shared/lib/use-search-condition-sido-list.ts new file mode 100644 index 000000000..45a6595cc --- /dev/null +++ b/apps/st/src/shared/lib/use-search-condition-sido-list.ts @@ -0,0 +1,61 @@ +"use client"; + +import useSWR from "swr"; + +import type { + ApiResponse, + SearchCondition, + Sido, +} from "@/entities/statistics/model/statistics-interface"; +import { statisticsApi } from "@/entities/statistics/api/statistics-service"; + +import { useUserStore } from "./use-user-store"; + +export type SearchConditionKind = "default" | "input"; + +export const SEARCH_CONDITION_SWR_KEY = ( + instSeCd: string, + kind: SearchConditionKind = "default", +) => + [ + kind === "input" ? "sido-list-input" : "sido-list", + instSeCd, + ] as const; + +export async function fetchSearchCondition( + key: readonly [string, string], +): Promise> { + return statisticsApi.getSidoList({ instSeCd: key[1] }); +} + +export async function fetchSearchConditionInput( + key: readonly [string, string], +): Promise> { + return statisticsApi.getSidoListInput({ instSeCd: key[1] }); +} + +const SIDO_LIST_SWR_OPTIONS = { + revalidateOnFocus: false, + revalidateOnReconnect: false, + revalidateIfStale: false, + dedupingInterval: 2000, + shouldRetryOnError: false, +} as const; + +/** SearchForm과 동일한 SWR 키로 시도 목록(`gugunSkipYn` 포함)을 재사용합니다. */ +export function useSearchConditionSidoList(options?: { isInput?: boolean }): { + sidoList: Sido[]; + isReady: boolean; +} { + const instSeCd = useUserStore((state) => state.userInfo?.instSeCd ?? ""); + const kind: SearchConditionKind = options?.isInput ? "input" : "default"; + const { data } = useSWR( + SEARCH_CONDITION_SWR_KEY(instSeCd, kind), + kind === "input" ? fetchSearchConditionInput : fetchSearchCondition, + SIDO_LIST_SWR_OPTIONS, + ); + return { + sidoList: data?.data?.sidoList ?? [], + isReady: data != 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 index 89322d7f1..40bcc0c2d 100644 --- a/apps/st/src/shared/lib/use-statistics-input-pending-navigation.ts +++ b/apps/st/src/shared/lib/use-statistics-input-pending-navigation.ts @@ -6,6 +6,7 @@ import { type StatisticsInputPendingSearch, verificationSearchToFormValues, } from "@/shared/lib/stats-cd-input-navigation"; +import { useSearchConditionSidoList } from "@/shared/lib/use-search-condition-sido-list"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store"; @@ -43,9 +44,11 @@ export function useStatisticsInputPendingNavigation({ const activeTabId = useTabStore((s) => s.activeTabId); const inputFocusNonce = useStatisticsInputFocusStore((s) => s.nonce); + const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true }); useLayoutEffect(() => { if (activeTabId !== urlPath) return; + if (!isReady) return; const pending = useStatisticsInputFocusStore .getState() .takePendingForUrl(urlPath); @@ -54,12 +57,15 @@ export function useStatisticsInputPendingNavigation({ handlersRef.current.onPending?.(pending); if (pending.search) { - const values = verificationSearchToFormValues(pending.search); + const values = verificationSearchToFormValues( + pending.search, + sidoList, + ); setVerificationSearchDefaults(values); autoSearchRef.current = { values, tabKey: pending.tabKey }; setSearchFormRemountKey((k) => k + 1); } - }, [activeTabId, inputFocusNonce, urlPath]); + }, [activeTabId, inputFocusNonce, urlPath, isReady, sidoList]); useEffect(() => { const job = autoSearchRef.current;