2379 lines
119 KiB
TypeScript
2379 lines
119 KiB
TypeScript
import { useEffect, useLayoutEffect,useMemo, useRef, useState } from "react";
|
|
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
|
|
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
|
import {
|
|
STATISTICS_INPUT_URL_PATH,
|
|
verificationSearchToFormValues,
|
|
} from "@/shared/lib/stats-cd-input-navigation";
|
|
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
|
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service";
|
|
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
|
import type {
|
|
SeweragePipeInputFactInstallStatusSaveRequest,
|
|
SeweragePipeInputFactInstallStatusSubmitRequest,
|
|
SeweragePipeInputManholeStatusItem,
|
|
SeweragePipeInputManholeStatusSaveRequest,
|
|
SeweragePipeInputManholeStatusSaveYearlyStatItem,
|
|
SeweragePipeInputManholeStatusSubmitRequest,
|
|
SeweragePipeInputPipeDiameterStatusSaveRequest,
|
|
SeweragePipeInputPipeDiameterStatusSubmitRequest,
|
|
SeweragePipeInputPipeTypeStatusItem,
|
|
SeweragePipeInputPipeTypeStatusSaveRequest,
|
|
SeweragePipeInputPipeTypeStatusSubmitRequest,
|
|
SeweragePipeInputRegionalFactStatusSaveItem,
|
|
SeweragePipeInputRegionalFactStatusSaveRequest,
|
|
SeweragePipeInputRegionalFactStatusSubmitRequest,
|
|
SeweragePipeInputSubmitResponse,
|
|
SeweragePipeInputVerificationResult,
|
|
SeweragePipeRequestParams,
|
|
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface";
|
|
import {
|
|
type CheckInputPeriodItem,
|
|
getVerificationPeriodActionBlockedMessage,
|
|
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 {
|
|
cancelSubmitRequest,
|
|
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";
|
|
|
|
import SubmitDailog from "./SubmitDailog";
|
|
|
|
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
|
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
|
import SaveButton from "@/components/button/SaveButton";
|
|
import SubmitButton from "@/components/button/SumitButton";
|
|
import VerificationResultButton from "@/components/button/VerificationResultButton";
|
|
import DataGrid from "@/components/module/DataGrid";
|
|
import GridTopActionButton from "@/components/module/GridTopActionButton";
|
|
import { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
|
import SearchForm from "@/components/module/SearchForm";
|
|
import SplitDataGrid from "@/components/module/SplitDataGrid";
|
|
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
|
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 { 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";
|
|
import { PipeTypeStatusInpColumn } from "@/components/util/column/input/sewagePipe/PipeTypeStatusInpColumn";
|
|
import { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn";
|
|
import { SewagePipeInpColumnInfoList } from "@/components/util/column/input/sewagePipe/SewagePipeInpColumn";
|
|
import { fetchColumnGuidesRecord } from "@/components/util/grid/columnGuides";
|
|
import { toGridRows } from "@/components/util/grid/gridFunction";
|
|
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
|
import {
|
|
patchFooterVerificationByTab,
|
|
patchVerificationFailedByTab,
|
|
} from "@/components/util/input/verification-failed-flag";
|
|
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
|
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
|
import { useAdminStore } from "@/store/admin-store";
|
|
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
|
import { useTabStore } from "@/store/tab-store";
|
|
|
|
/** 탭별 status + 입력기간으로 편집 가능 여부 */
|
|
const computeTabEnterable = (
|
|
statusInfo: SewerageStatusInfo | 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,
|
|
ctpvNm: values.sido,
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
|
};
|
|
}
|
|
|
|
const getPipeTypeLastDataYear = () => {
|
|
const lastDataYear = new Date().getFullYear() - 1;
|
|
return Math.max(1995, lastDataYear);
|
|
};
|
|
|
|
const PIPE_TYPE_STATUS_TAB_KEY = "ST00020003";
|
|
|
|
/** 조회 연도(statsYr)에 해당하는 연도 (y{year}* 필드용) */
|
|
const resolvePipeTypeEditableYear = (statsYr: string | null | undefined): number | null => {
|
|
if (!statsYr) return null;
|
|
const year = Number(statsYr);
|
|
if (!Number.isFinite(year)) return null;
|
|
return Math.min(getPipeTypeLastDataYear(), Math.max(1995, year));
|
|
};
|
|
|
|
/** 조회 연도(statsYr)에 해당하는 연도별 매설연장 필드명 (y{year}LyngPrlg) */
|
|
const resolvePipeTypeEditableLyngField = (statsYr: string | null | undefined): string | null => {
|
|
const year = resolvePipeTypeEditableYear(statsYr);
|
|
return year === null ? null : `y${year}LyngPrlg`;
|
|
};
|
|
|
|
const pipeTypeYearDmltnField = (year: number) => `y${year}DmltnPrlg`;
|
|
|
|
const shouldHidePipeTypeEditableYearDmltn = (
|
|
canEdit: boolean,
|
|
periodEnterable: boolean,
|
|
statsYr: string | null,
|
|
): number | null => {
|
|
if (!canEdit || !periodEnterable) return null;
|
|
return resolvePipeTypeEditableYear(statsYr);
|
|
};
|
|
|
|
const applyPipeTypeStatusColumnLayout = (
|
|
layout: ColumnLayoutItem[] | undefined,
|
|
hideDmltnYear: number | null,
|
|
): ColumnLayoutItem[] => {
|
|
if (hideDmltnYear === null) return layout ?? [];
|
|
const hideField = pipeTypeYearDmltnField(hideDmltnYear);
|
|
|
|
const walk = (items: ColumnLayoutItem[]): ColumnLayoutItem[] => {
|
|
const result: ColumnLayoutItem[] = [];
|
|
for (const item of items) {
|
|
if (typeof item === "string") {
|
|
if (item !== hideField) result.push(item);
|
|
continue;
|
|
}
|
|
if ("column" in item) {
|
|
if (item.column !== hideField) result.push(item);
|
|
continue;
|
|
}
|
|
const nextItems = walk(item.items as ColumnLayoutItem[]);
|
|
if (nextItems.length > 0) {
|
|
result.push({ ...item, items: nextItems });
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
return walk(layout ?? []);
|
|
};
|
|
|
|
/** ST00020003: 입력기간 통과 시 조회 연도의 매설연장(y{year}LyngPrlg)만 편집 가능, 해당 연도 철거연장 컬럼 숨김 */
|
|
const applyPipeTypeStatusEditableColumns = (
|
|
cols: ColumnConfig[] | undefined,
|
|
canEdit: boolean,
|
|
periodEnterable: boolean,
|
|
statsYr: string | null,
|
|
): ColumnConfig[] => {
|
|
const hideDmltnYear = shouldHidePipeTypeEditableYearDmltn(canEdit, periodEnterable, statsYr);
|
|
|
|
return (cols ?? []).map((col) => {
|
|
if (!canEdit) {
|
|
return { ...col, editable: false as const, styleName: col.styleName ?? "rg-cell-readonly" };
|
|
}
|
|
|
|
const fieldName = col.fieldName ?? col.name;
|
|
|
|
if (hideDmltnYear !== null && fieldName === pipeTypeYearDmltnField(hideDmltnYear)) {
|
|
return {
|
|
...col,
|
|
visible: false,
|
|
editable: false as const,
|
|
excludeFromExcelExport: true,
|
|
};
|
|
}
|
|
|
|
const isYearLyngField =
|
|
fieldName !== "lyngPrlg" && typeof fieldName === "string" && fieldName.endsWith("LyngPrlg");
|
|
if (fieldName === "lyngPrlg" || isYearLyngField) {
|
|
const editableLyngField = periodEnterable ? resolvePipeTypeEditableLyngField(statsYr) : null;
|
|
const isTargetYear = editableLyngField !== null && fieldName === editableLyngField;
|
|
if (isTargetYear) {
|
|
const { styleName: _readonlyStyle, ...rest } = col;
|
|
return {
|
|
...rest,
|
|
editable: true as const,
|
|
};
|
|
}
|
|
return { ...col, editable: false as const, styleName: col.styleName ?? "rg-cell-readonly" };
|
|
}
|
|
|
|
return col;
|
|
});
|
|
};
|
|
|
|
const getManholeLastDataYear = () => {
|
|
const lastDataYear = new Date().getFullYear() - 1;
|
|
return Math.max(2014, lastDataYear);
|
|
};
|
|
|
|
/** 2-3 관종별: verificationResult.prlgWhol → 총계 계(pipeByPrstWhol) */
|
|
function remapPipeTypePrlgWholVerification(
|
|
result?: SeweragePipeInputVerificationResult | null,
|
|
): SeweragePipeInputVerificationResult | null | undefined {
|
|
if (!result) return result;
|
|
const fromAlias = result.prlgWhol;
|
|
if (!Array.isArray(fromAlias) || fromAlias.length === 0) return result;
|
|
const existing = result.pipeByPrstWhol;
|
|
if (Array.isArray(existing) && existing.length > 0) return result;
|
|
return { ...result, pipeByPrstWhol: fromAlias };
|
|
}
|
|
|
|
/** 관종별 신 응답(yearlyStatList) -> 그리드 y{year}* 필드로 정규화 */
|
|
function normalizePipeTypeStatusRow(
|
|
row: SeweragePipeInputPipeTypeStatusItem,
|
|
ctpvNm: string,
|
|
): Record<string, string | number | SeweragePipeInputVerificationResult | undefined> {
|
|
const lastDataYear = getPipeTypeLastDataYear();
|
|
const {
|
|
yearlyStatList,
|
|
lyngPrlgWhol,
|
|
dmltnPrlgWhol,
|
|
verificationResult,
|
|
...rest
|
|
} = row;
|
|
const yearFields: Record<string, number> = {};
|
|
for (let year = 1995; year <= lastDataYear; year++) {
|
|
yearFields[`y${year}PrlgWhol`] = 0;
|
|
yearFields[`y${year}LyngPrlg`] = 0;
|
|
yearFields[`y${year}DmltnPrlg`] = 0;
|
|
}
|
|
|
|
for (const y of yearlyStatList ?? []) {
|
|
const year = Number(y.lyngYr);
|
|
if (!Number.isFinite(year)) continue;
|
|
const targetYear = Math.min(lastDataYear, Math.max(1995, year));
|
|
yearFields[`y${targetYear}PrlgWhol`] =
|
|
(yearFields[`y${targetYear}PrlgWhol`] ?? 0) + Number(y.prlgWhol ?? 0);
|
|
yearFields[`y${targetYear}LyngPrlg`] =
|
|
(yearFields[`y${targetYear}LyngPrlg`] ?? 0) + Number(y.lyngPrlg ?? 0);
|
|
yearFields[`y${targetYear}DmltnPrlg`] =
|
|
(yearFields[`y${targetYear}DmltnPrlg`] ?? 0) + Number(y.dmltnPrlg ?? 0);
|
|
}
|
|
|
|
return {
|
|
ctpvNm,
|
|
...rest,
|
|
lyngPrlg: Number(row.lyngPrlg ?? lyngPrlgWhol ?? 0),
|
|
dmltnPrlg: Number(row.dmltnPrlg ?? dmltnPrlgWhol ?? 0),
|
|
...yearFields,
|
|
verificationResult: remapPipeTypePrlgWholVerification(verificationResult) ?? undefined,
|
|
};
|
|
}
|
|
|
|
/** 맨홀 신 응답(yearlyStats) -> 그리드 yy{14..25}* 필드로 정규화 */
|
|
function normalizeManholeStatusRow(
|
|
row: SeweragePipeInputManholeStatusItem,
|
|
ctpvNm: string,
|
|
): Record<string, string | number | SeweragePipeInputVerificationResult | undefined> {
|
|
const maxYy = getManholeLastDataYear() % 100;
|
|
const { yearlyStats, ...rest } = row;
|
|
const yearFields: Record<string, number> = {};
|
|
for (let yy = 14; yy <= maxYy; yy++) {
|
|
yearFields[`yy${yy}WholMnhlCnt`] = 0;
|
|
yearFields[`yy${yy}Step1MnhlCnt`] = 0;
|
|
yearFields[`yy${yy}Step2MnhlCnt`] = 0;
|
|
yearFields[`yy${yy}Step3MnhlCnt`] = 0;
|
|
yearFields[`yy${yy}Step4MnhlCnt`] = 0;
|
|
yearFields[`yy${yy}Step5MnhlCnt`] = 0;
|
|
}
|
|
|
|
for (const y of yearlyStats ?? []) {
|
|
const year = Number(y.manhYr);
|
|
if (!Number.isFinite(year)) continue;
|
|
const yy = year % 100;
|
|
if (yy < 14 || yy > maxYy) continue;
|
|
yearFields[`yy${yy}WholMnhlCnt`] = Number(y.wholMnhlCnt ?? 0);
|
|
yearFields[`yy${yy}Step1MnhlCnt`] = Number(y.step1MnhlCnt ?? 0);
|
|
yearFields[`yy${yy}Step2MnhlCnt`] = Number(y.step2MnhlCnt ?? 0);
|
|
yearFields[`yy${yy}Step3MnhlCnt`] = Number(y.step3MnhlCnt ?? 0);
|
|
yearFields[`yy${yy}Step4MnhlCnt`] = Number(y.step4MnhlCnt ?? 0);
|
|
yearFields[`yy${yy}Step5MnhlCnt`] = Number(y.step5MnhlCnt ?? 0);
|
|
}
|
|
|
|
return {
|
|
ctpvNm,
|
|
...rest,
|
|
...yearFields,
|
|
};
|
|
}
|
|
|
|
/** ST00020006(연결)은 행이 좌·우 그리드 키에만 있음 */
|
|
type GridRowWithVerification = Record<string, string | number> & {
|
|
verificationResult?: SeweragePipeInputVerificationResult;
|
|
};
|
|
|
|
const REGIONAL_FACT_TAB_KEY = "ST00020001";
|
|
|
|
// function hasRegionalFactVerificationErrors(verification?: SeweragePipeInputVerificationResult | null): boolean {
|
|
// if (!verification) return false;
|
|
// return Object.values(verification).some((errors) => Array.isArray(errors) && errors.length > 0);
|
|
// }
|
|
|
|
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
|
|
|
async function waitForImrJobCompleted(
|
|
jobId: string,
|
|
getStatus: (id: string) => Promise<ImrStatusResponse>,
|
|
options?: {
|
|
pollIntervalMs?: number;
|
|
maxAttempts?: number;
|
|
onProgress?: (jobProgress: number) => void;
|
|
},
|
|
): Promise<void> {
|
|
const pollIntervalMs = options?.pollIntervalMs ?? 2000;
|
|
const maxAttempts = options?.maxAttempts ?? 150;
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
const res = await getStatus(jobId);
|
|
options?.onProgress?.(Math.max(0, Math.min(100, Number(res.job_prog) || 0)));
|
|
if (res.status === "COMPLETED" && Number(res.job_prog) === 100) return;
|
|
if (res.status === "FAILED" || res.status === "ERROR" || res.status === "CANCELLED") {
|
|
throw new Error(res.error_msg || `시계열 검증이 ${res.status} 상태로 종료되었습니다.`);
|
|
}
|
|
await sleep(pollIntervalMs);
|
|
}
|
|
throw new Error("시계열 검증 완료 대기 시간이 초과되었습니다.");
|
|
}
|
|
|
|
function buildRegionalFactSubmitListFromGrid(
|
|
currentData: Record<string, string | number>[],
|
|
): SeweragePipeInputRegionalFactStatusSaveItem[] {
|
|
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);
|
|
};
|
|
return currentData.map((d) => ({
|
|
admdstCd: nullableTrimmedString(d.admdstCd),
|
|
sggNm: nullableTrimmedString(d.sggNm),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
goalYr: nullableTrimmedString(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: nullableTrimmedString(d.rmrk),
|
|
}));
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
const sidoListRef: { current: Sido[] } = { current: [] };
|
|
|
|
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
|
|
return isGugunOkForToolbar(sidoListRef.current, values);
|
|
}
|
|
|
|
function buildSubmitOkForToolbar(values: SearchFormValues | null): boolean {
|
|
return isSubmitOkForToolbar(sidoListRef.current, values);
|
|
}
|
|
|
|
/**
|
|
* @param searchContext 조회에 사용된 검색값(해당 요청 기준으로 구군·사업자 조건 반영)
|
|
* @param treatAsSearched (hasRow || 이 값) 일 때 카드 후보 — load 내부에서는 searchDoneRef 반영용
|
|
*/
|
|
function computeTabToolbarSnapshot(
|
|
statusInfo: SewerageStatusInfo | null,
|
|
hasRowData: boolean,
|
|
treatAsSearched: boolean,
|
|
searchContext: SearchFormValues | null,
|
|
periodEnterable: boolean,
|
|
isAdminInfo: boolean,
|
|
): TabToolbarSnapshot {
|
|
const gugunOk = buildGugunOkForToolbar(searchContext);
|
|
const enterable = computeTabEnterable(statusInfo, periodEnterable);
|
|
const isSubmitted = statusInfo?.laeSbmsnSttsCd === "2";
|
|
const showSaveInToolbar = (enterable || isAdminInfo) && periodEnterable && gugunOk;
|
|
const showEnterableToolbarButtons = enterable && gugunOk;
|
|
const showSubmittedInInputPeriodToolbar = isSubmitted && periodEnterable && gugunOk;
|
|
const showActionToolbarCard =
|
|
(hasRowData || treatAsSearched) &&
|
|
(showSaveInToolbar ||
|
|
showEnterableToolbarButtons ||
|
|
showSubmittedInInputPeriodToolbar ||
|
|
(isSubmitted && isAdminInfo));
|
|
return { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard };
|
|
}
|
|
|
|
// 하수도 통계 관리 > 02. 하수관로
|
|
const SewagePipeInp = () => {
|
|
const { sidoList, isReady } = useSearchConditionSidoList({ isInput: true });
|
|
sidoListRef.current = sidoList;
|
|
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, SewerageStatusInfo | null>>(new Map());
|
|
const [showDailog, setShowDailog] = useState<boolean>(false);
|
|
const [isExceptionSubmitFlow, setIsExceptionSubmitFlow] = useState(false);
|
|
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
|
|
const [statsCd, setStatsCd] = useState<string>('');
|
|
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
|
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
|
Map<string, boolean>
|
|
>(new Map());
|
|
const [footerVerificationByTab, setFooterVerificationByTab] = useState<
|
|
Map<string, VerificationResultLikeMap | null>
|
|
>(new Map());
|
|
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
|
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
|
|
/** 탭키별 `engColNm -> columnGuide` 캐시 */
|
|
const [columnGuidesByTab, setColumnGuidesByTab] = useState<Record<string, Record<string, string>>>({});
|
|
/** 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 [searchContextByTab, setSearchContextByTab] = useState<Map<string, SearchFormValues>>(
|
|
() => new Map(),
|
|
);
|
|
const { confirm } = useConfirm();
|
|
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
|
|
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
|
|
const setGlobalProgressLabel = useGlobalProgressStore((state) => state.setLabel);
|
|
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
|
|
const globalProgressVisible = useGlobalProgressStore((state) => state.visible);
|
|
const { isAdminInfo } = useAdminStore();
|
|
const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate();
|
|
|
|
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
|
const getFactInstallGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
|
const getPipeTypeGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
|
const getPipeDiameterGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
|
|
const getManholeGridDataRef = 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 postVerificationSearchRef = useRef<{
|
|
values: SearchFormValues;
|
|
tabKey: string;
|
|
} | null>(null);
|
|
/** ST00020002 무데이터 안내/자동이동은 동일 조회조건에서 1회만 수행 */
|
|
const factInstallNoDataHandledKeyRef = useRef<string | null>(null);
|
|
|
|
const [verificationSearchDefaults, setVerificationSearchDefaults] =
|
|
useState<SearchFormValues | null>(null);
|
|
const [searchFormRemountKey, setSearchFormRemountKey] = useState(0);
|
|
|
|
const firstTabKey = SewagePipeInpColumnInfoList[0]?.tabKey ?? "ST00020001";
|
|
const activeTabRef = useRef<string>(firstTabKey);
|
|
|
|
useEffect(() => {
|
|
activeTabRef.current = activeTab;
|
|
}, [activeTab]);
|
|
|
|
const activeTabId = useTabStore((s) => s.activeTabId);
|
|
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);
|
|
if (!pending) return;
|
|
const { tabKey, search } = pending;
|
|
activeTabRef.current = tabKey;
|
|
setActiveTab(tabKey);
|
|
if (search) {
|
|
const values = verificationSearchToFormValues(search, sidoList);
|
|
setVerificationSearchDefaults(values);
|
|
postVerificationSearchRef.current = { values, tabKey };
|
|
setSearchFormRemountKey((k) => k + 1);
|
|
}
|
|
}, [activeTabId, inputFocusNonce, isReady, sidoList]);
|
|
|
|
useEffect(() => {
|
|
if (activeTabId !== STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE) return;
|
|
const tabKey = activeTab;
|
|
if (!tabKey) return;
|
|
if (Object.prototype.hasOwnProperty.call(columnGuidesByTab, tabKey)) return;
|
|
|
|
let cancelled = false;
|
|
void (async () => {
|
|
const next = await fetchColumnGuidesRecord(tabKey);
|
|
if (cancelled) return;
|
|
setColumnGuidesByTab((prev) =>
|
|
Object.prototype.hasOwnProperty.call(prev, tabKey)
|
|
? prev
|
|
: { ...prev, [tabKey]: next },
|
|
);
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [activeTabId, activeTab, columnGuidesByTab]);
|
|
|
|
const syncTopStatusIfStillOnTab = (loadedTabKey: string, nextStatsCd: string) => {
|
|
if (loadedTabKey === activeTabRef.current) {
|
|
setStatsCd(nextStatsCd);
|
|
}
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setRowDataMap(new Map());
|
|
setTabStatusInfoMap(new Map());
|
|
setTabStatsCdMap(new Map());
|
|
setIsVerificationFailedByTab(new Map());
|
|
setFooterVerificationByTab(new Map());
|
|
setLastSearchedYear(null);
|
|
setLastSearchedValues(null);
|
|
setHasSearched(false);
|
|
setInputPeriodEnterable(true);
|
|
setToolbarSnapshotByTab(new Map());
|
|
setSearchContextByTab(new Map());
|
|
searchParamRef.current = null;
|
|
searchDoneRef.current = false;
|
|
lazyTabLoggedRef.current.clear();
|
|
periodEnterableRef.current = true;
|
|
activeTabRef.current = firstTabKey;
|
|
setActiveTab(firstTabKey);
|
|
setStatsCd("");
|
|
setPeriodCheckList([]);
|
|
setVerificationSearchDefaults(null);
|
|
setSearchFormRemountKey((k) => k + 1);
|
|
};
|
|
|
|
const columnInfo = useMemo(() => {
|
|
const applyColumnGuides = (cols: ColumnConfig[] | undefined, tabKey: string) =>
|
|
(cols ?? []).map((col: ColumnConfig) => {
|
|
const guide = columnGuidesByTab[tabKey]?.[col.fieldName]?.trim();
|
|
return {
|
|
...col,
|
|
tooltip: guide && guide.length > 0 ? guide : undefined,
|
|
};
|
|
});
|
|
return SewagePipeInpColumnInfoList.map((tab: ColumnInfoList) => {
|
|
const canEdit =
|
|
(computeTabEnterable(tabStatusInfoMap.get(tab.tabKey) ?? null, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
|
const applyEditable = (cols: ColumnConfig[] | undefined) =>
|
|
tab.tabKey === PIPE_TYPE_STATUS_TAB_KEY
|
|
? applyPipeTypeStatusEditableColumns(
|
|
cols,
|
|
canEdit,
|
|
inputPeriodEnterable,
|
|
lastSearchedYear,
|
|
)
|
|
: (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: applyColumnGuides(applyEditable(tab.left.columns), tab.tabKey) },
|
|
right: { ...tab.right, columns: applyColumnGuides(applyEditable(tab.right.columns), tab.tabKey) },
|
|
};
|
|
}
|
|
return {
|
|
...tab,
|
|
columns: applyColumnGuides(applyEditable(tab.columns), tab.tabKey),
|
|
...(tab.tabKey === PIPE_TYPE_STATUS_TAB_KEY && tab.columnLayout
|
|
? {
|
|
columnLayout: applyPipeTypeStatusColumnLayout(
|
|
tab.columnLayout,
|
|
shouldHidePipeTypeEditableYearDmltn(
|
|
canEdit,
|
|
inputPeriodEnterable,
|
|
lastSearchedYear,
|
|
),
|
|
),
|
|
}
|
|
: {}),
|
|
};
|
|
});
|
|
}, [tabStatusInfoMap, isAdminInfo, inputPeriodEnterable, columnGuidesByTab, lastSearchedYear]);
|
|
|
|
const commitToolbarSnapshotForLoadedTab = (
|
|
tabKey: string,
|
|
statusInfo: SewerageStatusInfo | 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;
|
|
});
|
|
if (searchContext) {
|
|
setSearchContextByTab((prev) => {
|
|
const next = new Map(prev);
|
|
next.set(tabKey, searchContext);
|
|
return next;
|
|
});
|
|
}
|
|
};
|
|
|
|
/** mergeIntoExistingMap: true면 기존 맵에 병합(다른 탭 rowData·검색 후 다른 탭 캐시 유지). false면 맵 전체 교체 */
|
|
const loadSewagePipeActiveTabData = async (
|
|
tabKey: string,
|
|
searchParam: SeweragePipeRequestParams,
|
|
values: SearchFormValues,
|
|
mergeIntoExistingMap: boolean,
|
|
updateLastSearchedValues: boolean,
|
|
): Promise<boolean> => {
|
|
const searchConditionKey = JSON.stringify({
|
|
statsYr: searchParam.statsYr ?? "",
|
|
ctpvNm: searchParam.ctpvNm ?? "",
|
|
sggNm: values?.gugun === "ALL" ? "" : (values?.gugun ?? searchParam.sggNm ?? ""),
|
|
sewerBzmnCd: searchParam.sewerBzmnCd ?? "",
|
|
});
|
|
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);
|
|
}
|
|
};
|
|
const filterFetched = <T,>(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 = filterFetched(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));
|
|
setIsVerificationFailedByTab((prev) =>
|
|
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
|
);
|
|
setFooterVerificationByTab((prev) =>
|
|
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
|
);
|
|
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 = filterFetched(response.data.factInstallStatusList).map((d) => ({
|
|
ctpvNm: commonInfo.ctpvNm,
|
|
...d,
|
|
}));
|
|
if (convertedData.length === 0) {
|
|
const alreadyHandledForSameSearch =
|
|
factInstallNoDataHandledKeyRef.current === searchConditionKey;
|
|
if (!alreadyHandledForSameSearch) {
|
|
factInstallNoDataHandledKeyRef.current = searchConditionKey;
|
|
alert("시설별 설치 현황에 조회된 데이터가 없습니다.\n시설별 현황 관리에서 설정해주세요");
|
|
activeTabRef.current = "ST00020006";
|
|
setActiveTab("ST00020006");
|
|
return await loadSewagePipeActiveTabData(
|
|
"ST00020006",
|
|
searchParam,
|
|
values,
|
|
true,
|
|
updateLastSearchedValues,
|
|
);
|
|
}
|
|
}
|
|
applyRowMap((m) => m.set('ST00020002', toGridRows(convertedData)));
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
setIsVerificationFailedByTab((prev) =>
|
|
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
|
);
|
|
setFooterVerificationByTab((prev) =>
|
|
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
|
);
|
|
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 = filterFetched(response.data.pipeTypeStatusList).map((d) =>
|
|
normalizePipeTypeStatusRow(d, commonInfo.ctpvNm),
|
|
);
|
|
applyRowMap((m) => m.set('ST00020003', toGridRows(convertedData)));
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
setIsVerificationFailedByTab((prev) =>
|
|
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
|
);
|
|
setFooterVerificationByTab((prev) =>
|
|
patchFooterVerificationByTab(prev, tabKey, {
|
|
verificationResult:
|
|
remapPipeTypePrlgWholVerification(commonInfo.verificationResult) ??
|
|
null,
|
|
}),
|
|
);
|
|
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 = filterFetched(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));
|
|
setIsVerificationFailedByTab((prev) =>
|
|
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
|
);
|
|
setFooterVerificationByTab((prev) =>
|
|
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
|
);
|
|
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 = filterFetched(response.data.manholeStatusList).map((d) =>
|
|
normalizeManholeStatusRow(d, commonInfo.ctpvNm),
|
|
);
|
|
applyRowMap((m) => m.set('ST00020005', toGridRows(convertedData)));
|
|
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
|
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
|
setIsVerificationFailedByTab((prev) =>
|
|
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
|
);
|
|
setFooterVerificationByTab((prev) =>
|
|
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
|
);
|
|
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 = filterFetched(response.data.facilityConnectionList).map((d) => ({
|
|
...d,
|
|
}));
|
|
const adminConvertedData = filterFetched(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));
|
|
setIsVerificationFailedByTab((prev) =>
|
|
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
|
);
|
|
setFooterVerificationByTab((prev) =>
|
|
patchFooterVerificationByTab(prev, tabKey, commonInfo),
|
|
);
|
|
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,
|
|
options?: { forcedTabKey?: string },
|
|
) => {
|
|
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 periods = Array.isArray(periodResponse.data) ? periodResponse.data : [];
|
|
setPeriodCheckList(periods);
|
|
const periodOk = isInputPeriodCurrent(periods);
|
|
periodEnterableRef.current = periodOk;
|
|
setInputPeriodEnterable(periodOk);
|
|
|
|
/** 연도가 바뀐 검색만 전 탭 캐시 초기화 + 첫 탭으로 이동. 연도 외 조건만 바뀌면 다른 탭은 이전 조회 데이터 유지 */
|
|
const yearChangedFromLastSearch =
|
|
lastSearchedValues != null &&
|
|
(lastSearchedValues.year ?? "") !== (values.year ?? "");
|
|
if (yearChangedFromLastSearch) {
|
|
setRowDataMap(new Map());
|
|
setTabStatusInfoMap(new Map());
|
|
setTabStatsCdMap(new Map());
|
|
setIsVerificationFailedByTab(new Map());
|
|
setFooterVerificationByTab(new Map());
|
|
setStatsCd("");
|
|
setToolbarSnapshotByTab(new Map());
|
|
setSearchContextByTab(new Map());
|
|
}
|
|
|
|
lazyTabLoggedRef.current.clear();
|
|
|
|
setIsLoading(true);
|
|
|
|
const searchParam = buildSewagePipeSearchParam(values);
|
|
searchParamRef.current = searchParam;
|
|
searchDoneRef.current = true;
|
|
setHasSearched(true);
|
|
setLastSearchedYear(values.year ?? null);
|
|
|
|
const forcedTab = options?.forcedTabKey;
|
|
const tabKeyToLoad = forcedTab
|
|
? forcedTab
|
|
: yearChangedFromLastSearch
|
|
? firstTabKey
|
|
: activeTab;
|
|
|
|
if (forcedTab) {
|
|
activeTabRef.current = forcedTab;
|
|
setActiveTab(forcedTab);
|
|
} else if (yearChangedFromLastSearch) {
|
|
activeTabRef.current = firstTabKey;
|
|
setActiveTab(firstTabKey);
|
|
} else {
|
|
activeTabRef.current = activeTab;
|
|
}
|
|
await loadSewagePipeActiveTabData(tabKeyToLoad, searchParam, values, true, true);
|
|
} catch {
|
|
alert('데이터 조회 중 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const job = postVerificationSearchRef.current;
|
|
if (!job) return;
|
|
postVerificationSearchRef.current = null;
|
|
void handleSearch(job.values, { forcedTabKey: job.tabKey });
|
|
|
|
}, [searchFormRemountKey]);
|
|
|
|
const getCurrentRowsForTab = (tabKey: string): Record<string, string | number>[] => {
|
|
switch (tabKey) {
|
|
case REGIONAL_FACT_TAB_KEY:
|
|
return getGridDataRef.current?.() ?? [];
|
|
case "ST00020002":
|
|
return getFactInstallGridDataRef.current?.() ?? [];
|
|
case "ST00020003":
|
|
return getPipeTypeGridDataRef.current?.() ?? [];
|
|
case "ST00020004":
|
|
return getPipeDiameterGridDataRef.current?.() ?? [];
|
|
case "ST00020005":
|
|
return getManholeGridDataRef.current?.() ?? [];
|
|
case "ST00020006":
|
|
return [
|
|
...(rowDataMap.get("treatmentPlantConnectionInp") ?? []),
|
|
...(rowDataMap.get("adminAreaConnectionInp") ?? []),
|
|
];
|
|
default:
|
|
return rowDataMap.get(tabKey) ?? [];
|
|
}
|
|
};
|
|
|
|
const handleActiveTabChange = async (tabKey: string) => {
|
|
const prevTabKey = activeTabRef.current;
|
|
const latestRowsInPrevTab = getCurrentRowsForTab(prevTabKey);
|
|
if (latestRowsInPrevTab.length > 0 || rowDataMap.has(prevTabKey)) {
|
|
setRowDataMap((prev) => {
|
|
const next = new Map(prev);
|
|
next.set(prevTabKey, latestRowsInPrevTab);
|
|
return next;
|
|
});
|
|
}
|
|
|
|
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 getTabGridRowsForVerification = (tabKey: string): Record<string, string | number>[] => {
|
|
// switch (tabKey) {
|
|
// case "ST00020001":
|
|
// return getGridDataRef.current?.() ?? [];
|
|
// case "ST00020002":
|
|
// return getFactInstallGridDataRef.current?.() ?? [];
|
|
// case "ST00020003":
|
|
// return getPipeTypeGridDataRef.current?.() ?? [];
|
|
// case "ST00020004":
|
|
// return getPipeDiameterGridDataRef.current?.() ?? [];
|
|
// case "ST00020005":
|
|
// return getManholeGridDataRef.current?.() ?? [];
|
|
// case "ST00020006":
|
|
// return [
|
|
// ...(rowDataMap.get("treatmentPlantConnectionInp") ?? []),
|
|
// ...(rowDataMap.get("adminAreaConnectionInp") ?? []),
|
|
// ];
|
|
// default:
|
|
// return [];
|
|
// }
|
|
// };
|
|
|
|
const SubmitExceptionFunc = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
|
|
const tabKey = activeTabRef.current;
|
|
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
|
|
if (verificationBlocked) {
|
|
alert(verificationBlocked);
|
|
return;
|
|
}
|
|
const userInfo = await getUserInfo();
|
|
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? statsCd;
|
|
|
|
const params: SubmitExceptionRequest = {
|
|
...submitInfo,
|
|
...toInputSearchRegion(sidoListRef.current, searchValues),
|
|
ctpvNm: searchValues?.sido ?? "",
|
|
statsYr: searchValues?.year ?? "",
|
|
userId: userInfo.userUnqId,
|
|
statsCd: statsCdForTab,
|
|
};
|
|
|
|
try {
|
|
setIsLoading(true);
|
|
const response = await statisticsApi.submitException(params);
|
|
if (response.status === "success") {
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (values && searchParamRef.current) {
|
|
await loadSewagePipeActiveTabData(tabKey, searchParamRef.current, values, true, false);
|
|
}
|
|
alert("제출되었습니다.");
|
|
} else {
|
|
alert(response.message || "데이터 제출 중 오류가 발생했습니다.");
|
|
}
|
|
} catch {
|
|
alert("데이터 제출 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const submitFunc = async (isException: boolean) => {
|
|
// const tabKey = activeTabRef.current;
|
|
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
|
|
if (verificationBlocked) {
|
|
alert(verificationBlocked);
|
|
return;
|
|
}
|
|
if (!isException) {
|
|
// const currentData = getTabGridRowsForVerification(tabKey);
|
|
// const rowsWithVerificationErrors = currentData.filter((d) =>
|
|
// hasRegionalFactVerificationErrors((d as GridRowWithVerification).verificationResult),
|
|
// );
|
|
// if (rowsWithVerificationErrors.length > 0) {
|
|
// alert("데이터에 오류가 있습니다.\n수정 후 제출해주세요.");
|
|
// return;
|
|
// }
|
|
if (await confirm("제출 하시겠습니까?")) {
|
|
setIsExceptionSubmitFlow(false);
|
|
setShowDailog(true);
|
|
}
|
|
} else {
|
|
if (await confirm("예외 제출 시 검증 결과에 오류가 있어도 제출 처리됩니다.")) {
|
|
setIsExceptionSubmitFlow(true);
|
|
setShowDailog(true);
|
|
}
|
|
}
|
|
};
|
|
|
|
const executeExceptionSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
|
|
await SubmitExceptionFunc(submitInfo);
|
|
};
|
|
|
|
const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
|
|
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
|
|
if (verificationBlocked) {
|
|
alert(verificationBlocked);
|
|
return;
|
|
}
|
|
const tabKey = activeTabRef.current;
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!values?.year) {
|
|
alert("검색 후 제출해 주세요.");
|
|
return;
|
|
}
|
|
const searchParam = searchParamRef.current;
|
|
if (!searchParam) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
showGlobalProgress({ label: "서버에 요청 중...", progress: 0 });
|
|
const userInfo = await getUserInfo();
|
|
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? statsCd;
|
|
const statusForTab = tabStatusInfoMap.get(tabKey) ?? null;
|
|
const submitBase = {
|
|
statsYr: values.year ?? "",
|
|
ctpvNm: values.sido ?? "",
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
userId: userInfo.userUnqId,
|
|
laePicNm: submitInfo.laePicNm,
|
|
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
|
|
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
|
|
laePicTelNo: submitInfo.laePicTelNo,
|
|
laeIdfrNm: submitInfo.laeIdfrNm,
|
|
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
|
|
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
|
|
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
|
|
};
|
|
let response: SeweragePipeInputSubmitResponse | null = null;
|
|
|
|
switch (tabKey) {
|
|
case REGIONAL_FACT_TAB_KEY: {
|
|
const currentData = getGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("제출할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const submitList = buildRegionalFactSubmitListFromGrid(currentData);
|
|
|
|
const requestData: SeweragePipeInputRegionalFactStatusSubmitRequest = {
|
|
...submitBase,
|
|
submitList,
|
|
};
|
|
|
|
setGlobalProgressLabel("일반 검증 진행 중...");
|
|
response =
|
|
await statisticsInputSewPipeApiService.submitRegionalFactStatus(requestData);
|
|
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 submitList = currentData.map((d) => ({
|
|
admdstCd: nullableTrimmedString(d.admdstCd),
|
|
sggNm: nullableTrimmedString(d.sggNm),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
fcltSn: nullableTrimmedString(d.fcltSn),
|
|
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
|
swtrmtFcltNm: nullableTrimmedString(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: nullableTrimmedString(d.rmrk),
|
|
}));
|
|
const requestData: SeweragePipeInputFactInstallStatusSubmitRequest = {
|
|
...submitBase,
|
|
submitList,
|
|
};
|
|
setGlobalProgressLabel("일반 검증 진행 중...");
|
|
response = await statisticsInputSewPipeApiService.submitFactInstallStatus(requestData);
|
|
break;
|
|
}
|
|
case "ST00020003": {
|
|
const currentData = getPipeTypeGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("제출할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
const columnFormatMap = new Map<string, string>();
|
|
PipeTypeStatusInpColumn.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 lastDataYear = getPipeTypeLastDataYear();
|
|
const submitList = currentData.map((d) => {
|
|
const yearlyStatList = [];
|
|
for (let year = 1995; year <= lastDataYear; year++) {
|
|
const wholKey = `y${year}PrlgWhol`;
|
|
const lyngKey = `y${year}LyngPrlg`;
|
|
const dmltnKey = `y${year}DmltnPrlg`;
|
|
yearlyStatList.push({
|
|
lyngYr: String(year),
|
|
lyngPrlg: formatNumber(d[lyngKey] as string | number | undefined, lyngKey),
|
|
dmltnPrlg: formatNumber(d[dmltnKey] as string | number | undefined, dmltnKey),
|
|
prlgWhol: formatNumber(d[wholKey] as string | number | undefined, wholKey),
|
|
});
|
|
}
|
|
return {
|
|
admdstCd: nullableTrimmedString(d.admdstCd),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
pipeSeCd: nullableTrimmedString(d.pipeSeCd),
|
|
yearlyStatList,
|
|
rmrk: nullableTrimmedString(d.rmrk),
|
|
lyngPrlgWhol: formatNumber(d.lyngPrlg, "lyngPrlg"),
|
|
dmltnPrlgWhol: formatNumber(d.dmltnPrlg, "dmltnPrlg"),
|
|
pipeByPrstWhol: formatNumber(d.pipeByPrstWhol, "pipeByPrstWhol"),
|
|
};
|
|
});
|
|
const requestData: SeweragePipeInputPipeTypeStatusSubmitRequest = {
|
|
...submitBase,
|
|
submitList,
|
|
};
|
|
setGlobalProgressLabel("일반 검증 진행 중...");
|
|
response = await statisticsInputSewPipeApiService.submitPipeTypeStatus(requestData);
|
|
break;
|
|
}
|
|
case "ST00020004": {
|
|
const currentData = getPipeDiameterGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("제출할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
const columnFormatMap = new Map<string, string>();
|
|
PipeDiameterStatusInpColumn.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 submitList = currentData.map((d) => ({
|
|
pipeSeCd: nullableTrimmedString(d.pipeSeCd),
|
|
step1PdmtPrlg: formatNumber(d.step1PdmtPrlg, "step1PdmtPrlg"),
|
|
step2PdmtPrlg: formatNumber(d.step2PdmtPrlg, "step2PdmtPrlg"),
|
|
step3PdmtPrlg: formatNumber(d.step3PdmtPrlg, "step3PdmtPrlg"),
|
|
step4PdmtPrlg: formatNumber(d.step4PdmtPrlg, "step4PdmtPrlg"),
|
|
step5PdmtPrlg: formatNumber(d.step5PdmtPrlg, "step5PdmtPrlg"),
|
|
sqrePdmtPrlg: formatNumber(d.sqrePdmtPrlg, "sqrePdmtPrlg"),
|
|
etcPdmtPrlg: formatNumber(d.etcPdmtPrlg, "etcPdmtPrlg"),
|
|
rmrk: nullableTrimmedString(d.rmrk),
|
|
pdmtByTsmWhol: formatNumber(d.pdmtByTsmWhol, "pdmtByTsmWhol"),
|
|
admdstCd: nullableTrimmedString(d.admdstCd),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
}));
|
|
const requestData: SeweragePipeInputPipeDiameterStatusSubmitRequest = {
|
|
...submitBase,
|
|
submitList,
|
|
};
|
|
setGlobalProgressLabel("일반 검증 진행 중...");
|
|
response = await statisticsInputSewPipeApiService.submitPipeDiameterStatus(requestData);
|
|
break;
|
|
}
|
|
case "ST00020005": {
|
|
const currentData = getManholeGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("제출할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
const columnFormatMap = new Map<string, string>();
|
|
ManholeStatusInpColumn.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 minYy = 14;
|
|
const maxYy = getManholeLastDataYear() % 100;
|
|
const submitList = currentData.map((d) => {
|
|
const yearlyStats: SeweragePipeInputManholeStatusSaveYearlyStatItem[] = [];
|
|
for (let yy = minYy; yy <= maxYy; yy++) {
|
|
const year = 2000 + yy;
|
|
const wholKey = `yy${yy}WholMnhlCnt`;
|
|
const step1Key = `yy${yy}Step1MnhlCnt`;
|
|
const step2Key = `yy${yy}Step2MnhlCnt`;
|
|
const step3Key = `yy${yy}Step3MnhlCnt`;
|
|
const step4Key = `yy${yy}Step4MnhlCnt`;
|
|
const step5Key = `yy${yy}Step5MnhlCnt`;
|
|
yearlyStats.push({
|
|
manhYr: String(year),
|
|
wholMnhlCnt: formatNumber(d[wholKey] as string | number | undefined, wholKey),
|
|
step1MnhlCnt: formatNumber(d[step1Key] as string | number | undefined, step1Key),
|
|
step2MnhlCnt: formatNumber(d[step2Key] as string | number | undefined, step2Key),
|
|
step3MnhlCnt: formatNumber(d[step3Key] as string | number | undefined, step3Key),
|
|
step4MnhlCnt: formatNumber(d[step4Key] as string | number | undefined, step4Key),
|
|
step5MnhlCnt: formatNumber(d[step5Key] as string | number | undefined, step5Key),
|
|
});
|
|
}
|
|
return {
|
|
emphsMngSeCd: nullableTrimmedString(d.emphsMngSeCd),
|
|
admdstCd: nullableTrimmedString(d.admdstCd),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
yearlyStats,
|
|
rmrk: nullableTrimmedString(d.rmrk),
|
|
};
|
|
});
|
|
const requestData: SeweragePipeInputManholeStatusSubmitRequest = {
|
|
...submitBase,
|
|
submitList,
|
|
};
|
|
setGlobalProgressLabel("일반 검증 진행 중...");
|
|
response = await statisticsInputSewPipeApiService.submitManholeStatus(requestData);
|
|
break;
|
|
}
|
|
default:
|
|
alert("해당 탭 제출 API는 준비 중입니다.");
|
|
return;
|
|
}
|
|
|
|
if (!response || response.status !== "success") {
|
|
alert(response?.message || "데이터 제출 중 오류가 발생했습니다.");
|
|
return;
|
|
}
|
|
|
|
const finalStatus = response.data?.finalStatus;
|
|
const nvpassed = response.data?.nvpassed;
|
|
|
|
if (finalStatus === "5") {
|
|
setGlobalProgress(50);
|
|
setGlobalProgressLabel("시계열 검증 진행 중...");
|
|
const imrParam = {
|
|
user_id: userInfo.userUnqId,
|
|
sewer_bzmn_cd: values?.bsnmCd ?? '',
|
|
stats_cd: statsCdForTab,
|
|
stats_yr: values.year ?? "",
|
|
lae_sbmsn_stts_cd: statusForTab?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
|
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
|
};
|
|
|
|
try {
|
|
const imrResponse = await statisticsApi.callImrService(imrParam);
|
|
if (imrResponse.job_id) {
|
|
await waitForImrJobCompleted(
|
|
imrResponse.job_id,
|
|
(id) => statisticsApi.getImrStatus(id),
|
|
{
|
|
onProgress: (jobProgress) => {
|
|
setGlobalProgress(50 + Math.round(jobProgress / 2));
|
|
},
|
|
},
|
|
);
|
|
}
|
|
setGlobalProgress(100);
|
|
await loadSewagePipeActiveTabData(
|
|
tabKey,
|
|
buildSewagePipeSearchParam(values),
|
|
values,
|
|
true,
|
|
false,
|
|
);
|
|
await openVerificationResultDialogIfNeeded(
|
|
{
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
ctpvNm: values.sido ?? "",
|
|
statsYr: values.year ?? "",
|
|
statsCd: statsCdForTab,
|
|
},
|
|
setVerificationResultDialogOpen,
|
|
{ notify: alert },
|
|
);
|
|
} catch (e) {
|
|
await loadSewagePipeActiveTabData(
|
|
tabKey,
|
|
buildSewagePipeSearchParam(values),
|
|
values,
|
|
true,
|
|
false,
|
|
);
|
|
await openVerificationResultDialogIfNeeded(
|
|
{
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
ctpvNm: values.sido ?? "",
|
|
statsYr: values.year ?? "",
|
|
statsCd: statsCdForTab,
|
|
},
|
|
setVerificationResultDialogOpen,
|
|
{ notify: alert, notifySubmitComplete: false },
|
|
);
|
|
const msg =
|
|
e instanceof Error && e.message
|
|
? e.message
|
|
: "IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.";
|
|
alert(msg);
|
|
}
|
|
} else {
|
|
setGlobalProgress(100);
|
|
await loadSewagePipeActiveTabData(
|
|
tabKey,
|
|
buildSewagePipeSearchParam(values),
|
|
values,
|
|
true,
|
|
false,
|
|
);
|
|
await openVerificationResultDialogIfNeeded(
|
|
{
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
ctpvNm: values.sido ?? "",
|
|
statsYr: values.year ?? "",
|
|
statsCd: statsCdForTab,
|
|
},
|
|
setVerificationResultDialogOpen,
|
|
{ notify: alert },
|
|
);
|
|
}
|
|
} catch {
|
|
alert("데이터 제출 중 오류가 발생했습니다.");
|
|
} finally {
|
|
hideGlobalProgress();
|
|
}
|
|
};
|
|
|
|
const cnacelSubmitFunc = async () => {
|
|
try {
|
|
if (await confirm('제출된 내용을 취소하시겠습니까?')) {
|
|
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
|
|
if (verificationBlocked) {
|
|
alert(verificationBlocked);
|
|
return;
|
|
}
|
|
|
|
const userInfo = await getUserInfo();
|
|
|
|
const requestData: cancelSubmitRequest = {
|
|
...toInputSearchRegion(sidoListRef.current, searchValues),
|
|
ctpvNm: searchValues?.sido ?? '',
|
|
statsYr: searchValues?.year ?? '',
|
|
mdfrId: userInfo.userUnqId,
|
|
statsCd: tabStatsCdMap.get(activeTab) ?? statsCd,
|
|
};
|
|
|
|
const response = await statisticsApi.cancelSubmit(requestData);
|
|
|
|
if (response.status === 'success') {
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (values) {
|
|
const refreshedParam = buildSewagePipeSearchParam(values);
|
|
await loadSewagePipeActiveTabData(activeTab, refreshedParam, values, true, false);
|
|
}
|
|
alert('제출 취소되었습니다.');
|
|
} else {
|
|
alert(response.message ?? '제출 취소 중 오류가 발생했습니다.');
|
|
}
|
|
}
|
|
} catch {
|
|
alert('제출 취소 중 오류가 발생했습니다.');
|
|
}
|
|
};
|
|
|
|
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 () => {
|
|
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
|
|
if (verificationBlocked) {
|
|
alert(verificationBlocked);
|
|
return;
|
|
}
|
|
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: nullableTrimmedString(d.admdstCd),
|
|
sggNm: nullableTrimmedString(d.sggNm),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
goalYr: nullableTrimmedString(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: nullableTrimmedString(d.rmrk),
|
|
}));
|
|
|
|
const param: SeweragePipeInputRegionalFactStatusSaveRequest = {
|
|
statsYr: values.year ?? "",
|
|
ctpvNm: values.sido ?? "",
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
userId: userInfo.userUnqId,
|
|
...(isAdminInfo ? { superAdmin: "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: nullableTrimmedString(d.admdstCd),
|
|
sggNm: nullableTrimmedString(d.sggNm),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
fcltSn: nullableTrimmedString(d.fcltSn),
|
|
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
|
swtrmtFcltNm: nullableTrimmedString(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: nullableTrimmedString(d.rmrk),
|
|
}));
|
|
|
|
const param: SeweragePipeInputFactInstallStatusSaveRequest = {
|
|
statsYr: values.year ?? "",
|
|
ctpvNm: values.sido ?? "",
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
userId: userInfo.userUnqId,
|
|
...(isAdminInfo ? { superAdmin: "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;
|
|
}
|
|
case "ST00020003": {
|
|
const currentData = getPipeTypeGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("저장할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const columnFormatMap = new Map<string, string>();
|
|
PipeTypeStatusInpColumn.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 lastDataYear = getPipeTypeLastDataYear();
|
|
|
|
const saveList = currentData.map((d) => {
|
|
const yearlyStatList = [];
|
|
for (let year = 1995; year <= lastDataYear; year++) {
|
|
const wholKey = `y${year}PrlgWhol`;
|
|
const lyngKey = `y${year}LyngPrlg`;
|
|
const dmltnKey = `y${year}DmltnPrlg`;
|
|
yearlyStatList.push({
|
|
lyngYr: String(year),
|
|
lyngPrlg: formatNumber(
|
|
d[lyngKey] as string | number | undefined,
|
|
lyngKey,
|
|
),
|
|
dmltnPrlg: formatNumber(
|
|
d[dmltnKey] as string | number | undefined,
|
|
dmltnKey,
|
|
),
|
|
prlgWhol: formatNumber(
|
|
d[wholKey] as string | number | undefined,
|
|
wholKey,
|
|
),
|
|
});
|
|
}
|
|
return {
|
|
admdstCd: nullableTrimmedString(d.admdstCd),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
pipeSeCd: nullableTrimmedString(d.pipeSeCd),
|
|
yearlyStatList,
|
|
rmrk: nullableTrimmedString(d.rmrk),
|
|
lyngPrlgWhol: formatNumber(d.lyngPrlg, "lyngPrlg"),
|
|
dmltnPrlgWhol: formatNumber(d.dmltnPrlg, "dmltnPrlg"),
|
|
pipeByPrstWhol: formatNumber(d.pipeByPrstWhol, "pipeByPrstWhol"),
|
|
};
|
|
});
|
|
|
|
const userInfo = await getUserInfo();
|
|
|
|
const param: SeweragePipeInputPipeTypeStatusSaveRequest = {
|
|
statsYr: values.year ?? "",
|
|
ctpvNm: values.sido ?? "",
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
userId: userInfo.userUnqId,
|
|
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
|
saveList,
|
|
};
|
|
|
|
const response = await statisticsInputSewPipeApiService.savePipeTypeStatus(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 "ST00020004": {
|
|
const currentData = getPipeDiameterGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("저장할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const columnFormatMap = new Map<string, string>();
|
|
PipeDiameterStatusInpColumn.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 saveList = currentData.map((d) => ({
|
|
pipeSeCd: nullableTrimmedString(d.pipeSeCd),
|
|
step1PdmtPrlg: formatNumber(d.step1PdmtPrlg, "step1PdmtPrlg"),
|
|
step2PdmtPrlg: formatNumber(d.step2PdmtPrlg, "step2PdmtPrlg"),
|
|
step3PdmtPrlg: formatNumber(d.step3PdmtPrlg, "step3PdmtPrlg"),
|
|
step4PdmtPrlg: formatNumber(d.step4PdmtPrlg, "step4PdmtPrlg"),
|
|
step5PdmtPrlg: formatNumber(d.step5PdmtPrlg, "step5PdmtPrlg"),
|
|
sqrePdmtPrlg: formatNumber(d.sqrePdmtPrlg, "sqrePdmtPrlg"),
|
|
etcPdmtPrlg: formatNumber(d.etcPdmtPrlg, "etcPdmtPrlg"),
|
|
rmrk: nullableTrimmedString(d.rmrk),
|
|
pdmtByTsmWhol: formatNumber(d.pdmtByTsmWhol, "pdmtByTsmWhol"),
|
|
admdstCd: nullableTrimmedString(d.admdstCd),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
}));
|
|
|
|
const userInfo = await getUserInfo();
|
|
|
|
const param: SeweragePipeInputPipeDiameterStatusSaveRequest = {
|
|
statsYr: values.year ?? "",
|
|
ctpvNm: values.sido ?? "",
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
userId: userInfo.userUnqId,
|
|
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
|
saveList,
|
|
};
|
|
|
|
const response = await statisticsInputSewPipeApiService.savePipeDiameterStatus(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 "ST00020005": {
|
|
const currentData = getManholeGridDataRef.current?.();
|
|
if (!currentData || currentData.length === 0) {
|
|
alert("저장할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const columnFormatMap = new Map<string, string>();
|
|
ManholeStatusInpColumn.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 minYy = 14;
|
|
const maxYy = getManholeLastDataYear() % 100;
|
|
|
|
const saveList = currentData.map((d) => {
|
|
const yearlyStats: SeweragePipeInputManholeStatusSaveYearlyStatItem[] = [];
|
|
for (let yy = minYy; yy <= maxYy; yy++) {
|
|
const year = 2000 + yy;
|
|
const wholKey = `yy${yy}WholMnhlCnt`;
|
|
const step1Key = `yy${yy}Step1MnhlCnt`;
|
|
const step2Key = `yy${yy}Step2MnhlCnt`;
|
|
const step3Key = `yy${yy}Step3MnhlCnt`;
|
|
const step4Key = `yy${yy}Step4MnhlCnt`;
|
|
const step5Key = `yy${yy}Step5MnhlCnt`;
|
|
yearlyStats.push({
|
|
manhYr: String(year),
|
|
wholMnhlCnt: formatNumber(d[wholKey] as string | number | undefined, wholKey),
|
|
step1MnhlCnt: formatNumber(d[step1Key] as string | number | undefined, step1Key),
|
|
step2MnhlCnt: formatNumber(d[step2Key] as string | number | undefined, step2Key),
|
|
step3MnhlCnt: formatNumber(d[step3Key] as string | number | undefined, step3Key),
|
|
step4MnhlCnt: formatNumber(d[step4Key] as string | number | undefined, step4Key),
|
|
step5MnhlCnt: formatNumber(d[step5Key] as string | number | undefined, step5Key),
|
|
});
|
|
}
|
|
return {
|
|
emphsMngSeCd: nullableTrimmedString(d.emphsMngSeCd),
|
|
admdstCd: nullableTrimmedString(d.admdstCd),
|
|
admdstNm: nullableTrimmedString(d.admdstNm),
|
|
yearlyStats,
|
|
rmrk: nullableTrimmedString(d.rmrk),
|
|
};
|
|
});
|
|
|
|
const userInfo = await getUserInfo();
|
|
const param: SeweragePipeInputManholeStatusSaveRequest = {
|
|
statsYr: values.year ?? "",
|
|
ctpvNm: values.sido ?? "",
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
userId: userInfo.userUnqId,
|
|
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
|
saveList,
|
|
};
|
|
|
|
const response = await statisticsInputSewPipeApiService.saveManholeStatus(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 searchContextForToolbar = searchContextByTab.get(activeTab) ?? lastSearchedValues;
|
|
const gugunOkOrNotDoList = buildGugunOkForToolbar(searchContextForToolbar);
|
|
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
|
|
|
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
|
|
const activeTabToolbar =
|
|
computeTabToolbarSnapshot(
|
|
activeTabStatus,
|
|
tabRowDataExists(rowDataMap, activeTab),
|
|
hasSearched,
|
|
searchContextForToolbar,
|
|
inputPeriodEnterable,
|
|
isAdminInfo,
|
|
);
|
|
|
|
const hideToolbarForConnectionTab = activeTab === "ST00020006";
|
|
const {
|
|
showSaveInToolbar: baseShowSaveInToolbar,
|
|
showEnterableToolbarButtons: baseShowEnterableToolbarButtons,
|
|
showActionToolbarCard: baseShowActionToolbarCard,
|
|
} = activeTabToolbar;
|
|
const showSaveInToolbar =
|
|
!hideToolbarForConnectionTab && baseShowSaveInToolbar && toolbarGroupAllowed;
|
|
const showEnterableToolbarButtons =
|
|
!hideToolbarForConnectionTab && baseShowEnterableToolbarButtons && toolbarGroupAllowed;
|
|
const showActionToolbarCard =
|
|
!hideToolbarForConnectionTab && baseShowActionToolbarCard && toolbarGroupAllowed;
|
|
|
|
return (
|
|
<>
|
|
<SearchForm
|
|
isInput
|
|
key={searchFormRemountKey}
|
|
defaultValues={verificationSearchDefaults ?? undefined}
|
|
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}
|
|
isVerificationFailed={
|
|
isVerificationFailedByTab.get(activeTab) === true
|
|
}
|
|
/>
|
|
{showEnterableToolbarButtons && (
|
|
<>
|
|
{activeTab !== 'ST00020002' && <LastYearDataButton getLastYearDataFunc={() => void getLastYearDataFunc()} />}
|
|
{submitOk && <SubmitButton submitFunc={() => void submitFunc(false)} />}
|
|
</>
|
|
)}
|
|
{activeTabStatus?.laeSbmsnSttsCd === "2" && submitOk && (
|
|
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
|
)}
|
|
{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 =
|
|
activeTab === tabKey &&
|
|
Boolean(splitTopButton?.text) &&
|
|
(splitTopButton?.visible ?? true) &&
|
|
tabRowDataExists(rowDataMap, tabKey) &&
|
|
gugunOkOrNotDoList;
|
|
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={tabKey}
|
|
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}
|
|
excelImportSkipNonEditable={
|
|
tabKey === PIPE_TYPE_STATUS_TAB_KEY
|
|
? !isAdminInfo
|
|
: false
|
|
}
|
|
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
|
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
|
isExcelExport
|
|
showHeaderCheckbox={false}
|
|
fixedColumns={info.fixedColumns}
|
|
enableSort={false}
|
|
statusInfo={tabStatusInfoMap.get(tabKey) ?? null}
|
|
getGridDataRef={
|
|
tabKey === REGIONAL_FACT_TAB_KEY
|
|
? getGridDataRef
|
|
: tabKey === "ST00020002"
|
|
? getFactInstallGridDataRef
|
|
: tabKey === "ST00020003"
|
|
? getPipeTypeGridDataRef
|
|
: tabKey === "ST00020004"
|
|
? getPipeDiameterGridDataRef
|
|
: tabKey === "ST00020005"
|
|
? getManholeGridDataRef
|
|
: 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" || e.vrfcCd === "HB");
|
|
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
|
|
}
|
|
}}
|
|
footerVerificationResult={
|
|
footerVerificationByTab.get(tabKey) ?? null
|
|
}
|
|
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" ? "시계열 검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "모집단 검증 - ");
|
|
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)
|
|
? {
|
|
...toInputSearchRegion(sidoListRef.current, searchValues),
|
|
ctpvNm: searchValues.sido,
|
|
statsYr: searchValues.year,
|
|
statsCd: tabStatsCdMap.get(activeTab) ?? statsCd,
|
|
}
|
|
: null
|
|
}
|
|
onExceptionResetSubmitted={async () => {
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!values?.year || !values?.sido || !values?.bsnmCd) return;
|
|
const refreshedParam = buildSewagePipeSearchParam(values);
|
|
await loadSewagePipeActiveTabData(activeTab, refreshedParam, values, true, false);
|
|
}}
|
|
/>
|
|
<SubmitDailog
|
|
open={showDailog}
|
|
onOpenChange={(open) => {
|
|
setShowDailog(open);
|
|
if (!open) setIsExceptionSubmitFlow(false);
|
|
}}
|
|
statusInfo={tabStatusInfoMap.get(activeTab) ?? null}
|
|
onConfirm={executeSubmit}
|
|
onExceptionConfirm={executeExceptionSubmit}
|
|
submitMode={isExceptionSubmitFlow ? "exception" : "normal"}
|
|
isLoading={isLoading}
|
|
/>
|
|
<VerificationResultDialog
|
|
open={verificationResultDialogOpen}
|
|
onOpenChange={setVerificationResultDialogOpen}
|
|
queryContext={
|
|
searchValues?.year &&
|
|
searchValues?.sido &&
|
|
searchValues?.bsnmCd &&
|
|
(tabStatsCdMap.get(activeTab) ?? statsCd)
|
|
? {
|
|
...toInputSearchRegion(sidoListRef.current, searchValues),
|
|
ctpvNm: searchValues.sido,
|
|
statsYr: searchValues.year,
|
|
statsCd: tabStatsCdMap.get(activeTab) ?? statsCd,
|
|
}
|
|
: null
|
|
}
|
|
onExceptionSubmitted={async () => {
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!values?.year || !values?.sido || !values?.bsnmCd) return;
|
|
const refreshedParam = buildSewagePipeSearchParam(values);
|
|
await loadSewagePipeActiveTabData(activeTab, refreshedParam, values, true, false);
|
|
}}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SewagePipeInp; |