1128 lines
44 KiB
TypeScript
1128 lines
44 KiB
TypeScript
import {
|
|
type RefObject,
|
|
useCallback,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import { flushSync } from "react-dom";
|
|
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
import { ValueType } from "realgrid";
|
|
import type { RealGridReact } from "realgrid-react";
|
|
|
|
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate, useSearchConditionSidoList } from "@/shared/lib";
|
|
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
|
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
|
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
|
import { nullableTrimmedString } from "@/components/util/input/nullableString";
|
|
import { statisticsInputExcretaTreatmentFacilityApi } from "@/entities/statistics/api/input/statistics-input-excreta-treatment-facility-service";
|
|
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
|
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
|
import {
|
|
EXCRETA_TREATMENT_FACILITY_STATS_CD,
|
|
type ExcretaTreatmentFacilityItem,
|
|
type ExcretaTreatmentFacilityRequestParams,
|
|
type ExcretaTreatmentFacilitySaveItem,
|
|
type ExcretaTreatmentFacilityVerificationResult,
|
|
} from "@/entities/statistics/model/input/statistics-input-excreta-treatment-facility-interface";
|
|
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
|
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
|
import type {
|
|
ApiResponse,
|
|
cancelSubmitRequest,
|
|
ImrStatusResponse,
|
|
SewerageStatusInfo,
|
|
Sgg,
|
|
Sido,
|
|
} from "@/entities/statistics/model/statistics-interface";
|
|
import {
|
|
filterBySelectedAdmdstCd,
|
|
isGugunOkForToolbar,
|
|
isSubmitOkForToolbar,
|
|
resolveNoApplicableForRequest,
|
|
toInputSearchRegion,
|
|
} from "@/entities/statistics";
|
|
|
|
import { LoadingWithAnimation } from "@common/components";
|
|
|
|
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
|
|
|
import SubmitDailog from "./SubmitDailog";
|
|
|
|
import AddArticleButton from "@/components/button/AddArticleButton";
|
|
import CancelSubmitButton from "@/components/button/CancelSubmitButton";
|
|
import DeleteArticleButton from "@/components/button/DeleteArticleButton";
|
|
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
|
import NotApplicableCheckButton from "@/components/button/NotApplicableCheckButton";
|
|
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 { openVerificationResultDialogIfNeeded } from "@/components/module/post-submit-verification-results";
|
|
import SearchForm from "@/components/module/SearchForm";
|
|
import { VerificationResultDialog } from "@/components/module/VerificationResultDialog";
|
|
import { VerificationResultHistoryDialog } from "@/components/module/VerificationResultHistoryDialog";
|
|
import type {
|
|
CellStyleCallbackParams,
|
|
CellTooltipCallbackParams,
|
|
ColumnConfig,
|
|
} from "@/components/type/DataGrid.type";
|
|
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
|
import { ExcretaTreatFactInpColumn } from "@/components/util/column/input/ExcretaTreatFactInpColumn";
|
|
import { toGridRows } from "@/components/util/grid/gridFunction";
|
|
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
|
import { toExcelExportSearchContextFromSearchForm } from "@/components/util/gridExcelExport";
|
|
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
|
import type { VerificationResultLikeMap } from "@/components/util/input/verification-result-display";
|
|
import { useAdminStore } from "@/store/admin-store";
|
|
import { useTabStore } from "@/store/tab-store";
|
|
|
|
type GridRowWithVerification = Record<string, string | number> & {
|
|
verificationResult?: ExcretaTreatmentFacilityVerificationResult;
|
|
};
|
|
|
|
type TabToolbarSnapshot = {
|
|
showSaveInToolbar: boolean;
|
|
showEnterableToolbarButtons: boolean;
|
|
showActionToolbarCard: boolean;
|
|
};
|
|
|
|
const computeTabEnterable = (
|
|
statusInfo: SewerageStatusInfo | null | undefined,
|
|
periodEnterable: boolean,
|
|
): boolean => {
|
|
if (statusInfo?.lclgvSbmsnSttsCd) {
|
|
if (["2", "3"].includes(statusInfo.lclgvSbmsnSttsCd)) {
|
|
return false;
|
|
}
|
|
return periodEnterable;
|
|
}
|
|
return periodEnterable;
|
|
};
|
|
|
|
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);
|
|
}
|
|
|
|
function buildNoApplicableForRequest(
|
|
values: SearchFormValues | null,
|
|
checked: boolean,
|
|
isSaveListEmpty: boolean,
|
|
): boolean {
|
|
return resolveNoApplicableForRequest(sidoListRef.current, values, checked, isSaveListEmpty);
|
|
}
|
|
|
|
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?.lclgvSbmsnSttsCd === "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 };
|
|
}
|
|
|
|
function buildExcretaTreatmentFacilitySearchParam(
|
|
values: SearchFormValues,
|
|
overrides?: { mode?: string },
|
|
): ExcretaTreatmentFacilityRequestParams {
|
|
const fcltNm = values.fctName?.trim();
|
|
return {
|
|
statsYr: values.year,
|
|
ctpvNm: values.sido,
|
|
...toInputSearchRegion(sidoListRef.current, values),
|
|
...(fcltNm ? { fcltNm } : {}),
|
|
...(overrides?.mode ? { mode: overrides.mode } : {}),
|
|
};
|
|
}
|
|
|
|
function parseSggListFromResponse(res: ApiResponse<Sgg[]>): Sgg[] {
|
|
if (res.status !== "success" || res.data == null) return [];
|
|
return Array.isArray(res.data) ? res.data : [];
|
|
}
|
|
|
|
function buildSggNmColumn(
|
|
col: ColumnConfig,
|
|
canEdit: boolean,
|
|
sggOptions: Sgg[],
|
|
): ColumnConfig {
|
|
if (!canEdit) {
|
|
return { ...col, editable: false as const };
|
|
}
|
|
if (sggOptions.length === 0) {
|
|
return { ...col, editable: true };
|
|
}
|
|
const labels = sggOptions.map((s) => s.gugun);
|
|
return {
|
|
...col,
|
|
editable: true,
|
|
editor: {
|
|
type: "dropdown",
|
|
domainOnly: true,
|
|
textReadOnly: true,
|
|
commitOnSelect: true,
|
|
dropDownWhenClick: true,
|
|
values: labels,
|
|
labels,
|
|
},
|
|
values: labels,
|
|
labels,
|
|
lookupDisplay: true,
|
|
};
|
|
}
|
|
|
|
function mapExcretaTreatmentFacilityListToGridRows(
|
|
list: ExcretaTreatmentFacilityItem[],
|
|
ctpvNm: string,
|
|
): GridRowWithVerification[] {
|
|
return list.map((item) => {
|
|
const row = toGridRows([
|
|
{
|
|
...item,
|
|
ctpvNm,
|
|
cmptnStrtYmd: formatCompactYmdForGrid(item.cmptnStrtYmd),
|
|
oprtngStrtYmd: formatCompactYmdForGrid(item.oprtngStrtYmd),
|
|
},
|
|
])[0] as GridRowWithVerification;
|
|
if (item.verificationResult) {
|
|
row.verificationResult = item.verificationResult;
|
|
}
|
|
return row;
|
|
});
|
|
}
|
|
|
|
function toGridNumber(value: string | number | null | undefined): number {
|
|
const n = Number(value);
|
|
return Number.isFinite(n) ? n : 0;
|
|
}
|
|
|
|
function toApiYmd(value: string | number | Date | null | undefined): string | null {
|
|
return toCompactYmdOrNull(value);
|
|
}
|
|
|
|
function resolveExcretaTreatmentFacilityAdmdst(
|
|
row: Record<string, string | number>,
|
|
sggOptions: Sgg[],
|
|
): { admdstCd: string | null; admdstNm: string | null } {
|
|
const existingCd = nullableTrimmedString(row.admdstCd);
|
|
const existingNm = nullableTrimmedString(row.admdstNm);
|
|
if (existingCd && existingNm) {
|
|
return { admdstCd: existingCd, admdstNm: existingNm };
|
|
}
|
|
const sggNm = String(row.sggNm ?? "").trim();
|
|
const matched = sggOptions.find((s) => s.gugun === sggNm);
|
|
if (matched) {
|
|
return { admdstCd: matched.bsnmCd, admdstNm: matched.gugun };
|
|
}
|
|
return {
|
|
admdstCd: existingCd,
|
|
admdstNm: existingNm ?? nullableTrimmedString(sggNm),
|
|
};
|
|
}
|
|
|
|
/** 그리드 행 → ST00070001 저장/삭제 API 항목 */
|
|
function buildExcretaTreatmentFacilitySaveItem(
|
|
row: Record<string, string | number>,
|
|
sggOptions: Sgg[] = [],
|
|
): ExcretaTreatmentFacilitySaveItem {
|
|
const { admdstCd, admdstNm } = resolveExcretaTreatmentFacilityAdmdst(
|
|
row,
|
|
sggOptions,
|
|
);
|
|
return {
|
|
admdstCd,
|
|
admdstNm,
|
|
fcltSn: nullableTrimmedString(row.fcltSn),
|
|
fcltNm: nullableTrimmedString(row.fcltNm),
|
|
fcltPstnCn: nullableTrimmedString(row.fcltPstnCn),
|
|
telnoCn: nullableTrimmedString(row.telnoCn),
|
|
cmptnStrtYmd: toApiYmd(row.cmptnStrtYmd),
|
|
oprtngStrtYmd: toApiYmd(row.oprtngStrtYmd),
|
|
wholPjtcoAmt: toGridNumber(row.wholPjtcoAmt),
|
|
fcltCpct: toGridNumber(row.fcltCpct),
|
|
excrFcltPrcqty: toGridNumber(row.excrFcltPrcqty),
|
|
fcltOprtngDayCnt: toGridNumber(row.fcltOprtngDayCnt),
|
|
prcsExpln: nullableTrimmedString(row.prcsExpln),
|
|
linkTmplntCn: nullableTrimmedString(row.linkTmplntCn),
|
|
linkPrcsQnt: toGridNumber(row.linkPrcsQnt),
|
|
inflwWqBod: toGridNumber(row.inflwWqBod),
|
|
inflwWqTorgcb: toGridNumber(row.inflwWqTorgcb),
|
|
inflwWqSpsld: toGridNumber(row.inflwWqSpsld),
|
|
inflwWqDtn: toGridNumber(row.inflwWqDtn),
|
|
inflwWqTph: toGridNumber(row.inflwWqTph),
|
|
fcltInflwClfgCnt: toGridNumber(row.fcltInflwClfgCnt),
|
|
dschgWqBod: toGridNumber(row.dschgWqBod),
|
|
dschgWqTorgcb: toGridNumber(row.dschgWqTorgcb),
|
|
dschgWqSpsld: toGridNumber(row.dschgWqSpsld),
|
|
dschgWqDtn: toGridNumber(row.dschgWqDtn),
|
|
dschgWqTph: toGridNumber(row.dschgWqTph),
|
|
fcltDschgClfgCnt: toGridNumber(row.fcltDschgClfgCnt),
|
|
fcltWtsysNm: nullableTrimmedString(row.fcltWtsysNm),
|
|
fcltTrbtrNm: nullableTrimmedString(row.fcltTrbtrNm),
|
|
msirbCn: nullableTrimmedString(row.msirbCn),
|
|
ssirbCn: nullableTrimmedString(row.ssirbCn),
|
|
operCn: nullableTrimmedString(row.operCn),
|
|
wholNope: toGridNumber(row.wholNope),
|
|
sftyAcdntNocs: toGridNumber(row.sftyAcdntNocs),
|
|
rmrk: nullableTrimmedString(row.rmrk),
|
|
};
|
|
}
|
|
|
|
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 createEmptyExcretaTreatmentFacilityRow(
|
|
ctpvNm: string,
|
|
sggNm = "",
|
|
): GridRowWithVerification {
|
|
const row: GridRowWithVerification = { ctpvNm, sggNm };
|
|
for (const col of ExcretaTreatFactInpColumn.columns) {
|
|
const fieldName = col.fieldName;
|
|
if (!fieldName || fieldName === "ctpvNm" || fieldName === "sggNm") continue;
|
|
row[fieldName] = col.valueType === ValueType.NUMBER ? 0 : "";
|
|
}
|
|
return row;
|
|
}
|
|
|
|
const EXCRETA_TREATMENT_FACILITY_NAME_GUIDE_MESSAGE =
|
|
"분뇨처리시설 등록시 분뇨처리시설명이 동일할 경우 같은 데이터로 분류 됩니다.\n\n" +
|
|
"분뇨처리시설명이 동일 할 경우 구분을 위해 분뇨처리시설 1, 분뇨처리시설 2 형태로 구분하여 주시길 바랍니다.";
|
|
|
|
// 하수도 통계 관리 > 07. 분뇨처리시설
|
|
const ExcretaTreatmentFacilityInp = () => {
|
|
const { sidoList } = useSearchConditionSidoList({ isInput: true });
|
|
sidoListRef.current = sidoList;
|
|
const { setMessage: alert } = useAlertStore((state) => state);
|
|
const { confirm } = useConfirm();
|
|
const { isAdminInfo } = useAdminStore();
|
|
const toolbarGroupAllowed = useStatisticsInputToolbarGroupGate();
|
|
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
|
|
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
|
|
const setGlobalProgressLabel = useGlobalProgressStore((state) => state.setLabel);
|
|
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
|
|
|
|
useEffect(() => {
|
|
alert(EXCRETA_TREATMENT_FACILITY_NAME_GUIDE_MESSAGE);
|
|
}, [alert]);
|
|
|
|
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
|
const [isNotApplicable, setIsNotApplicable] = useState(false);
|
|
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
|
|
const [lastSearchedValues, setLastSearchedValues] =
|
|
useState<SearchFormValues | null>(null);
|
|
const [hasSearched, setHasSearched] = useState(false);
|
|
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
|
|
const [statsCd, setStatsCd] = useState("");
|
|
const [isVerificationFailed, setIsVerificationFailed] = useState(false);
|
|
const [footerVerificationResult, setFooterVerificationResult] =
|
|
useState<VerificationResultLikeMap | null>(null);
|
|
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
|
const [columnGuidesByEngCol, setColumnGuidesByEngCol] = useState<
|
|
Record<string, string>
|
|
>({});
|
|
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
|
const [verificationResultDialogOpen, setVerificationResultDialogOpen] =
|
|
useState(false);
|
|
const [showSubmitDialog, setShowSubmitDialog] = useState(false);
|
|
const [deleteList, setDeleteList] = useState<Record<string, string | number>[]>([]);
|
|
|
|
const gridRef = useRef<RealGridReact | null>(null);
|
|
const gridGetRowsRef =
|
|
useRef<(() => Record<string, string | number>[]) | null>(null);
|
|
const sggOptionsSidoRef = useRef<string | null>(null);
|
|
const [searchLoaded, setSearchLoaded] = useState(false);
|
|
|
|
const activeTabId = useTabStore((s) => s.activeTabId);
|
|
|
|
useEffect(() => {
|
|
if (activeTabId !== STATISTICS_INPUT_URL_PATH.EXCRETA_TREATMENT_FACILITY) return;
|
|
let cancelled = false;
|
|
void (async () => {
|
|
try {
|
|
const res = await statisticsApi.getColumnGuides(
|
|
EXCRETA_TREATMENT_FACILITY_STATS_CD,
|
|
);
|
|
if (cancelled) return;
|
|
if (res.status !== "success" || !Array.isArray(res.data)) {
|
|
setColumnGuidesByEngCol({});
|
|
return;
|
|
}
|
|
const next: Record<string, string> = {};
|
|
for (const item of res.data) {
|
|
const key = item.engColNm?.trim();
|
|
const guide = item.columnGuide?.trim();
|
|
if (key && guide) next[key] = guide;
|
|
}
|
|
setColumnGuidesByEngCol(next);
|
|
} catch {
|
|
if (!cancelled) setColumnGuidesByEngCol({});
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [activeTabId]);
|
|
|
|
const loadSggOptions = useCallback(async (sido: string): Promise<Sgg[]> => {
|
|
if (!sido || sido === "ALL") {
|
|
setSggOptions([]);
|
|
return [];
|
|
}
|
|
try {
|
|
const res = await statisticsApi.getSggAllList(sido);
|
|
const list = parseSggListFromResponse(res);
|
|
setSggOptions(list);
|
|
return list;
|
|
} catch {
|
|
setSggOptions([]);
|
|
return [];
|
|
}
|
|
}, []);
|
|
|
|
const columnInfo = useMemo(() => {
|
|
const enterable =
|
|
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
|
const rawColumns = ExcretaTreatFactInpColumn.columns ?? [];
|
|
const base = enterable
|
|
? rawColumns
|
|
: rawColumns.map((col) => ({ ...col, editable: false as const }));
|
|
|
|
const columns = base
|
|
.map((col: ColumnConfig) => {
|
|
if (col.fieldName === "ctpvNm") {
|
|
return { ...col, editable: false as const };
|
|
}
|
|
if (col.fieldName === "sggNm" && enterable) {
|
|
return buildSggNmColumn(col, true, sggOptions);
|
|
}
|
|
return col;
|
|
})
|
|
.map((col) => {
|
|
const guide = columnGuidesByEngCol[col.fieldName]?.trim();
|
|
return guide ? { ...col, tooltip: guide } : col;
|
|
});
|
|
|
|
return {
|
|
columns,
|
|
columnLayout: ExcretaTreatFactInpColumn.columnLayout ?? [],
|
|
};
|
|
}, [statusInfo, inputPeriodEnterable, isAdminInfo, sggOptions, columnGuidesByEngCol]);
|
|
|
|
const canEditGrid =
|
|
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
|
|
|
const getCurrentRows = (): Record<string, string | number>[] =>
|
|
gridGetRowsRef.current?.() ?? rowData;
|
|
|
|
const commitGrid = () => {
|
|
const gridView = gridRef.current?.gridView as
|
|
| { commit?: (force?: boolean) => void; cancel?: () => void }
|
|
| undefined;
|
|
try {
|
|
gridView?.commit?.(true);
|
|
} catch {
|
|
gridView?.cancel?.();
|
|
}
|
|
};
|
|
|
|
const fetchExcretaTreatmentFacilityList = async (
|
|
values: SearchFormValues,
|
|
overrides?: { mode?: string },
|
|
): Promise<boolean> => {
|
|
const response =
|
|
await statisticsInputExcretaTreatmentFacilityApi.getExcretaTreatmentFacilityList(
|
|
buildExcretaTreatmentFacilitySearchParam(values, overrides),
|
|
);
|
|
|
|
if (response.status !== "success" || !response.data) {
|
|
alert(response.message ?? "데이터 조회 중 오류가 발생했습니다.");
|
|
setSearchLoaded(false);
|
|
return false;
|
|
}
|
|
|
|
const {
|
|
commonInfo,
|
|
statusInfo: nextStatusInfo,
|
|
excretaTreatmentFacilityList,
|
|
} = response.data;
|
|
const list = filterBySelectedAdmdstCd(
|
|
excretaTreatmentFacilityList ?? [],
|
|
sidoListRef.current,
|
|
values,
|
|
);
|
|
const gridRows = mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm);
|
|
|
|
setRowData(gridRows);
|
|
setStatusInfo(nextStatusInfo);
|
|
setStatsCd(commonInfo.statsCd);
|
|
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
|
setFooterVerificationResult(commonInfo.verificationResult ?? null);
|
|
if (overrides?.mode !== "PRE") {
|
|
setIsNotApplicable(gridRows.length === 0);
|
|
} else if (gridRows.length > 0) {
|
|
setIsNotApplicable(false);
|
|
}
|
|
setSearchLoaded(true);
|
|
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 periods = Array.isArray(periodResponse.data)
|
|
? periodResponse.data
|
|
: [];
|
|
setInputPeriodEnterable(isInputPeriodCurrent(periods));
|
|
|
|
setIsLoading(true);
|
|
setHasSearched(true);
|
|
setLastSearchedValues(values);
|
|
|
|
const loadedSgg = await loadSggOptions(values.sido);
|
|
sggOptionsSidoRef.current = values.sido;
|
|
if (loadedSgg.length === 0) {
|
|
alert("시군구 목록을 불러오지 못했습니다.");
|
|
setSearchLoaded(false);
|
|
return;
|
|
}
|
|
|
|
await fetchExcretaTreatmentFacilityList(values);
|
|
} catch {
|
|
alert("데이터 조회 중 오류가 발생했습니다.");
|
|
setRowData([]);
|
|
setStatusInfo(null);
|
|
setSearchLoaded(false);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setDeleteList([]);
|
|
setRowData([]);
|
|
setLastSearchedValues(null);
|
|
setHasSearched(false);
|
|
setStatusInfo(null);
|
|
setStatsCd("");
|
|
setIsVerificationFailed(false);
|
|
setFooterVerificationResult(null);
|
|
setIsNotApplicable(false);
|
|
setInputPeriodEnterable(true);
|
|
setSggOptions([]);
|
|
sggOptionsSidoRef.current = null;
|
|
setSearchLoaded(false);
|
|
};
|
|
|
|
const getLastYearDataFunc = async () => {
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!values?.sido || values.sido === "ALL") {
|
|
alert("시도를 선택해주세요.");
|
|
return;
|
|
}
|
|
if (!hasSearched || !searchLoaded) {
|
|
alert("먼저 데이터를 조회해주세요.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setIsLoading(true);
|
|
await fetchExcretaTreatmentFacilityList(values, { mode: "PRE" });
|
|
} catch {
|
|
alert("데이터 조회 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const openVerificationHistoryDialog = () => {
|
|
const ctx = lastSearchedValues ?? searchValues;
|
|
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
setVerificationHistoryOpen(true);
|
|
};
|
|
|
|
const cnacelSubmitFunc = () => {
|
|
void (async () => {
|
|
try {
|
|
if (!(await confirm("제출된 내용을 취소하시겠습니까?"))) return;
|
|
|
|
if (!lastSearchedValues) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
|
|
const userInfo = await getUserInfo();
|
|
const requestData: cancelSubmitRequest = {
|
|
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
|
ctpvNm: lastSearchedValues.sido ?? "",
|
|
statsYr: lastSearchedValues.year ?? "",
|
|
mdfrUuid: userInfo.userUnqId,
|
|
statsCd: statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD,
|
|
};
|
|
|
|
const response = await statisticsApi.cancelSubmit(requestData);
|
|
|
|
if (response.status === "success") {
|
|
setIsLoading(true);
|
|
await fetchExcretaTreatmentFacilityList(lastSearchedValues);
|
|
alert("제출 취소되었습니다.");
|
|
} else {
|
|
alert(response.message ?? "제출 취소 중 오류가 발생했습니다.");
|
|
}
|
|
} catch {
|
|
alert("제출 취소 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
})();
|
|
};
|
|
|
|
const saveFunc = () => {
|
|
void (async () => {
|
|
if (!lastSearchedValues) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
if (!buildGugunOkForToolbar(lastSearchedValues)) {
|
|
alert("시군구를 선택해주세요.");
|
|
return;
|
|
}
|
|
if (!(await confirm("저장 하시겠습니까?"))) return;
|
|
|
|
commitGrid();
|
|
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
|
const currentData = getCurrentRows();
|
|
const saveList = noApplicable
|
|
? []
|
|
: currentData.map((row) =>
|
|
buildExcretaTreatmentFacilitySaveItem(row, sggOptions),
|
|
);
|
|
const deleteListForSave = deleteList
|
|
.filter((row) => String(row.fcltSn ?? "").trim() !== "")
|
|
.map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions));
|
|
|
|
if (!noApplicable && saveList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
|
alert("해당사항 없음을 체크해주세요.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setIsLoading(true);
|
|
const userInfo = await getUserInfo();
|
|
const response =
|
|
await statisticsInputExcretaTreatmentFacilityApi.saveExcretaTreatmentFacilityList(
|
|
{
|
|
statsYr: lastSearchedValues.year ?? "",
|
|
ctpvNm: lastSearchedValues.sido ?? "",
|
|
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
|
userUuid: userInfo.userUnqId,
|
|
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
|
|
noApplicable,
|
|
saveList,
|
|
deleteList: deleteListForSave,
|
|
},
|
|
);
|
|
|
|
if (response.status === "success") {
|
|
setDeleteList([]);
|
|
await fetchExcretaTreatmentFacilityList(lastSearchedValues);
|
|
alert("저장되었습니다.");
|
|
} else {
|
|
alert(response.message ?? "저장 중 오류가 발생했습니다.");
|
|
}
|
|
} catch {
|
|
alert("저장 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
})();
|
|
};
|
|
|
|
const submitFunc = () => {
|
|
void (async () => {
|
|
try {
|
|
if (!lastSearchedValues) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
if (!buildGugunOkForToolbar(lastSearchedValues)) {
|
|
alert("시군구를 선택해주세요.");
|
|
return;
|
|
}
|
|
|
|
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
|
const currentData = getCurrentRows();
|
|
if (!noApplicable && currentData.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
|
alert("해당사항 없음을 체크해주세요.");
|
|
return;
|
|
}
|
|
|
|
if (await confirm("제출 하시겠습니까?")) {
|
|
setShowSubmitDialog(true);
|
|
}
|
|
} catch {
|
|
alert("제출 중 오류가 발생했습니다.");
|
|
}
|
|
})();
|
|
};
|
|
|
|
const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
|
|
if (!lastSearchedValues) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
if (!buildGugunOkForToolbar(lastSearchedValues)) {
|
|
alert("시군구를 선택해주세요.");
|
|
return;
|
|
}
|
|
|
|
commitGrid();
|
|
const noApplicable = buildNoApplicableForRequest(lastSearchedValues, isNotApplicable, getCurrentRows().length === 0);
|
|
const currentData = getCurrentRows();
|
|
const submitList = noApplicable
|
|
? []
|
|
: currentData.map((row) =>
|
|
buildExcretaTreatmentFacilitySaveItem(row, sggOptions),
|
|
);
|
|
const deleteListForSubmit = deleteList
|
|
.filter((row) => String(row.fcltSn ?? "").trim() !== "")
|
|
.map((row) => buildExcretaTreatmentFacilitySaveItem(row, sggOptions));
|
|
|
|
if (!noApplicable && submitList.length === 0 && buildSubmitOkForToolbar(lastSearchedValues)) {
|
|
alert("해당사항 없음을 체크해주세요.");
|
|
return;
|
|
}
|
|
|
|
const statsCdForSubmit = statsCd || EXCRETA_TREATMENT_FACILITY_STATS_CD;
|
|
const verificationQuery = {
|
|
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
|
ctpvNm: lastSearchedValues.sido ?? "",
|
|
statsYr: lastSearchedValues.year ?? "",
|
|
statsCd: statsCdForSubmit,
|
|
};
|
|
|
|
const reloadAfterSubmit = async () => {
|
|
setDeleteList([]);
|
|
await fetchExcretaTreatmentFacilityList(lastSearchedValues);
|
|
};
|
|
|
|
try {
|
|
showGlobalProgress({ label: "서버에 요청 중...", progress: 0 });
|
|
const userInfo = await getUserInfo();
|
|
setGlobalProgressLabel("일반 검증 진행 중...");
|
|
const response =
|
|
await statisticsInputExcretaTreatmentFacilityApi.submitExcretaTreatmentFacilityList(
|
|
{
|
|
statsYr: lastSearchedValues.year ?? "",
|
|
ctpvNm: lastSearchedValues.sido ?? "",
|
|
...toInputSearchRegion(sidoListRef.current, lastSearchedValues),
|
|
userUuid: userInfo.userUnqId,
|
|
lclgvPicNm: submitInfo.lclgvPicNm,
|
|
lclgvPicTelnoCn: submitInfo.lclgvPicTelnoCn,
|
|
lclgvKbrdrDeptNm: submitInfo.lclgvKbrdrDeptNm,
|
|
lclgvKbrdrJbgdNm: submitInfo.lclgvKbrdrJbgdNm,
|
|
lclgvIdfrNm: submitInfo.lclgvIdfrNm,
|
|
lclgvIdfrTelnoCn: submitInfo.lclgvIdfrTelnoCn,
|
|
lclgvIdfrDeptNm: submitInfo.lclgvIdfrDeptNm,
|
|
lclgvIdfrJbgdNm: submitInfo.lclgvIdfrJbgdNm,
|
|
noApplicable,
|
|
submitList,
|
|
deleteList: deleteListForSubmit,
|
|
},
|
|
);
|
|
|
|
if (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: lastSearchedValues?.bsnmCd ?? "",
|
|
stats_cd: statsCdForSubmit,
|
|
stats_yr: lastSearchedValues.year ?? "",
|
|
lae_sbmsn_stts_cd:
|
|
statusInfo?.lclgvSbmsnSttsCd === "4" ? "4" : "2",
|
|
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
|
hb_pass_yn: response.data?.isHbPassed,
|
|
};
|
|
|
|
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 reloadAfterSubmit();
|
|
await openVerificationResultDialogIfNeeded(
|
|
verificationQuery,
|
|
setVerificationResultDialogOpen,
|
|
{ notify: alert },
|
|
);
|
|
} catch (e) {
|
|
await reloadAfterSubmit();
|
|
await openVerificationResultDialogIfNeeded(
|
|
verificationQuery,
|
|
setVerificationResultDialogOpen,
|
|
{ notify: alert, notifySubmitComplete: false },
|
|
);
|
|
const msg =
|
|
e instanceof Error && e.message
|
|
? e.message
|
|
: "IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.";
|
|
alert(msg);
|
|
}
|
|
} else {
|
|
setGlobalProgress(100);
|
|
await reloadAfterSubmit();
|
|
await openVerificationResultDialogIfNeeded(
|
|
verificationQuery,
|
|
setVerificationResultDialogOpen,
|
|
{ notify: alert },
|
|
);
|
|
}
|
|
} catch {
|
|
alert("데이터 제출 중 오류가 발생했습니다.");
|
|
} finally {
|
|
hideGlobalProgress();
|
|
}
|
|
};
|
|
|
|
const addArticleFunc = async () => {
|
|
const ctx = lastSearchedValues ?? searchValues;
|
|
if (!hasSearched || !ctx?.sido || ctx.sido === "ALL") {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
|
|
const sggNm = ctx.gugun && ctx.gugun !== "ALL" ? ctx.gugun : "";
|
|
|
|
let list = sggOptions;
|
|
if (sggOptionsSidoRef.current !== ctx.sido || list.length === 0) {
|
|
list = await loadSggOptions(ctx.sido);
|
|
sggOptionsSidoRef.current = ctx.sido;
|
|
}
|
|
if (list.length === 0) {
|
|
alert("시군구 목록을 불러오지 못했습니다.");
|
|
return;
|
|
}
|
|
|
|
commitGrid();
|
|
const currentRows = getCurrentRows();
|
|
const newRow = createEmptyExcretaTreatmentFacilityRow(ctx.sido, sggNm);
|
|
|
|
flushSync(() => {
|
|
setSggOptions(list);
|
|
setRowData([...currentRows, newRow]);
|
|
setIsNotApplicable(false);
|
|
});
|
|
};
|
|
|
|
const deleteArticleFunc = () => {
|
|
commitGrid();
|
|
const currentRows = getCurrentRows();
|
|
if (currentRows.length === 0) {
|
|
alert("삭제할 데이터가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const gridView = gridRef.current?.gridView as
|
|
| {
|
|
getCheckedRows?: (refresh?: boolean) => number[];
|
|
getCheckedItems?: (refresh?: boolean) => number[];
|
|
}
|
|
| undefined;
|
|
|
|
const checkedIndexes =
|
|
gridView?.getCheckedRows?.(false) ??
|
|
gridView?.getCheckedItems?.(false) ??
|
|
[];
|
|
|
|
if (checkedIndexes.length === 0) {
|
|
alert("삭제할 항목을 선택해주세요.");
|
|
return;
|
|
}
|
|
|
|
const indexSet = new Set(
|
|
checkedIndexes.filter(
|
|
(idx) => Number.isInteger(idx) && idx >= 0 && idx < currentRows.length,
|
|
),
|
|
);
|
|
const removedRows = currentRows.filter((_, idx) => indexSet.has(idx));
|
|
if (removedRows.length === 0) {
|
|
alert("삭제할 항목을 선택해주세요.");
|
|
return;
|
|
}
|
|
|
|
setDeleteList((prev) => [...prev, ...removedRows]);
|
|
setRowData(currentRows.filter((_, idx) => !indexSet.has(idx)));
|
|
};
|
|
|
|
const verificationCellStyle = ({
|
|
fieldName,
|
|
row,
|
|
}: CellStyleCallbackParams): string | undefined => {
|
|
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";
|
|
}
|
|
};
|
|
|
|
const verificationCellTooltip = ({
|
|
fieldName,
|
|
row,
|
|
}: CellTooltipCallbackParams): string | undefined => {
|
|
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"
|
|
? "일반검증 - "
|
|
: "모집단 검증 -";
|
|
return `${prefix}${e.rsnExpln}
|
|
(검증 기준값: ${e.cmprCn} / 현재 입력값: ${e.nowCn})`;
|
|
})
|
|
.filter(Boolean)
|
|
.join("\n");
|
|
}
|
|
};
|
|
|
|
const searchContextForToolbar = lastSearchedValues;
|
|
const toolbarSnapshot = computeTabToolbarSnapshot(
|
|
statusInfo,
|
|
rowData.length > 0,
|
|
hasSearched,
|
|
searchContextForToolbar,
|
|
inputPeriodEnterable,
|
|
isAdminInfo,
|
|
);
|
|
|
|
const showSaveInToolbar =
|
|
toolbarSnapshot.showSaveInToolbar && toolbarGroupAllowed;
|
|
const showEnterableToolbarButtons =
|
|
toolbarSnapshot.showEnterableToolbarButtons && toolbarGroupAllowed;
|
|
const showActionToolbarCard =
|
|
toolbarSnapshot.showActionToolbarCard && toolbarGroupAllowed;
|
|
const showArticleToolbarCard =
|
|
hasSearched &&
|
|
toolbarGroupAllowed &&
|
|
(showSaveInToolbar || showEnterableToolbarButtons);
|
|
const tabEnterableForGrid =
|
|
(computeTabEnterable(statusInfo, inputPeriodEnterable) || isAdminInfo) && inputPeriodEnterable;
|
|
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
|
|
const submitOk = buildSubmitOkForToolbar(searchContextForToolbar);
|
|
const showNotApplicable =
|
|
searchLoaded &&
|
|
hasSearched &&
|
|
rowData.length === 0 &&
|
|
computeTabEnterable(statusInfo, inputPeriodEnterable) &&
|
|
submitOk;
|
|
|
|
const verificationQueryContext = (() => {
|
|
const ctx = lastSearchedValues ?? searchValues;
|
|
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd || !statsCd) return null;
|
|
return {
|
|
...toInputSearchRegion(sidoListRef.current, ctx),
|
|
ctpvNm: ctx.sido,
|
|
statsYr: ctx.year,
|
|
statsCd,
|
|
};
|
|
})();
|
|
|
|
const { verificationSearchDefaults, searchFormRemountKey } =
|
|
useStatisticsInputPendingNavigation({
|
|
urlPath: STATISTICS_INPUT_URL_PATH.EXCRETA_TREATMENT_FACILITY,
|
|
onAutoSearch: (values) => handleSearch(values),
|
|
});
|
|
|
|
return (
|
|
<>
|
|
<div className="relative">
|
|
<SearchForm
|
|
isInput
|
|
key={searchFormRemountKey}
|
|
defaultValues={verificationSearchDefaults ?? undefined}
|
|
fctName={true}
|
|
onSubmit={handleSearch}
|
|
onReset={handleReset}
|
|
onValuesChange={setSearchValues}
|
|
/>
|
|
{showActionToolbarCard && (
|
|
<div className="card !py-0">
|
|
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
|
<VerificationResultButton
|
|
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
|
isVerificationFailed={isVerificationFailed}
|
|
/>
|
|
{showEnterableToolbarButtons && (
|
|
<>
|
|
<LastYearDataButton
|
|
getLastYearDataFunc={() => void getLastYearDataFunc()}
|
|
/>
|
|
{submitOk && <SubmitButton submitFunc={submitFunc} />}
|
|
</>
|
|
)}
|
|
{statusInfo?.lclgvSbmsnSttsCd === "2" && submitOk && (
|
|
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
|
|
)}
|
|
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{showArticleToolbarCard && (
|
|
<div className="card !py-0">
|
|
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
|
{showNotApplicable && (
|
|
<NotApplicableCheckButton
|
|
isCheck={isNotApplicable}
|
|
setIsCheck={setIsNotApplicable}
|
|
/>
|
|
)}
|
|
<AddArticleButton addArticleFunc={addArticleFunc} />
|
|
<DeleteArticleButton deleteArticleFunc={deleteArticleFunc} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
{isLoading && (
|
|
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10 [data-theme='black']:bg-transparent">
|
|
<div className="relative size-[5rem]">
|
|
<LoadingWithAnimation color="primary" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
<DataGrid
|
|
key={canEditGrid ? "enterable" : "readonly"}
|
|
getGridRef={gridRef as RefObject<RealGridReact | null>}
|
|
columnInfo={columnInfo}
|
|
rowStartNum={ExcretaTreatFactInpColumn.rowStartNum}
|
|
fixedColumns={ExcretaTreatFactInpColumn.fixedColumns}
|
|
rowData={rowData}
|
|
setRowData={setRowData}
|
|
getGridDataRef={gridGetRowsRef}
|
|
showHeaderCheckbox
|
|
statusInfo={statusInfo}
|
|
isExcelImport={
|
|
searchLoaded && tabEnterableForGrid && gugunOk
|
|
}
|
|
excelImportSewerBzmnCd={(lastSearchedValues ?? searchValues)?.bsnmCd}
|
|
excelExportSearchContext={toExcelExportSearchContextFromSearchForm(lastSearchedValues ?? searchValues)}
|
|
cellStyleCallback={verificationCellStyle}
|
|
footerVerificationResult={footerVerificationResult}
|
|
cellTooltipCallback={verificationCellTooltip}
|
|
/>
|
|
</div>
|
|
<VerificationResultHistoryDialog
|
|
open={verificationHistoryOpen}
|
|
onOpenChange={setVerificationHistoryOpen}
|
|
queryContext={verificationQueryContext}
|
|
onExceptionResetSubmitted={async () => {
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!values) return;
|
|
await fetchExcretaTreatmentFacilityList(values);
|
|
}}
|
|
/>
|
|
<VerificationResultDialog
|
|
open={verificationResultDialogOpen}
|
|
onOpenChange={setVerificationResultDialogOpen}
|
|
queryContext={verificationQueryContext}
|
|
onExceptionSubmitted={async () => {
|
|
const values = lastSearchedValues ?? searchValues;
|
|
if (!values) return;
|
|
await fetchExcretaTreatmentFacilityList(values);
|
|
}}
|
|
/>
|
|
<SubmitDailog
|
|
open={showSubmitDialog}
|
|
onOpenChange={setShowSubmitDialog}
|
|
statusInfo={statusInfo}
|
|
onConfirm={executeSubmit}
|
|
isLoading={isLoading}
|
|
/>
|
|
<ConfirmDialog isOpen={false} />
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ExcretaTreatmentFacilityInp;
|