987 lines
50 KiB
TypeScript
987 lines
50 KiB
TypeScript
import { useState, useMemo, useRef, useEffect } from "react";
|
|
|
|
import SearchForm from "@/components/module/SearchForm";
|
|
import DataGrid from "@/components/module/DataGrid";
|
|
import SplitDataGrid from "@/components/module/SplitDataGrid";
|
|
import GridTopActionButton from "@/components/module/GridTopActionButton";
|
|
import SubmitButton from "@/components/button/SumitButton";
|
|
import SaveButton from "@/components/button/SaveButton";
|
|
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
|
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
|
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
|
|
|
import { SewagePipeInpColumnInfoList } from "@/components/util/column/input/sewagePipe/SewagePipeInpColumn";
|
|
import type { ColumnInfo, ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type";
|
|
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
|
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
|
|
import { useAdminStore } from "@/store/admin-store";
|
|
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
import { doList } from "@/components/util/column/input/DoList";
|
|
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service";
|
|
import type {
|
|
SeweragePipeInputFactInstallStatusSaveRequest,
|
|
SeweragePipeInputRegionalFactStatusSaveRequest,
|
|
SeweragePipeInputStatusInfo,
|
|
SeweragePipeInputVerificationResult,
|
|
SeweragePipeRequestParams,
|
|
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface";
|
|
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
|
import { FactInstallStatusInpColumn } from "@/components/util/column/input/sewagePipe/FactInstallStatusInpColumn";
|
|
import { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn";
|
|
import { toGridRows } from "@/components/util/grid/gridFunction";
|
|
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
|
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
|
|
|
/** 탭별 status + 입력기간으로 편집 가능 여부 */
|
|
const computeTabEnterable = (
|
|
statusInfo: SeweragePipeInputStatusInfo | null | undefined,
|
|
periodEnterable: boolean,
|
|
): boolean => {
|
|
if (statusInfo?.laeSbmsnSttsCd) {
|
|
if (["2", "3"].includes(statusInfo.laeSbmsnSttsCd)) {
|
|
return false;
|
|
}
|
|
return periodEnterable;
|
|
}
|
|
return periodEnterable;
|
|
};
|
|
|
|
function buildSewagePipeSearchParam(
|
|
values: SearchFormValues,
|
|
overrides?: { mode?: string },
|
|
): SeweragePipeRequestParams {
|
|
return {
|
|
statsYr: values.year,
|
|
sewerBzmnCd: values.bsnmCd,
|
|
ctpvNm: values.sido,
|
|
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
|
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
|
};
|
|
}
|
|
|
|
/** ST00020006(연결)은 행이 좌·우 그리드 키에만 있음 */
|
|
type GridRowWithVerification = Record<string, string | number> & {
|
|
verificationResult?: SeweragePipeInputVerificationResult;
|
|
};
|
|
|
|
function tabRowDataExists(
|
|
m: Map<string, Record<string, string | number>[]>,
|
|
tabKey: string,
|
|
): boolean {
|
|
if (tabKey === "ST00020006") {
|
|
const left = m.get("treatmentPlantConnectionInp");
|
|
const right = m.get("adminAreaConnectionInp");
|
|
return (
|
|
(Array.isArray(left) && left.length > 0) ||
|
|
(Array.isArray(right) && right.length > 0)
|
|
);
|
|
}
|
|
const rows = m.get(tabKey);
|
|
return Array.isArray(rows) && rows.length > 0;
|
|
}
|
|
|
|
/** 조회 직후 해당 탭에만 저장하는 상단 툴바 노출 조건 */
|
|
type TabToolbarSnapshot = {
|
|
showSaveInToolbar: boolean;
|
|
showEnterableToolbarButtons: boolean;
|
|
showActionToolbarCard: boolean;
|
|
};
|
|
|
|
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
|
return doList.includes(values?.bsnmCd || "")
|
|
? Boolean(values?.gugun && values.gugun !== "ALL")
|
|
: true;
|
|
}
|
|
|
|
/**
|
|
* @param searchContext 조회에 사용된 검색값(해당 요청 기준으로 구군·사업자 조건 반영)
|
|
* @param treatAsSearched (hasRow || 이 값) 일 때 카드 후보 — load 내부에서는 searchDoneRef 반영용
|
|
*/
|
|
function computeTabToolbarSnapshot(
|
|
statusInfo: SeweragePipeInputStatusInfo | null,
|
|
hasRowData: boolean,
|
|
treatAsSearched: boolean,
|
|
searchContext: SearchFormValues | null,
|
|
periodEnterable: boolean,
|
|
isAdminInfo: boolean,
|
|
): TabToolbarSnapshot {
|
|
const gugunOk = buildGugunOkForToolbar(searchContext);
|
|
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
|
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
|
|
const showEnterableToolbarButtons = enterable && gugunOk;
|
|
const showActionToolbarCard =
|
|
(hasRowData || treatAsSearched) &&
|
|
(showSaveInToolbar ||
|
|
(statusInfo?.laeSbmsnSttsCd === "2" &&
|
|
(isAdminInfo || (enterable && gugunOk))));
|
|
return { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard };
|
|
}
|
|
|
|
// 하수도 통계 관리 > 02. 하수관로
|
|
const SewagePipeInp = () => {
|
|
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
|
const { setMessage: alert } = useAlertStore((state) => state);
|
|
const [activeTab, setActiveTab] = useState<string>(SewagePipeInpColumnInfoList[0]?.tabKey ?? "ST00020001");
|
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
|
|
const [lastSearchedYear, setLastSearchedYear] = useState<string | null>(null);
|
|
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
|
|
const [tabStatusInfoMap, setTabStatusInfoMap] = useState<Map<string, SeweragePipeInputStatusInfo | null>>(new Map());
|
|
// const [showDailog, setShowDailog] = useState<boolean>(false);
|
|
// const [isExceptionSubmitFlow, setIsExceptionSubmitFlow] = useState(false);
|
|
const [statsCd, setStatsCd] = useState<string>('');
|
|
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
|
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
|
/** API 연결 전에도 검색 후 툴바 등 UI를 쓸 수 있게 함 */
|
|
const [hasSearched, setHasSearched] = useState(false);
|
|
/** checkInputPeriod — columnInfo useMemo가 입력기간 변경을 반영하도록 state로도 유지 */
|
|
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
|
/** 탭별 조회 시점의 상단 버튼 노출 조건(해당 탭을 다시 조회할 때만 그 탭 항목 갱신) */
|
|
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<Map<string, TabToolbarSnapshot>>(
|
|
() => new Map(),
|
|
);
|
|
const { confirm } = useConfirm();
|
|
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
|
|
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
|
|
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
|
|
const globalProgressVisible = useGlobalProgressStore((state) => state.visible);
|
|
const { isAdminInfo } = useAdminStore();
|
|
|
|
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
|
const getFactInstallGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
|
const searchParamRef = useRef<SeweragePipeRequestParams | null>(null);
|
|
/** 검색 시 checkInputPeriod 결과 — 탭 전환 지연 조회 시 편집 가능 여부와 동일하게 맞춤 */
|
|
const periodEnterableRef = useRef(true);
|
|
/** 검색 완료 후(API 연결 전) 탭별 지연 로그용 — 첫 탭에 행이 없어도 동작하도록 보조 */
|
|
const searchDoneRef = useRef(false);
|
|
const lazyTabLoggedRef = useRef<Set<string>>(new Set());
|
|
|
|
const firstTabKey = SewagePipeInpColumnInfoList[0]?.tabKey ?? "ST00020001";
|
|
const activeTabRef = useRef<string>(firstTabKey);
|
|
|
|
useEffect(() => {
|
|
activeTabRef.current = activeTab;
|
|
}, [activeTab]);
|
|
|
|
const syncTopStatusIfStillOnTab = (loadedTabKey: string, nextStatsCd: string) => {
|
|
if (loadedTabKey === activeTabRef.current) {
|
|
setStatsCd(nextStatsCd);
|
|
}
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setRowDataMap(new Map());
|
|
setTabStatusInfoMap(new Map());
|
|
setTabStatsCdMap(new Map());
|
|
setLastSearchedYear(null);
|
|
setLastSearchedValues(null);
|
|
setHasSearched(false);
|
|
setInputPeriodEnterable(true);
|
|
setToolbarSnapshotByTab(new Map());
|
|
searchParamRef.current = null;
|
|
searchDoneRef.current = false;
|
|
lazyTabLoggedRef.current.clear();
|
|
periodEnterableRef.current = true;
|
|
activeTabRef.current = firstTabKey;
|
|
setActiveTab(firstTabKey);
|
|
setStatsCd("");
|
|
};
|
|
|
|
const columnInfo = useMemo(() => {
|
|
return SewagePipeInpColumnInfoList.map((tab: ColumnInfoList) => {
|
|
const canEdit =
|
|
computeTabEnterable(tabStatusInfoMap.get(tab.tabKey) ?? null, inputPeriodEnterable) ||
|
|
isAdminInfo;
|
|
const applyEditable = (cols: ColumnConfig[] | undefined) =>
|
|
(cols ?? []).map((col: ColumnConfig) =>
|
|
canEdit
|
|
? col
|
|
: { ...col, editable: false as const, styleName: col.styleName ?? "rg-cell-readonly" }
|
|
);
|
|
|
|
if (tab.left && tab.right) {
|
|
return {
|
|
...tab,
|
|
left: { ...tab.left, columns: applyEditable(tab.left.columns) },
|
|
right: { ...tab.right, columns: applyEditable(tab.right.columns) },
|
|
};
|
|
}
|
|
return {
|
|
...tab,
|
|
columns: applyEditable(tab.columns),
|
|
};
|
|
});
|
|
}, [tabStatusInfoMap, isAdminInfo, inputPeriodEnterable]);
|
|
|
|
const commitToolbarSnapshotForLoadedTab = (
|
|
tabKey: string,
|
|
statusInfo: SeweragePipeInputStatusInfo | null,
|
|
hasRowData: boolean,
|
|
searchContext: SearchFormValues,
|
|
) => {
|
|
setToolbarSnapshotByTab((prev) => {
|
|
const next = new Map(prev);
|
|
next.set(
|
|
tabKey,
|
|
computeTabToolbarSnapshot(
|
|
statusInfo,
|
|
hasRowData,
|
|
searchDoneRef.current,
|
|
searchContext,
|
|
periodEnterableRef.current,
|
|
isAdminInfo,
|
|
),
|
|
);
|
|
return next;
|
|
});
|
|
};
|
|
|
|
/** mergeIntoExistingMap: true면 기존 맵에 병합(다른 탭 rowData·검색 후 다른 탭 캐시 유지). false면 맵 전체 교체 */
|
|
const loadSewagePipeActiveTabData = async (
|
|
tabKey: string,
|
|
searchParam: SeweragePipeRequestParams,
|
|
values: SearchFormValues,
|
|
mergeIntoExistingMap: boolean,
|
|
updateLastSearchedValues: boolean,
|
|
): Promise<boolean> => {
|
|
const applyRowMap = (apply: (m: Map<string, Record<string, string | number>[]>) => void) => {
|
|
if (mergeIntoExistingMap) {
|
|
setRowDataMap((prev) => {
|
|
const next = new Map(prev);
|
|
apply(next);
|
|
return next;
|
|
});
|
|
} else {
|
|
const next = new Map<string, Record<string, string | number>[]>();
|
|
apply(next);
|
|
setRowDataMap(next);
|
|
}
|
|
};
|
|
|
|
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) => ({
|
|
ctpvNm: commonInfo.ctpvNm,
|
|
...d,
|
|
}));
|
|
applyRowMap((m) => m.set('ST00020001', toGridRows(convertedData)));
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
|
if (updateLastSearchedValues) setLastSearchedValues(values);
|
|
commitToolbarSnapshotForLoadedTab(
|
|
tabKey,
|
|
response.data.statusInfo,
|
|
convertedData.length > 0,
|
|
values,
|
|
);
|
|
return true;
|
|
}
|
|
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
|
|
return false;
|
|
}
|
|
if (tabKey === 'ST00020002') {
|
|
const response = await statisticsInputSewPipeApiService.getFactInstallStatus(searchParam);
|
|
if (response.status === 'success') {
|
|
const commonInfo = response.data.commonInfo;
|
|
const convertedData = (response.data.factInstallStatusList ?? []).map((d) => ({
|
|
ctpvNm: commonInfo.ctpvNm,
|
|
...d,
|
|
}));
|
|
applyRowMap((m) => m.set('ST00020002', toGridRows(convertedData)));
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
|
if (updateLastSearchedValues) setLastSearchedValues(values);
|
|
commitToolbarSnapshotForLoadedTab(
|
|
tabKey,
|
|
response.data.statusInfo,
|
|
convertedData.length > 0,
|
|
values,
|
|
);
|
|
return true;
|
|
}
|
|
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
|
|
return false;
|
|
}
|
|
if (tabKey === 'ST00020003') {
|
|
const response = await statisticsInputSewPipeApiService.getPipeTypeStatus(searchParam);
|
|
if (response.status === 'success') {
|
|
const commonInfo = response.data.commonInfo;
|
|
const convertedData = (response.data.pipeTypeStatusList ?? []).map((d) => ({
|
|
ctpvNm: commonInfo.ctpvNm,
|
|
...d,
|
|
}));
|
|
applyRowMap((m) => m.set('ST00020003', toGridRows(convertedData)));
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
|
if (updateLastSearchedValues) setLastSearchedValues(values);
|
|
commitToolbarSnapshotForLoadedTab(
|
|
tabKey,
|
|
response.data.statusInfo,
|
|
convertedData.length > 0,
|
|
values,
|
|
);
|
|
return true;
|
|
}
|
|
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
|
|
return false;
|
|
}
|
|
if (tabKey === 'ST00020004') {
|
|
const response = await statisticsInputSewPipeApiService.getPipeDiameterStatus(searchParam);
|
|
if (response.status === 'success') {
|
|
const commonInfo = response.data.commonInfo;
|
|
const convertedData = (response.data.pipeDiameterStatusList ?? []).map((d) => ({
|
|
ctpvNm: commonInfo.ctpvNm,
|
|
...d,
|
|
}));
|
|
applyRowMap((m) => m.set('ST00020004', toGridRows(convertedData)));
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
|
if (updateLastSearchedValues) setLastSearchedValues(values);
|
|
commitToolbarSnapshotForLoadedTab(
|
|
tabKey,
|
|
response.data.statusInfo,
|
|
convertedData.length > 0,
|
|
values,
|
|
);
|
|
return true;
|
|
}
|
|
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
|
|
return false;
|
|
}
|
|
if (tabKey === 'ST00020005') {
|
|
const response = await statisticsInputSewPipeApiService.getManholeStatus(searchParam);
|
|
if (response.status === 'success') {
|
|
const commonInfo = response.data.commonInfo;
|
|
const convertedData = (response.data.manholeStatusList ?? []).map((d) => ({
|
|
ctpvNm: commonInfo.ctpvNm,
|
|
...d,
|
|
}));
|
|
applyRowMap((m) => m.set('ST00020005', toGridRows(convertedData)));
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
|
if (updateLastSearchedValues) setLastSearchedValues(values);
|
|
commitToolbarSnapshotForLoadedTab(
|
|
tabKey,
|
|
response.data.statusInfo,
|
|
convertedData.length > 0,
|
|
values,
|
|
);
|
|
return true;
|
|
}
|
|
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
|
|
return false;
|
|
}
|
|
if (tabKey === 'ST00020006') {
|
|
const response = await statisticsInputSewPipeApiService.getConnectionStatus(searchParam);
|
|
if (response.status === 'success') {
|
|
const commonInfo = response.data.commonInfo;
|
|
const facilityConvertedData = (response.data.facilityConnectionList ?? []).map((d) => ({
|
|
...d,
|
|
}));
|
|
const adminConvertedData = (response.data.adminAreaConnectionList ?? []).map((d) => ({
|
|
...d,
|
|
ctpvNm: d.ctpvNm || commonInfo.ctpvNm,
|
|
}));
|
|
applyRowMap((m) => {
|
|
m.set('treatmentPlantConnectionInp', toGridRows(facilityConvertedData));
|
|
m.set('adminAreaConnectionInp', toGridRows(adminConvertedData));
|
|
});
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
|
if (updateLastSearchedValues) setLastSearchedValues(values);
|
|
commitToolbarSnapshotForLoadedTab(
|
|
tabKey,
|
|
response.data.statusInfo,
|
|
facilityConvertedData.length > 0 || adminConvertedData.length > 0,
|
|
values,
|
|
);
|
|
return true;
|
|
}
|
|
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
|
|
return false;
|
|
}
|
|
if (!mergeIntoExistingMap) {
|
|
setRowDataMap(new Map());
|
|
}
|
|
if (updateLastSearchedValues) setLastSearchedValues(values);
|
|
return true;
|
|
};
|
|
|
|
const handleSearch = async (values: SearchFormValues) => {
|
|
if (!values.sido || values.sido === 'ALL') {
|
|
alert('시도를 선택해주세요.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const periodResponse = await statisticsInputSewSupplyRateApi.checkInputPeriod(values.year || '');
|
|
|
|
if (periodResponse.status !== 'success') {
|
|
alert(periodResponse.message);
|
|
return;
|
|
}
|
|
|
|
const periodOk = isInputPeriodCurrent(periodResponse.data);
|
|
periodEnterableRef.current = periodOk;
|
|
setInputPeriodEnterable(periodOk);
|
|
|
|
/** 연도가 바뀐 검색만 전 탭 캐시 초기화 + 첫 탭으로 이동. 연도 외 조건만 바뀌면 다른 탭은 이전 조회 데이터 유지 */
|
|
const yearChangedFromLastSearch =
|
|
lastSearchedValues != null &&
|
|
(lastSearchedValues.year ?? "") !== (values.year ?? "");
|
|
if (yearChangedFromLastSearch) {
|
|
setRowDataMap(new Map());
|
|
setTabStatusInfoMap(new Map());
|
|
setTabStatsCdMap(new Map());
|
|
setStatsCd("");
|
|
setToolbarSnapshotByTab(new Map());
|
|
}
|
|
|
|
lazyTabLoggedRef.current.clear();
|
|
|
|
setIsLoading(true);
|
|
|
|
const searchParam = buildSewagePipeSearchParam(values);
|
|
searchParamRef.current = searchParam;
|
|
searchDoneRef.current = true;
|
|
setHasSearched(true);
|
|
setLastSearchedYear(values.year ?? null);
|
|
|
|
const tabKeyToLoad = yearChangedFromLastSearch ? firstTabKey : activeTab;
|
|
|
|
if (yearChangedFromLastSearch) {
|
|
activeTabRef.current = firstTabKey;
|
|
setActiveTab(firstTabKey);
|
|
} else {
|
|
activeTabRef.current = activeTab;
|
|
}
|
|
await loadSewagePipeActiveTabData(tabKeyToLoad, searchParam, values, true, true);
|
|
} catch {
|
|
alert('데이터 조회 중 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleActiveTabChange = async (tabKey: string) => {
|
|
activeTabRef.current = tabKey;
|
|
setActiveTab(tabKey);
|
|
setStatsCd(tabStatsCdMap.get(tabKey) ?? "");
|
|
|
|
const hasActiveTabData = tabRowDataExists(rowDataMap, tabKey);
|
|
|
|
const canLazyLoad =
|
|
searchParamRef.current &&
|
|
!hasActiveTabData &&
|
|
!lazyTabLoggedRef.current.has(tabKey) &&
|
|
searchDoneRef.current;
|
|
|
|
if (!canLazyLoad) return;
|
|
|
|
const searchParam = searchParamRef.current;
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!searchParam || !values) return;
|
|
|
|
try {
|
|
setIsLoading(true);
|
|
const ok = await loadSewagePipeActiveTabData(tabKey, searchParam, values, true, false);
|
|
if (ok) lazyTabLoggedRef.current.add(tabKey);
|
|
} catch {
|
|
alert('데이터 조회 중 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const getLastYearDataFunc = async () => {
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!values?.sido || values.sido === 'ALL') {
|
|
alert('시도를 선택해주세요.');
|
|
return;
|
|
}
|
|
if (!lastSearchedYear) {
|
|
alert('먼저 데이터를 조회해주세요.');
|
|
return;
|
|
}
|
|
try {
|
|
setIsLoading(true);
|
|
const param = buildSewagePipeSearchParam(values, { mode: 'PRE' });
|
|
await loadSewagePipeActiveTabData(activeTab, param, values, true, false);
|
|
} catch {
|
|
alert('데이터 조회 중 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const submitFunc = () => {
|
|
console.log('submit');
|
|
// 제출 로직
|
|
};
|
|
|
|
const refreshConnectionStatusAfterPopupSave = async () => {
|
|
const searchParam = searchParamRef.current;
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!searchParam || !values) return;
|
|
await loadSewagePipeActiveTabData("ST00020006", searchParam, values, true, false);
|
|
};
|
|
|
|
const openVerificationHistoryDialog = () => {
|
|
const activeTabStatsCd = tabStatsCdMap.get(activeTab) ?? statsCd;
|
|
if (!searchValues?.year || !searchValues?.sido || !searchValues?.bsnmCd || !activeTabStatsCd) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
setVerificationHistoryOpen(true);
|
|
};
|
|
|
|
const saveFunc = async () => {
|
|
if (!(await confirm("저장 하시겠습니까?"))) return;
|
|
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!values?.year) {
|
|
alert("검색 후 저장해 주세요.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
showGlobalProgress({ label: "저장 진행 중...", progress: 0 });
|
|
|
|
switch (activeTab) {
|
|
case "ST00020001": {
|
|
const currentData = getGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("저장할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const columnFormatMap = new Map<string, string>();
|
|
RegionalFactStatusInpColumn.columns.forEach((col) => {
|
|
if (col.fieldName && col.dataFormat) {
|
|
columnFormatMap.set(col.fieldName, col.dataFormat);
|
|
}
|
|
});
|
|
|
|
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
|
|
if (value === null || value === undefined || value === "") return 0;
|
|
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
|
|
if (isNaN(num)) return 0;
|
|
const format = columnFormatMap.get(fieldName);
|
|
if (!format) return num;
|
|
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
|
|
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
|
|
};
|
|
|
|
const userInfo = await getUserInfo();
|
|
|
|
const saveList = currentData.map((d) => ({
|
|
admdstCd: String(d.admdstCd ?? ""),
|
|
sggNm: String(d.sggNm ?? ""),
|
|
admdstNm: String(d.admdstNm ?? ""),
|
|
goalYr: String(d.goalYr ?? ""),
|
|
wholPplMtncPlanPrlg: formatNumber(d.wholPplMtncPlanPrlg, "wholPplMtncPlanPrlg"),
|
|
wholPlanPrlg: formatNumber(d.wholPlanPrlg, "wholPlanPrlg"),
|
|
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
|
|
swagePplPsplyRt: formatNumber(d.swagePplPsplyRt, "swagePplPsplyRt"),
|
|
cbsysPplMtncPlanArea: formatNumber(d.cbsysPplMtncPlanArea, "cbsysPplMtncPlanArea"),
|
|
cbsysPplMtncPlanPrlg: formatNumber(d.cbsysPplMtncPlanPrlg, "cbsysPplMtncPlanPrlg"),
|
|
cbsysPlanPrlg: formatNumber(d.cbsysPlanPrlg, "cbsysPlanPrlg"),
|
|
cbsysFcltPrlg: formatNumber(d.cbsysFcltPrlg, "cbsysFcltPrlg"),
|
|
cbsysSqrePrlg: formatNumber(d.cbsysSqrePrlg, "cbsysSqrePrlg"),
|
|
cbsysCiclPrlg: formatNumber(d.cbsysCiclPrlg, "cbsysCiclPrlg"),
|
|
cbsysOpnPrlg: formatNumber(d.cbsysOpnPrlg, "cbsysOpnPrlg"),
|
|
cbsysGttrPrlg: formatNumber(d.cbsysGttrPrlg, "cbsysGttrPrlg"),
|
|
drtwPplMtncPlanArea: formatNumber(d.drtwPplMtncPlanArea, "drtwPplMtncPlanArea"),
|
|
drtwPplMtncPlanPrlg: formatNumber(d.drtwPplMtncPlanPrlg, "drtwPplMtncPlanPrlg"),
|
|
drtwPplPlanPrlg: formatNumber(d.drtwPplPlanPrlg, "drtwPplPlanPrlg"),
|
|
drtwPplFcltPrlg: formatNumber(d.drtwPplFcltPrlg, "drtwPplFcltPrlg"),
|
|
drtwPplSqrePrlg: formatNumber(d.drtwPplSqrePrlg, "drtwPplSqrePrlg"),
|
|
drtwPplCiclPrlg: formatNumber(d.drtwPplCiclPrlg, "drtwPplCiclPrlg"),
|
|
exlnPplMtncPlanArea: formatNumber(d.exlnPplMtncPlanArea, "exlnPplMtncPlanArea"),
|
|
exlnPplMtncPlanPrlg: formatNumber(d.exlnPplMtncPlanPrlg, "exlnPplMtncPlanPrlg"),
|
|
exlnPplPlanPrlg: formatNumber(d.exlnPplPlanPrlg, "exlnPplPlanPrlg"),
|
|
exlnPplFcltPrlg: formatNumber(d.exlnPplFcltPrlg, "exlnPplFcltPrlg"),
|
|
exlnPplSqrePrlg: formatNumber(d.exlnPplSqrePrlg, "exlnPplSqrePrlg"),
|
|
exlnPplCiclPrlg: formatNumber(d.exlnPplCiclPrlg, "exlnPplCiclPrlg"),
|
|
exlnPplOpnPrlg: formatNumber(d.exlnPplOpnPrlg, "exlnPplOpnPrlg"),
|
|
exlnPplGttrPrlg: formatNumber(d.exlnPplGttrPrlg, "exlnPplGttrPrlg"),
|
|
cbsysMnhlCnt: formatNumber(d.cbsysMnhlCnt, "cbsysMnhlCnt"),
|
|
sprsysDrtwMnhlCnt: formatNumber(d.sprsysDrtwMnhlCnt, "sprsysDrtwMnhlCnt"),
|
|
sprsysExlnMnhlCnt: formatNumber(d.sprsysExlnMnhlCnt, "sprsysExlnMnhlCnt"),
|
|
drtwRcvprpCnt: formatNumber(d.drtwRcvprpCnt, "drtwRcvprpCnt"),
|
|
exlnRcvprpCnt: formatNumber(d.exlnRcvprpCnt, "exlnRcvprpCnt"),
|
|
stofcbrCnt: formatNumber(d.stofcbrCnt, "stofcbrCnt"),
|
|
exlnOtflCnt: formatNumber(d.exlnOtflCnt, "exlnOtflCnt"),
|
|
sewerLdgrEdpPrlg: formatNumber(d.sewerLdgrEdpPrlg, "sewerLdgrEdpPrlg"),
|
|
sewerLdgrEdpRt: formatNumber(d.sewerLdgrEdpRt, "sewerLdgrEdpRt"),
|
|
rmrk: (d.rmrk as string | null) ?? null,
|
|
}));
|
|
|
|
const param: SeweragePipeInputRegionalFactStatusSaveRequest = {
|
|
statsYr: values.year ?? "",
|
|
ctpvNm: values.sido ?? "",
|
|
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
|
|
sewerBzmnCd: values.bsnmCd ?? "",
|
|
userId: userInfo.userUnqId,
|
|
superAdmin: isAdminInfo ? "superAdmin" : "",
|
|
saveList,
|
|
};
|
|
|
|
const response =
|
|
await statisticsInputSewPipeApiService.saveRegionalFactStatus(param);
|
|
if (response.status === "success") {
|
|
setGlobalProgress(50);
|
|
const refreshedParam = buildSewagePipeSearchParam(values);
|
|
await loadSewagePipeActiveTabData(activeTab, refreshedParam, values, true, false);
|
|
setGlobalProgress(100);
|
|
alert("저장되었습니다.");
|
|
} else {
|
|
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
|
|
}
|
|
break;
|
|
}
|
|
case "ST00020002": {
|
|
const currentData = getFactInstallGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("저장할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const columnFormatMap = new Map<string, string>();
|
|
FactInstallStatusInpColumn.columns.forEach((col) => {
|
|
if (col.fieldName && col.dataFormat) {
|
|
columnFormatMap.set(col.fieldName, col.dataFormat);
|
|
}
|
|
});
|
|
|
|
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
|
|
if (value === null || value === undefined || value === "") return 0;
|
|
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
|
|
if (isNaN(num)) return 0;
|
|
const format = columnFormatMap.get(fieldName);
|
|
if (!format) return num;
|
|
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
|
|
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
|
|
};
|
|
|
|
const userInfo = await getUserInfo();
|
|
|
|
const saveList = currentData.map((d) => ({
|
|
admdstCd: String(d.admdstCd ?? ""),
|
|
sggNm: String(d.sggNm ?? ""),
|
|
admdstNm: String(d.admdstNm ?? ""),
|
|
fcltSn: String(d.fcltSn ?? ""),
|
|
swtrmtFcltCd: String(d.swtrmtFcltCd ?? ""),
|
|
swtrmtFcltNm: String(d.swtrmtFcltNm ?? ""),
|
|
prcsFcltCpct: formatNumber(d.prcsFcltCpct, "prcsFcltCpct"),
|
|
prcsZoneArea: formatNumber(d.prcsZoneArea, "prcsZoneArea"),
|
|
cbsysPrcsZoneArea: formatNumber(d.cbsysPrcsZoneArea, "cbsysPrcsZoneArea"),
|
|
sprsysPrcsZoneArea: formatNumber(d.sprsysPrcsZoneArea, "sprsysPrcsZoneArea"),
|
|
sprsysVartnRt: formatNumber(d.sprsysVartnRt, "sprsysVartnRt"),
|
|
mltvEqpmCnt: formatNumber(d.mltvEqpmCnt, "mltvEqpmCnt"),
|
|
cbsysMltvEqpmCnt: formatNumber(d.cbsysMltvEqpmCnt, "cbsysMltvEqpmCnt"),
|
|
sprsysMltvEqpmCnt: formatNumber(d.sprsysMltvEqpmCnt, "sprsysMltvEqpmCnt"),
|
|
sprsysMltvLnkgCnt: formatNumber(d.sprsysMltvLnkgCnt, "sprsysMltvLnkgCnt"),
|
|
sprsysMltvYetLnkgCnt: formatNumber(d.sprsysMltvYetLnkgCnt, "sprsysMltvYetLnkgCnt"),
|
|
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
|
|
wholIcptgAtlfcsPrlg: formatNumber(d.wholIcptgAtlfcsPrlg, "wholIcptgAtlfcsPrlg"),
|
|
icptgPrlg: formatNumber(d.icptgPrlg, "icptgPrlg"),
|
|
atlfcsPrlg: formatNumber(d.atlfcsPrlg, "atlfcsPrlg"),
|
|
cbsysStofcbrCnt: formatNumber(d.cbsysStofcbrCnt, "cbsysStofcbrCnt"),
|
|
sprsysStofcbrCnt: formatNumber(d.sprsysStofcbrCnt, "sprsysStofcbrCnt"),
|
|
sprsysExlnOtflCnt: formatNumber(d.sprsysExlnOtflCnt, "sprsysExlnOtflCnt"),
|
|
wholBrchlnPrlg: formatNumber(d.wholBrchlnPrlg, "wholBrchlnPrlg"),
|
|
cbsysBrchlnPrlg: formatNumber(d.cbsysBrchlnPrlg, "cbsysBrchlnPrlg"),
|
|
exlnBrchlnPrlg: formatNumber(d.exlnBrchlnPrlg, "exlnBrchlnPrlg"),
|
|
drtwBrchlnPrlg: formatNumber(d.drtwBrchlnPrlg, "drtwBrchlnPrlg"),
|
|
rmrk: String(d.rmrk ?? ""),
|
|
}));
|
|
|
|
const param: SeweragePipeInputFactInstallStatusSaveRequest = {
|
|
statsYr: values.year ?? "",
|
|
ctpvNm: values.sido ?? "",
|
|
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
|
|
sewerBzmnCd: values.bsnmCd ?? "",
|
|
userId: userInfo.userUnqId,
|
|
superAdmin: isAdminInfo ? "superAdmin" : "",
|
|
saveList,
|
|
};
|
|
|
|
const response =
|
|
await statisticsInputSewPipeApiService.saveFactInstallStatus(param);
|
|
if (response.status === "success") {
|
|
setGlobalProgress(50);
|
|
const refreshedParam = buildSewagePipeSearchParam(values);
|
|
await loadSewagePipeActiveTabData(activeTab, refreshedParam, values, true, false);
|
|
setGlobalProgress(100);
|
|
alert("저장되었습니다.");
|
|
} else {
|
|
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
alert("현재 탭 저장 API 연동 전입니다.");
|
|
break;
|
|
}
|
|
} catch {
|
|
alert("데이터 저장 중 오류가 발생했습니다.");
|
|
} finally {
|
|
hideGlobalProgress();
|
|
}
|
|
};
|
|
|
|
const gugunOkOrNotDoList = buildGugunOkForToolbar(lastSearchedValues);
|
|
|
|
const searchContextForToolbar = lastSearchedValues ?? searchValues;
|
|
const activeTabToolbar =
|
|
toolbarSnapshotByTab.get(activeTab) ??
|
|
computeTabToolbarSnapshot(
|
|
tabStatusInfoMap.get(activeTab) ?? null,
|
|
tabRowDataExists(rowDataMap, activeTab),
|
|
hasSearched,
|
|
searchContextForToolbar,
|
|
inputPeriodEnterable,
|
|
isAdminInfo,
|
|
);
|
|
|
|
const {
|
|
showSaveInToolbar,
|
|
showEnterableToolbarButtons,
|
|
showActionToolbarCard,
|
|
} = activeTabToolbar;
|
|
|
|
return (
|
|
<>
|
|
<SearchForm onSubmit={handleSearch} onReset={handleReset} onValuesChange={setSearchValues} />
|
|
{showActionToolbarCard && (
|
|
<div className="card !py-0">
|
|
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
|
|
<VerificationResultButton
|
|
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
|
/>
|
|
{showEnterableToolbarButtons && (
|
|
<>
|
|
<LastYearDataButton getLastYearDataFunc={() => void getLastYearDataFunc()} />
|
|
<SubmitButton submitFunc={submitFunc} />
|
|
</>
|
|
)}
|
|
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{isLoading && !globalProgressVisible && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
|
<div className="relative size-[5rem]">
|
|
<LoadingWithAnimation color="primary" />
|
|
</div>
|
|
</div>}
|
|
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
|
<Tabs
|
|
value={activeTab}
|
|
onValueChange={(v) => void handleActiveTabChange(v)}
|
|
className="w-full"
|
|
variant="default"
|
|
>
|
|
<TabsList>
|
|
{columnInfo.map((info) => (
|
|
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
|
{info.tabLabel || info.tabKey}
|
|
</TabsTrigger>
|
|
))}
|
|
</TabsList>
|
|
{columnInfo.map((info) => {
|
|
const tabKey = info.tabKey;
|
|
const tabEnterableForGrid = computeTabEnterable(
|
|
tabStatusInfoMap.get(tabKey) ?? null,
|
|
inputPeriodEnterable,
|
|
);
|
|
const excelToolbarEnabledForTab =
|
|
tabRowDataExists(rowDataMap, tabKey) &&
|
|
tabEnterableForGrid &&
|
|
gugunOkOrNotDoList;
|
|
|
|
if (info.isSplitGrid && info.left && info.right) {
|
|
const leftKey = info.left.tabKey || `${tabKey}_left`;
|
|
const rightKey = info.right.tabKey || `${tabKey}_right`;
|
|
const leftRowData = rowDataMap.get(leftKey) ?? [];
|
|
const rightRowData = rowDataMap.get(rightKey) ?? [];
|
|
const splitTopButton = info.right.topButton;
|
|
const showSplitTopButton =
|
|
Boolean(splitTopButton?.text) &&
|
|
(splitTopButton?.visible ?? true) &&
|
|
excelToolbarEnabledForTab;
|
|
const leftColumnInfo: ColumnInfo = {
|
|
columns: info.left.columns ?? [],
|
|
columnLayout: info.left.columnLayout ?? [],
|
|
};
|
|
const rightColumnInfo: ColumnInfo = {
|
|
columns: info.right.columns ?? [],
|
|
columnLayout: info.right.columnLayout ?? [],
|
|
};
|
|
|
|
return (
|
|
<TabsContent key={tabKey} value={tabKey}>
|
|
<div className="flex flex-col gap-[0.6rem]">
|
|
<div className="flex justify-end gap-[0.6rem]">
|
|
{showSplitTopButton && (
|
|
<GridTopActionButton
|
|
text={splitTopButton?.text ?? ""}
|
|
onClick={splitTopButton?.onClick}
|
|
statsYr={lastSearchedYear ?? undefined}
|
|
initialSearchValues={
|
|
lastSearchedValues
|
|
? {
|
|
sido: lastSearchedValues.sido,
|
|
gugun: lastSearchedValues.gugun,
|
|
bsnmCd: lastSearchedValues.bsnmCd,
|
|
}
|
|
: null
|
|
}
|
|
onSaveSuccess={refreshConnectionStatusAfterPopupSave}
|
|
/>
|
|
)}
|
|
</div>
|
|
<SplitDataGrid
|
|
leftColumnInfo={leftColumnInfo}
|
|
rightColumnInfo={rightColumnInfo}
|
|
leftFixedColumns={info.left.fixedColumns}
|
|
rightFixedColumns={info.right.fixedColumns}
|
|
showHeaderCheckbox={false}
|
|
enableSort={false}
|
|
leftRowData={leftRowData}
|
|
rightRowData={rightRowData}
|
|
setLeftRowData={(updater) => {
|
|
setRowDataMap((prev) => {
|
|
const next = new Map(prev);
|
|
const cur = next.get(leftKey) ?? [];
|
|
const n =
|
|
typeof updater === "function"
|
|
? updater(cur)
|
|
: updater;
|
|
next.set(leftKey, n);
|
|
return next;
|
|
});
|
|
}}
|
|
setRightRowData={(updater) => {
|
|
setRowDataMap((prev) => {
|
|
const next = new Map(prev);
|
|
const cur = next.get(rightKey) ?? [];
|
|
const n =
|
|
typeof updater === "function"
|
|
? updater(cur)
|
|
: updater;
|
|
next.set(rightKey, n);
|
|
return next;
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
</TabsContent>
|
|
);
|
|
}
|
|
|
|
if (!info.columns || !info.columnLayout) {
|
|
return null;
|
|
}
|
|
|
|
const rowData = rowDataMap.get(tabKey) ?? [];
|
|
const gridColumnInfo: ColumnInfo = {
|
|
columns: info.columns,
|
|
columnLayout: info.columnLayout,
|
|
};
|
|
|
|
return (
|
|
<TabsContent key={tabKey} value={tabKey}>
|
|
<DataGrid
|
|
key={(tabEnterableForGrid || isAdminInfo) ? "enterable" : "readonly"}
|
|
columnInfo={gridColumnInfo}
|
|
rowStartNum={info.rowStartNum}
|
|
excelExportFileSuffix={info.tabLabel || tabKey}
|
|
rowData={rowData}
|
|
setRowData={(updater) => {
|
|
setRowDataMap((prev) => {
|
|
const next = new Map(prev);
|
|
const cur = next.get(tabKey) ?? [];
|
|
const n =
|
|
typeof updater === "function" ? updater(cur) : updater;
|
|
next.set(tabKey, n);
|
|
return next;
|
|
});
|
|
}}
|
|
isExcelImport={excelToolbarEnabledForTab}
|
|
isExcelExport
|
|
showHeaderCheckbox={false}
|
|
fixedColumns={info.fixedColumns}
|
|
enableSort={false}
|
|
statusInfo={tabStatusInfoMap.get(tabKey) ?? null}
|
|
getGridDataRef={
|
|
tabKey === "ST00020001"
|
|
? getGridDataRef
|
|
: tabKey === "ST00020002"
|
|
? getFactInstallGridDataRef
|
|
: undefined
|
|
}
|
|
cellStyleCallback={({ row, fieldName }) => {
|
|
const verification = (row as GridRowWithVerification).verificationResult;
|
|
if (!verification) return;
|
|
const errors = verification[fieldName];
|
|
if (Array.isArray(errors) && errors.length > 0) {
|
|
const hasImr = errors.some((e) => e.vrfcCd === "IMR");
|
|
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
|
|
}
|
|
}}
|
|
cellTooltipCallback={({ row, fieldName }) => {
|
|
const verification = (row as GridRowWithVerification).verificationResult;
|
|
if (!verification) return;
|
|
const errors = verification[fieldName];
|
|
if (Array.isArray(errors) && errors.length > 0) {
|
|
return errors
|
|
.map((e) => {
|
|
const prefix =
|
|
e.vrfcCd === "IMR" ? "IMR검증 - " : "일반검증 - ";
|
|
const comp = e.compVlCn ?? "";
|
|
const now = e.nowVlCn ?? "";
|
|
return `${prefix}${e.rsnExpln}(검증 기준값: ${comp} / 현재 입력값: ${now})`;
|
|
})
|
|
.filter(Boolean)
|
|
.join("\n");
|
|
}
|
|
}}
|
|
/>
|
|
</TabsContent>
|
|
);
|
|
})}
|
|
</Tabs>
|
|
</div>
|
|
<VerificationResultHistoryDialog
|
|
open={verificationHistoryOpen}
|
|
onOpenChange={setVerificationHistoryOpen}
|
|
queryContext={
|
|
searchValues?.year &&
|
|
searchValues?.sido &&
|
|
searchValues?.bsnmCd &&
|
|
(tabStatsCdMap.get(activeTab) ?? statsCd)
|
|
? {
|
|
sewerBzmnCd: searchValues.bsnmCd,
|
|
ctpvNm: searchValues.sido,
|
|
sggNm: searchValues.gugun === "ALL" ? "" : (searchValues.gugun ?? ""),
|
|
statsYr: searchValues.year,
|
|
statsCd: tabStatsCdMap.get(activeTab) ?? statsCd,
|
|
}
|
|
: null
|
|
}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SewagePipeInp; |