[st] - fix: 오류 수정 및 누락 기능 추가
This commit is contained in:
@@ -20,6 +20,11 @@ import type {
|
||||
} from "../type/DataGrid.type";
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks";
|
||||
import {
|
||||
prepareGridColumnsForExcelExport,
|
||||
restoreGridColumnsAfterExcelExport,
|
||||
shouldPrepareColumnsForExcelExport,
|
||||
} from "@/components/util/gridExcelExport";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
// RealGrid 타입 확장
|
||||
@@ -47,6 +52,23 @@ if (typeof window !== "undefined") {
|
||||
|
||||
RealGrid.setLicenseKey(process.env.NEXT_PUBLIC_ST_REALGRID_KEY || "");
|
||||
|
||||
type GridViewWithCheckApi = {
|
||||
checkItem?: (itemIndex: number, checked: boolean, exclusive?: boolean) => void;
|
||||
checkRow?: (dataRow: number, checked: boolean, exclusive?: boolean) => void;
|
||||
checkAll?: (checked: boolean) => void;
|
||||
};
|
||||
|
||||
function isRowCheckedFlag(value: unknown): boolean {
|
||||
return (
|
||||
value === true ||
|
||||
value === 1 ||
|
||||
value === "1" ||
|
||||
value === "Y" ||
|
||||
value === "y" ||
|
||||
value === "true"
|
||||
);
|
||||
}
|
||||
|
||||
/** setDataComparer(field, comparer) - setFields 없이 숫자 정렬용. comparer(field, row1, row2) => -1|0|1 */
|
||||
interface RealGridDataProviderComparer {
|
||||
setDataComparer?(
|
||||
@@ -411,6 +433,7 @@ const DataGrid = ({
|
||||
const onItemCheckedRef = useRef<typeof onItemChecked>(onItemChecked);
|
||||
const onItemAllCheckedRef =
|
||||
useRef<typeof onItemAllChecked>(onItemAllChecked);
|
||||
const isSyncingChecksFromRowDataRef = useRef(false);
|
||||
columnsRef.current = columns;
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
@@ -547,6 +570,12 @@ const DataGrid = ({
|
||||
gridView.setColumnLayout?.(normalizedLayout);
|
||||
}
|
||||
|
||||
const useExcelColumnVisibilityPrep =
|
||||
shouldPrepareColumnsForExcelExport(columns);
|
||||
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
|
||||
? prepareGridColumnsForExcelExport(gridView, columns)
|
||||
: undefined;
|
||||
|
||||
const exportBaseName = excelExportFileSuffix
|
||||
? `${menuName}_${excelExportFileSuffix}`
|
||||
: menuName;
|
||||
@@ -556,8 +585,14 @@ const DataGrid = ({
|
||||
target: "local",
|
||||
fileName: `${exportBaseName}.xlsx`,
|
||||
footer: "hidden",
|
||||
allColumns: true, // 숨김(visible: false) 컬럼도 엑셀에 포함
|
||||
allColumns: !useExcelColumnVisibilityPrep,
|
||||
done: () => {
|
||||
if (excelExportVisibilitySnapshot) {
|
||||
restoreGridColumnsAfterExcelExport(
|
||||
gridView,
|
||||
excelExportVisibilitySnapshot,
|
||||
);
|
||||
}
|
||||
// 헤더 원복
|
||||
columns.forEach((col) => {
|
||||
if (col.fieldName && gridView.columnByName) {
|
||||
@@ -1278,6 +1313,7 @@ const DataGrid = ({
|
||||
|
||||
if (onItemChecked) {
|
||||
gridView.onItemChecked = (_grid, itemIndex, checked) => {
|
||||
if (isSyncingChecksFromRowDataRef.current) return;
|
||||
onItemCheckedRef.current?.(itemIndex, checked);
|
||||
};
|
||||
} else {
|
||||
@@ -1286,6 +1322,7 @@ const DataGrid = ({
|
||||
|
||||
if (onItemAllChecked) {
|
||||
gridView.onItemAllChecked = (_grid, checked) => {
|
||||
if (isSyncingChecksFromRowDataRef.current) return;
|
||||
onItemAllCheckedRef.current?.(checked);
|
||||
};
|
||||
} else {
|
||||
@@ -1299,6 +1336,45 @@ const DataGrid = ({
|
||||
};
|
||||
}, [onItemChecked, onItemAllChecked]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!gridRef.current?.gridView || !showHeaderCheckbox) return;
|
||||
|
||||
const gridView = gridRef.current.gridView as GridViewWithCheckApi;
|
||||
const dataProvider = gridRef.current.dataProvider as
|
||||
| { setRows?: (rows: Record<string, string | number>[]) => void }
|
||||
| undefined;
|
||||
|
||||
if (dataProvider?.setRows) {
|
||||
dataProvider.setRows(rowData);
|
||||
}
|
||||
|
||||
const hasCheckedColumn = rowData.some((row) =>
|
||||
Object.prototype.hasOwnProperty.call(row, "__checked"),
|
||||
);
|
||||
if (!hasCheckedColumn) return;
|
||||
|
||||
const timer = window.setTimeout(() => {
|
||||
isSyncingChecksFromRowDataRef.current = true;
|
||||
try {
|
||||
gridView.checkAll?.(false);
|
||||
rowData.forEach((row, itemIndex) => {
|
||||
if (!isRowCheckedFlag((row as Record<string, unknown>).__checked)) return;
|
||||
if (gridView.checkItem) {
|
||||
gridView.checkItem(itemIndex, true, false);
|
||||
return;
|
||||
}
|
||||
gridView.checkRow?.(itemIndex, true, false);
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("check sync:", e);
|
||||
} finally {
|
||||
isSyncingChecksFromRowDataRef.current = false;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [rowData, showHeaderCheckbox]);
|
||||
|
||||
useEffect(() => {
|
||||
const gridView = gridRef.current?.gridView;
|
||||
if (!gridView) return;
|
||||
|
||||
@@ -16,6 +16,11 @@ import GridTopActionButton from "./GridTopActionButton";
|
||||
import SplitDataGrid from "./SplitDataGrid";
|
||||
|
||||
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
|
||||
import {
|
||||
prepareGridColumnsForExcelExport,
|
||||
restoreGridColumnsAfterExcelExport,
|
||||
shouldPrepareColumnsForExcelExport,
|
||||
} from "@/components/util/gridExcelExport"
|
||||
import { useTabStore } from "@/store/tab-store"
|
||||
|
||||
// RealGrid 타입 확장
|
||||
@@ -432,13 +437,21 @@ const TabDataGrid = ({
|
||||
gridView.setColumnLayout?.(normalizedLayout);
|
||||
}
|
||||
|
||||
const useExcelColumnVisibilityPrep = shouldPrepareColumnsForExcelExport(columns);
|
||||
const excelExportVisibilitySnapshot = useExcelColumnVisibilityPrep
|
||||
? prepareGridColumnsForExcelExport(gridView, columns)
|
||||
: undefined;
|
||||
|
||||
gridView.exportGrid({
|
||||
type: 'excel',
|
||||
target: 'local',
|
||||
fileName: `${menuName}_${tabLabel}.xlsx`,
|
||||
footer: 'hidden',
|
||||
allColumns: true, // 숨김(visible: false) 컬럼도 엑셀에 포함
|
||||
allColumns: !useExcelColumnVisibilityPrep,
|
||||
done: () => {
|
||||
if (excelExportVisibilitySnapshot) {
|
||||
restoreGridColumnsAfterExcelExport(gridView, excelExportVisibilitySnapshot);
|
||||
}
|
||||
// 헤더 원복
|
||||
columns.forEach((col) => {
|
||||
if (col.fieldName && gridView.columnByName) {
|
||||
|
||||
@@ -23,7 +23,11 @@ import {
|
||||
} from "@common/components";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type { CellEditedCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type {
|
||||
CellEditedCallbackParams,
|
||||
CellStyleCallbackParams,
|
||||
ColumnInfo,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
|
||||
@@ -31,6 +35,16 @@ import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
type VerificationResultGridRow = Record<string, string | number>;
|
||||
|
||||
type VerificationMethodCd = "NV" | "IMR" | "HB";
|
||||
|
||||
const VERIFICATION_METHOD_CODES: VerificationMethodCd[] = ["NV", "IMR", "HB"];
|
||||
|
||||
const VRFC_MTHD_NAME_MAP: Record<VerificationMethodCd, string> = {
|
||||
NV: "일반",
|
||||
IMR: "I-MR",
|
||||
HB: "HB",
|
||||
};
|
||||
|
||||
export type VerificationExceptionSubmitItem = {
|
||||
vrfcSn: number;
|
||||
prcsOpnnExpln: string;
|
||||
@@ -60,9 +74,23 @@ const PRCS_YN_LABEL: Record<string, string> = {
|
||||
"3": "반려",
|
||||
};
|
||||
|
||||
/** History와 동일 + 편집 가능한 처리의견(예외사유, API 필드명 `prcsOpnnExpln`과 동일) */
|
||||
function verificationMthdCellStyle({
|
||||
fieldName,
|
||||
row,
|
||||
}: CellStyleCallbackParams): string | undefined {
|
||||
if (fieldName !== "vrfcMthdNm") return undefined;
|
||||
const code = String(row.vrfcMthdCd ?? "");
|
||||
if (code === "IMR") return "rg-cell-vrf-mthd-imr";
|
||||
if (code === "HB") return "rg-cell-vrf-mthd-hb";
|
||||
if (code === "NV") return "rg-cell-vrf-mthd-nv";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** History와 동일 + 구분(검증유형) + 편집 가능한 처리의견(예외사유) */
|
||||
const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{ name: "vrfcMthdNm", fieldName: "vrfcMthdNm", header: { text: "구분" }, editable: false },
|
||||
{ name: "vrfcMthdCd", fieldName: "vrfcMthdCd", header: { text: "검증유형코드" }, editable: false, visible: false },
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
@@ -81,6 +109,7 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
},
|
||||
],
|
||||
columnLayout: [
|
||||
"vrfcMthdNm",
|
||||
"ctpvNm",
|
||||
"sggNm",
|
||||
"vrfcTrgtNm",
|
||||
@@ -121,7 +150,6 @@ export function VerificationResultDialog({
|
||||
emptyMessage = "표시할 검증 결과가 없습니다.",
|
||||
onExceptionSubmitted,
|
||||
}: VerificationResultDialogProps) {
|
||||
/** 부모가 매 렌더마다 새 객체를 넘기면 queryContext 참조만 바뀌어 loadInitial이 재실행되므로 필드 값 기준으로 안정화 */
|
||||
const stableQueryContext = useMemo((): VerificationResultDialogQueryContext | null => {
|
||||
if (!queryContext) return null;
|
||||
return queryContext;
|
||||
@@ -133,47 +161,24 @@ export function VerificationResultDialog({
|
||||
queryContext?.statsCd,
|
||||
]);
|
||||
|
||||
const [normalPageIndex, setNormalPageIndex] = useState(0);
|
||||
const [imrPageIndex, setImrPageIndex] = useState(0);
|
||||
const [normalPageCount, setNormalPageCount] = useState(1);
|
||||
const [imrPageCount, setImrPageCount] = useState(1);
|
||||
const [normalLoading, setNormalLoading] = useState(false);
|
||||
const [imrLoading, setImrLoading] = useState(false);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageCount, setPageCount] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const [normalRows, setNormalRows] = useState<VerificationResultGridRow[]>([]);
|
||||
const [imrRows, setImrRows] = useState<VerificationResultGridRow[]>([]);
|
||||
const [normalCheckedIndices, setNormalCheckedIndices] = useState<Set<number>>(
|
||||
() => new Set(),
|
||||
);
|
||||
const [imrCheckedIndices, setImrCheckedIndices] = useState<Set<number>>(() => new Set());
|
||||
const [rows, setRows] = useState<VerificationResultGridRow[]>([]);
|
||||
const [checkedIndices, setCheckedIndices] = useState<Set<number>>(() => new Set());
|
||||
|
||||
const normalGridRef = useRef<RealGridReact | null>(null);
|
||||
const imrGridRef = useRef<RealGridReact | null>(null);
|
||||
/** 메인 입력 화면처럼 편집 값은 그리드에 두고, 제출 시에만 스냅샷 조회 → Enter 시 rows 재주입 충돌 방지 */
|
||||
const getNormalGridDataRef = useRef<(() => VerificationResultGridRow[]) | null>(null);
|
||||
const getImrGridDataRef = useRef<(() => VerificationResultGridRow[]) | null>(null);
|
||||
const gridRef = useRef<RealGridReact | null>(null);
|
||||
const getGridDataRef = useRef<(() => VerificationResultGridRow[]) | null>(null);
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const onNormalPrcsOpnnExplnEdited = useCallback((params: CellEditedCallbackParams) => {
|
||||
const onPrcsOpnnExplnEdited = useCallback((params: CellEditedCallbackParams) => {
|
||||
if (params.fieldName !== "prcsOpnnExpln") return;
|
||||
const text = String(params.newValue ?? "");
|
||||
const hasValue = text.trim().length > 0;
|
||||
setRowCheckByItemIndex(normalGridRef, params.itemIndex, hasValue);
|
||||
setNormalCheckedIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (hasValue) next.add(params.itemIndex);
|
||||
else next.delete(params.itemIndex);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onImrPrcsOpnnExplnEdited = useCallback((params: CellEditedCallbackParams) => {
|
||||
if (params.fieldName !== "prcsOpnnExpln") return;
|
||||
const text = String(params.newValue ?? "");
|
||||
const hasValue = text.trim().length > 0;
|
||||
setRowCheckByItemIndex(imrGridRef, params.itemIndex, hasValue);
|
||||
setImrCheckedIndices((prev) => {
|
||||
setRowCheckByItemIndex(gridRef, params.itemIndex, hasValue);
|
||||
setCheckedIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (hasValue) next.add(params.itemIndex);
|
||||
else next.delete(params.itemIndex);
|
||||
@@ -182,38 +187,38 @@ export function VerificationResultDialog({
|
||||
}, []);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setNormalPageIndex(0);
|
||||
setImrPageIndex(0);
|
||||
setNormalPageCount(1);
|
||||
setImrPageCount(1);
|
||||
setNormalLoading(false);
|
||||
setImrLoading(false);
|
||||
setPageIndex(0);
|
||||
setPageCount(1);
|
||||
setLoading(false);
|
||||
setErrorMessage(null);
|
||||
setNormalRows([]);
|
||||
setImrRows([]);
|
||||
setNormalCheckedIndices(new Set());
|
||||
setImrCheckedIndices(new Set());
|
||||
setRows([]);
|
||||
setCheckedIndices(new Set());
|
||||
}, []);
|
||||
|
||||
const mapItemToRow = useCallback((item: VerificationResultReportItem): VerificationResultGridRow => {
|
||||
return {
|
||||
vrfcSn: item.vrfcSn,
|
||||
ctpvNm: item.ctpvNm,
|
||||
sggNm: item.sggNm,
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYn: PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn,
|
||||
pbcpAprvYn: item.pbcpAprvYn,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "",
|
||||
};
|
||||
}, []);
|
||||
const mapItemToRow = useCallback(
|
||||
(item: VerificationResultReportItem, vrfcMthdCd: VerificationMethodCd): VerificationResultGridRow => {
|
||||
return {
|
||||
vrfcSn: item.vrfcSn,
|
||||
vrfcMthdCd,
|
||||
vrfcMthdNm: VRFC_MTHD_NAME_MAP[vrfcMthdCd] ?? item.vrfcMthdNm,
|
||||
ctpvNm: item.ctpvNm,
|
||||
sggNm: item.sggNm,
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYn: PRCS_YN_LABEL[item.prcsYn] ?? item.prcsYn,
|
||||
pbcpAprvYn: item.pbcpAprvYn,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
prcsOpnnExpln: item.prcsOpnnExpln ?? "",
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchVerificationResultPage = useCallback(
|
||||
async (vrfcMthdCd: "NV" | "IMR", pageIndex: number) => {
|
||||
async (vrfcMthdCd: VerificationMethodCd, page: number) => {
|
||||
if (!stableQueryContext) return null;
|
||||
const param: VerificationResultListRequest = {
|
||||
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
|
||||
@@ -224,7 +229,7 @@ export function VerificationResultDialog({
|
||||
pbcpAprvYn: "N",
|
||||
vrfcMthdCd,
|
||||
prcsYnList: "0",
|
||||
page: pageIndex + 1,
|
||||
page: page + 1,
|
||||
size: 10000,
|
||||
};
|
||||
return statisticsApi.getVerificationResultList(param);
|
||||
@@ -232,81 +237,56 @@ export function VerificationResultDialog({
|
||||
[stableQueryContext],
|
||||
);
|
||||
|
||||
const loadInitial = useCallback(async () => {
|
||||
if (!stableQueryContext) {
|
||||
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setNormalLoading(true);
|
||||
setImrLoading(true);
|
||||
setNormalPageIndex(0);
|
||||
setImrPageIndex(0);
|
||||
try {
|
||||
const [nvRes, imrRes] = await Promise.all([
|
||||
fetchVerificationResultPage("NV", 0),
|
||||
fetchVerificationResultPage("IMR", 0),
|
||||
]);
|
||||
const nvList = nvRes?.data?.verifyResultList ?? [];
|
||||
const imrList = imrRes?.data?.verifyResultList ?? [];
|
||||
setNormalRows(nvList.map(mapItemToRow));
|
||||
setImrRows(imrList.map(mapItemToRow));
|
||||
setNormalPageCount(nvRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageCount(imrRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
} catch {
|
||||
setNormalRows([]);
|
||||
setImrRows([]);
|
||||
setNormalPageCount(1);
|
||||
setImrPageCount(1);
|
||||
setErrorMessage("검증 결과 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setNormalLoading(false);
|
||||
setImrLoading(false);
|
||||
setNormalCheckedIndices(new Set());
|
||||
setImrCheckedIndices(new Set());
|
||||
}
|
||||
}, [fetchVerificationResultPage, mapItemToRow, stableQueryContext]);
|
||||
|
||||
const handleNormalPageChange = useCallback(
|
||||
async (pageIndex: number) => {
|
||||
if (pageIndex === normalPageIndex || !stableQueryContext) return;
|
||||
const loadPage = useCallback(
|
||||
async (nextPageIndex: number) => {
|
||||
if (!stableQueryContext) {
|
||||
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setNormalLoading(true);
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetchVerificationResultPage("NV", pageIndex);
|
||||
const list = response?.data?.verifyResultList ?? [];
|
||||
setNormalRows(list.map(mapItemToRow));
|
||||
setNormalPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setNormalPageIndex(pageIndex);
|
||||
const responses = await Promise.all(
|
||||
VERIFICATION_METHOD_CODES.map((code) =>
|
||||
fetchVerificationResultPage(code, nextPageIndex),
|
||||
),
|
||||
);
|
||||
const merged: VerificationResultGridRow[] = [];
|
||||
let maxPageCount = 1;
|
||||
VERIFICATION_METHOD_CODES.forEach((code, index) => {
|
||||
const response = responses[index];
|
||||
const list = response?.data?.verifyResultList ?? [];
|
||||
merged.push(...list.map((item) => mapItemToRow(item, code)));
|
||||
maxPageCount = Math.max(
|
||||
maxPageCount,
|
||||
response?.data?.pageInfo?.totalPages ?? 1,
|
||||
);
|
||||
});
|
||||
setRows(merged);
|
||||
setPageCount(maxPageCount);
|
||||
setPageIndex(nextPageIndex);
|
||||
} catch {
|
||||
setErrorMessage("일반검증 내역 조회 중 오류가 발생했습니다.");
|
||||
setRows([]);
|
||||
setPageCount(1);
|
||||
setErrorMessage("검증 결과 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setNormalLoading(false);
|
||||
setNormalCheckedIndices(new Set());
|
||||
setLoading(false);
|
||||
setCheckedIndices(new Set());
|
||||
}
|
||||
},
|
||||
[fetchVerificationResultPage, mapItemToRow, normalPageIndex, stableQueryContext],
|
||||
[fetchVerificationResultPage, mapItemToRow, stableQueryContext],
|
||||
);
|
||||
|
||||
const handleImrPageChange = useCallback(
|
||||
async (pageIndex: number) => {
|
||||
if (pageIndex === imrPageIndex || !stableQueryContext) return;
|
||||
setErrorMessage(null);
|
||||
setImrLoading(true);
|
||||
try {
|
||||
const response = await fetchVerificationResultPage("IMR", pageIndex);
|
||||
const list = response?.data?.verifyResultList ?? [];
|
||||
setImrRows(list.map(mapItemToRow));
|
||||
setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("I-MR 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setImrLoading(false);
|
||||
setImrCheckedIndices(new Set());
|
||||
}
|
||||
const loadInitial = useCallback(async () => {
|
||||
await loadPage(0);
|
||||
}, [loadPage]);
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
async (nextPageIndex: number) => {
|
||||
if (nextPageIndex === pageIndex || !stableQueryContext) return;
|
||||
await loadPage(nextPageIndex);
|
||||
},
|
||||
[fetchVerificationResultPage, mapItemToRow, imrPageIndex, stableQueryContext],
|
||||
[loadPage, pageIndex, stableQueryContext],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -317,58 +297,32 @@ export function VerificationResultDialog({
|
||||
void loadInitial();
|
||||
}, [loadInitial, open, resetState]);
|
||||
|
||||
/** 조회된 행에 예외사유 텍스트가 있으면 체크바·체크 인덱스와 맞춤 */
|
||||
useEffect(() => {
|
||||
if (normalLoading) return;
|
||||
if (loading) return;
|
||||
const id = requestAnimationFrame(() => {
|
||||
setNormalCheckedIndices((prev) => {
|
||||
setCheckedIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
normalRows.forEach((row, itemIndex) => {
|
||||
rows.forEach((row, itemIndex) => {
|
||||
const has = String(row.prcsOpnnExpln ?? "").trim().length > 0;
|
||||
setRowCheckByItemIndex(normalGridRef, itemIndex, has);
|
||||
setRowCheckByItemIndex(gridRef, itemIndex, has);
|
||||
if (has) next.add(itemIndex);
|
||||
});
|
||||
return next;
|
||||
});
|
||||
});
|
||||
return () => cancelAnimationFrame(id);
|
||||
}, [normalRows, normalLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (imrLoading) return;
|
||||
const id = requestAnimationFrame(() => {
|
||||
setImrCheckedIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
imrRows.forEach((row, itemIndex) => {
|
||||
const has = String(row.prcsOpnnExpln ?? "").trim().length > 0;
|
||||
setRowCheckByItemIndex(imrGridRef, itemIndex, has);
|
||||
if (has) next.add(itemIndex);
|
||||
});
|
||||
return next;
|
||||
});
|
||||
});
|
||||
return () => cancelAnimationFrame(id);
|
||||
}, [imrRows, imrLoading]);
|
||||
}, [rows, loading]);
|
||||
|
||||
const handleExceptionSubmitClick = useCallback(async () => {
|
||||
const nvGrid = getNormalGridDataRef.current?.() ?? [];
|
||||
const imGrid = getImrGridDataRef.current?.() ?? [];
|
||||
const gridRows = getGridDataRef.current?.() ?? [];
|
||||
const payload: VerificationExceptionSubmitItem[] = [];
|
||||
const pushChecked = (
|
||||
indices: Set<number>,
|
||||
gridRows: VerificationResultGridRow[],
|
||||
idRows: VerificationResultGridRow[],
|
||||
) => {
|
||||
indices.forEach((idx) => {
|
||||
const prcsOpnnExpln = String(gridRows[idx]?.prcsOpnnExpln ?? "").trim();
|
||||
if (!prcsOpnnExpln) return;
|
||||
const vrfcSn = Number(idRows[idx]?.vrfcSn);
|
||||
if (!Number.isFinite(vrfcSn)) return;
|
||||
payload.push({ vrfcSn, prcsOpnnExpln });
|
||||
});
|
||||
};
|
||||
pushChecked(normalCheckedIndices, nvGrid, normalRows);
|
||||
pushChecked(imrCheckedIndices, imGrid, imrRows);
|
||||
checkedIndices.forEach((idx) => {
|
||||
const prcsOpnnExpln = String(gridRows[idx]?.prcsOpnnExpln ?? "").trim();
|
||||
if (!prcsOpnnExpln) return;
|
||||
const vrfcSn = Number(rows[idx]?.vrfcSn);
|
||||
if (!Number.isFinite(vrfcSn)) return;
|
||||
payload.push({ vrfcSn, prcsOpnnExpln });
|
||||
});
|
||||
if (payload.length === 0) {
|
||||
alert("작성된 예외 제출 항목이 없습니다.");
|
||||
return;
|
||||
@@ -402,75 +356,13 @@ export function VerificationResultDialog({
|
||||
}
|
||||
}, [
|
||||
alert,
|
||||
imrCheckedIndices,
|
||||
imrRows,
|
||||
normalCheckedIndices,
|
||||
normalRows,
|
||||
checkedIndices,
|
||||
rows,
|
||||
onExceptionSubmitted,
|
||||
onOpenChange,
|
||||
stableQueryContext,
|
||||
]);
|
||||
|
||||
const renderGrid = (
|
||||
rows: VerificationResultGridRow[],
|
||||
setRows: Dispatch<SetStateAction<VerificationResultGridRow[]>>,
|
||||
pageIndex: number,
|
||||
pageCount: number,
|
||||
onPageIndexChange: (page: number) => void,
|
||||
loading: boolean,
|
||||
gridRef: MutableRefObject<RealGridReact | null>,
|
||||
getGridDataRef: MutableRefObject<(() => VerificationResultGridRow[]) | null>,
|
||||
onPrcsOpnnExplnCellEdited: (params: CellEditedCallbackParams) => void,
|
||||
setCheckedIndices: Dispatch<SetStateAction<Set<number>>>,
|
||||
) => {
|
||||
if (!loading && rows.length === 0) {
|
||||
return <p className="p-6 text-center text-[1.3rem] text-muted-foreground">{emptyMessage}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative space-y-2">
|
||||
<DataGrid
|
||||
columnInfo={verificationSubmitResultColumnInfo}
|
||||
rowData={rows}
|
||||
setRowData={setRows as Dispatch<SetStateAction<Record<string, string | number>[]>>}
|
||||
isExcelImport={false}
|
||||
isExcelExport={false}
|
||||
showHeaderCheckbox
|
||||
enableSort={false}
|
||||
getGridRef={gridRef}
|
||||
getGridDataRef={getGridDataRef}
|
||||
onCellEdited={onPrcsOpnnExplnCellEdited}
|
||||
onItemChecked={(itemIndex, checked) => {
|
||||
setCheckedIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (checked) next.add(itemIndex);
|
||||
else next.delete(itemIndex);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
onItemAllChecked={(checked) => {
|
||||
setCheckedIndices(
|
||||
checked ? new Set(rows.map((_, i) => i)) : new Set(),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<VerificationResultHistoryPagination
|
||||
pageCount={Math.max(pageCount, 1)}
|
||||
pageIndex={Math.max(pageIndex, 0)}
|
||||
onPageIndexChange={onPageIndexChange}
|
||||
className="mx-2 mb-2"
|
||||
/>
|
||||
{loading && (
|
||||
<div className="absolute inset-0 z-10 rounded-md [data-theme='black']:bg-transparent bg-white/40 flex items-center justify-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const titleSuffix = useMemo(() => {
|
||||
if (!stableQueryContext) return "";
|
||||
const sgg = stableQueryContext.sggNm?.trim() ? ` ${stableQueryContext.sggNm}` : "";
|
||||
@@ -493,33 +385,56 @@ export function VerificationResultDialog({
|
||||
</p>
|
||||
)}
|
||||
<section className="rounded-md border border-border">
|
||||
<h3 className="border-b border-border px-4 py-2 text-[1.35rem] font-semibold">일반검증 내역</h3>
|
||||
{renderGrid(
|
||||
normalRows,
|
||||
setNormalRows,
|
||||
normalPageIndex,
|
||||
normalPageCount,
|
||||
handleNormalPageChange,
|
||||
normalLoading,
|
||||
normalGridRef,
|
||||
getNormalGridDataRef,
|
||||
onNormalPrcsOpnnExplnEdited,
|
||||
setNormalCheckedIndices,
|
||||
)}
|
||||
</section>
|
||||
<section className="rounded-md border border-border">
|
||||
<h3 className="border-b border-border px-4 py-2 text-[1.35rem] font-semibold">I-MR 검증내역</h3>
|
||||
{renderGrid(
|
||||
imrRows,
|
||||
setImrRows,
|
||||
imrPageIndex,
|
||||
imrPageCount,
|
||||
handleImrPageChange,
|
||||
imrLoading,
|
||||
imrGridRef,
|
||||
getImrGridDataRef,
|
||||
onImrPrcsOpnnExplnEdited,
|
||||
setImrCheckedIndices,
|
||||
{!loading && rows.length === 0 ? (
|
||||
<p className="p-6 text-center text-[1.3rem] text-muted-foreground">
|
||||
{emptyMessage}
|
||||
</p>
|
||||
) : (
|
||||
<div className="relative space-y-2">
|
||||
<DataGrid
|
||||
columnInfo={verificationSubmitResultColumnInfo}
|
||||
rowData={rows}
|
||||
setRowData={
|
||||
setRows as Dispatch<
|
||||
SetStateAction<Record<string, string | number>[]>
|
||||
>
|
||||
}
|
||||
isExcelImport={false}
|
||||
isExcelExport={false}
|
||||
showHeaderCheckbox
|
||||
enableSort={false}
|
||||
getGridRef={gridRef}
|
||||
getGridDataRef={getGridDataRef}
|
||||
onCellEdited={onPrcsOpnnExplnEdited}
|
||||
cellStyleCallback={verificationMthdCellStyle}
|
||||
onItemChecked={(itemIndex, checked) => {
|
||||
setCheckedIndices((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (checked) next.add(itemIndex);
|
||||
else next.delete(itemIndex);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
onItemAllChecked={(checked) => {
|
||||
setCheckedIndices(
|
||||
checked ? new Set(rows.map((_, i) => i)) : new Set(),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<VerificationResultHistoryPagination
|
||||
pageCount={Math.max(pageCount, 1)}
|
||||
pageIndex={Math.max(pageIndex, 0)}
|
||||
onPageIndexChange={handlePageChange}
|
||||
className="mx-2 mb-2"
|
||||
/>
|
||||
{loading && (
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-md bg-white/40 [data-theme='black']:bg-transparent">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { type Dispatch, type SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
type Dispatch,
|
||||
type MutableRefObject,
|
||||
type SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import type { RealGridReact } from "realgrid-react";
|
||||
|
||||
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { VerificationResultListRequest, VerificationResultReportItem } from "@/entities/statistics/model/statistics-interface";
|
||||
import {
|
||||
@@ -22,6 +33,7 @@ import {
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
||||
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
type VerificationResultGridRow = Record<string, string | number>;
|
||||
type VerificationHistoryTabKey = "NV" | "IMR" | "HB";
|
||||
@@ -32,6 +44,24 @@ const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string
|
||||
{ value: "HB", label: "HB 검증내역" },
|
||||
];
|
||||
|
||||
const PAGE_SIZE = 30;
|
||||
|
||||
/** vrfcSn → prcsYn (페이지 이동 후에도 취소 가능 여부 검증용) */
|
||||
const createEmptyCheckedVrfcByTab = (): Record<VerificationHistoryTabKey, Map<number, string>> => ({
|
||||
NV: new Map(),
|
||||
IMR: new Map(),
|
||||
HB: new Map(),
|
||||
});
|
||||
|
||||
const initialTabPageState = (): Record<
|
||||
VerificationHistoryTabKey,
|
||||
{ pageIndex: number; pageCount: number }
|
||||
> => ({
|
||||
NV: { pageIndex: 0, pageCount: 1 },
|
||||
IMR: { pageIndex: 0, pageCount: 1 },
|
||||
HB: { pageIndex: 0, pageCount: 1 },
|
||||
});
|
||||
|
||||
function prcsYnCodeToNumber(raw: string | number | undefined): number | undefined {
|
||||
if (raw === undefined || raw === null) return undefined;
|
||||
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
||||
@@ -41,7 +71,62 @@ function prcsYnCodeToNumber(raw: string | number | undefined): number | undefine
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
/** prcsYn 0·4 → 빨간색, 그 외 → 초록색 (처리여부 컬럼) */
|
||||
/** 예외 제출 취소 가능: prcsYn = 2 (예외처리) */
|
||||
function canExceptionReset(prcsYn: string | number | undefined): boolean {
|
||||
return prcsYnCodeToNumber(prcsYn) === 2;
|
||||
}
|
||||
|
||||
const EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE =
|
||||
"예외 제출 취소는 처리여부가 사용자예외처리인 항목만 선택할 수 있습니다.";
|
||||
|
||||
type GridViewWithCheckApi = {
|
||||
getCheckedRows?: (allItems?: boolean, visibleOnly?: boolean, exclusive?: boolean) => number[];
|
||||
checkItem?: (itemIndex: number, checked: boolean, exclusive?: boolean) => void;
|
||||
};
|
||||
|
||||
function uncheckGridRow(gridRef: MutableRefObject<RealGridReact | null>, itemIndex: number) {
|
||||
try {
|
||||
(gridRef.current?.gridView as GridViewWithCheckApi | undefined)?.checkItem?.(itemIndex, false, false);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function syncCheckedMapFromGrid(
|
||||
entries: VerificationResultReportItem[],
|
||||
gridRef: MutableRefObject<RealGridReact | null>,
|
||||
prevMap: Map<number, string>,
|
||||
): Map<number, string> {
|
||||
const next = new Map(prevMap);
|
||||
for (const entry of entries) {
|
||||
const sn = Number(entry.vrfcSn);
|
||||
if (Number.isFinite(sn)) next.delete(sn);
|
||||
}
|
||||
const indices =
|
||||
(gridRef.current?.gridView as GridViewWithCheckApi | undefined)?.getCheckedRows?.(true, false, true) ??
|
||||
[];
|
||||
for (const itemIndex of indices) {
|
||||
const entry = entries[itemIndex];
|
||||
if (!entry) continue;
|
||||
const sn = Number(entry.vrfcSn);
|
||||
if (!Number.isFinite(sn)) continue;
|
||||
next.set(sn, String(entry.prcsYn ?? ""));
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function selectionHasNonExceptionResetAllowed(
|
||||
map: Map<number, string>,
|
||||
currentEntries: VerificationResultReportItem[],
|
||||
): boolean {
|
||||
for (const [vrfcSn, storedPrcsYn] of map) {
|
||||
const onPage = currentEntries.find((e) => Number(e.vrfcSn) === vrfcSn);
|
||||
const prcsYn = onPage ? onPage.prcsYn : storedPrcsYn;
|
||||
if (!canExceptionReset(prcsYn)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function verificationPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
|
||||
if (fieldName !== "prcsYnNm") return undefined;
|
||||
const code = prcsYnCodeToNumber(row.prcsYn);
|
||||
@@ -62,8 +147,22 @@ const verificationResultColumnInfo: ColumnInfo = {
|
||||
{ name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false },
|
||||
{ name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false },
|
||||
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
||||
{ name: "vrfcSn", fieldName: "vrfcSn", header: { text: "검증일련번호" }, editable: false, visible: false },
|
||||
],
|
||||
columnLayout: [
|
||||
"ctpvNm",
|
||||
"sggNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
"compVlCn",
|
||||
"prcsYnNm",
|
||||
"pbcpAprvYn",
|
||||
"regDt",
|
||||
"mdfcnDt",
|
||||
"prcsYn",
|
||||
"vrfcSn",
|
||||
],
|
||||
columnLayout: ["ctpvNm", "sggNm", "vrfcTrgtNm", "rsnExpln", "nowVlCn", "compVlCn", "prcsYnNm", "pbcpAprvYn", "regDt", "mdfcnDt", "prcsYn"],
|
||||
};
|
||||
|
||||
export interface VerificationResultHistoryDialogProps {
|
||||
@@ -87,152 +186,168 @@ export function VerificationResultHistoryDialog({
|
||||
title = "검증 결과 내역",
|
||||
emptyMessage = "표시할 검증 결과가 없습니다.",
|
||||
}: VerificationResultHistoryDialogProps) {
|
||||
const stableQueryContext = useMemo(() => {
|
||||
if (!queryContext) return null;
|
||||
return queryContext;
|
||||
}, [
|
||||
queryContext?.sewerBzmnCd,
|
||||
queryContext?.ctpvNm,
|
||||
queryContext?.sggNm,
|
||||
queryContext?.statsYr,
|
||||
queryContext?.statsCd,
|
||||
]);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<VerificationHistoryTabKey>("NV");
|
||||
const [normalEntries, setNormalEntries] = useState<VerificationResultReportItem[]>([]);
|
||||
const [imrEntries, setImrEntries] = useState<VerificationResultReportItem[]>([]);
|
||||
const [hbEntries, setHbEntries] = useState<VerificationResultReportItem[]>([]);
|
||||
const [normalPageIndex, setNormalPageIndex] = useState(0);
|
||||
const [imrPageIndex, setImrPageIndex] = useState(0);
|
||||
const [hbPageIndex, setHbPageIndex] = useState(0);
|
||||
const [normalPageCount, setNormalPageCount] = useState(1);
|
||||
const [imrPageCount, setImrPageCount] = useState(1);
|
||||
const [hbPageCount, setHbPageCount] = useState(1);
|
||||
const [normalLoading, setNormalLoading] = useState(false);
|
||||
const [imrLoading, setImrLoading] = useState(false);
|
||||
const [hbLoading, setHbLoading] = useState(false);
|
||||
const [entriesByTab, setEntriesByTab] = useState<
|
||||
Record<VerificationHistoryTabKey, VerificationResultReportItem[]>
|
||||
>({
|
||||
NV: [],
|
||||
IMR: [],
|
||||
HB: [],
|
||||
});
|
||||
const [tabPages, setTabPages] = useState(initialTabPageState);
|
||||
const [loadingByTab, setLoadingByTab] = useState<Record<VerificationHistoryTabKey, boolean>>({
|
||||
NV: false,
|
||||
IMR: false,
|
||||
HB: false,
|
||||
});
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [resetSubmitting, setResetSubmitting] = useState(false);
|
||||
const [checkedVrfcByTab, setCheckedVrfcByTab] = useState(createEmptyCheckedVrfcByTab);
|
||||
|
||||
const nvGridRef = useRef<RealGridReact | null>(null);
|
||||
const imrGridRef = useRef<RealGridReact | null>(null);
|
||||
const hbGridRef = useRef<RealGridReact | null>(null);
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const gridRefByTab: Record<VerificationHistoryTabKey, MutableRefObject<RealGridReact | null>> = {
|
||||
NV: nvGridRef,
|
||||
IMR: imrGridRef,
|
||||
HB: hbGridRef,
|
||||
};
|
||||
const checkedVrfcByTabRef = useRef(checkedVrfcByTab);
|
||||
checkedVrfcByTabRef.current = checkedVrfcByTab;
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setActiveTab("NV");
|
||||
setNormalEntries([]);
|
||||
setImrEntries([]);
|
||||
setHbEntries([]);
|
||||
setNormalPageIndex(0);
|
||||
setImrPageIndex(0);
|
||||
setHbPageIndex(0);
|
||||
setNormalPageCount(1);
|
||||
setImrPageCount(1);
|
||||
setHbPageCount(1);
|
||||
setNormalLoading(false);
|
||||
setImrLoading(false);
|
||||
setHbLoading(false);
|
||||
setEntriesByTab({ NV: [], IMR: [], HB: [] });
|
||||
setTabPages(initialTabPageState());
|
||||
setLoadingByTab({ NV: false, IMR: false, HB: false });
|
||||
setErrorMessage(null);
|
||||
setResetSubmitting(false);
|
||||
setCheckedVrfcByTab(createEmptyCheckedVrfcByTab());
|
||||
}, []);
|
||||
|
||||
const fetchVerificationResultPage = useCallback(
|
||||
async (vrfcMthdCd: VerificationHistoryTabKey, pageIndex: number) => {
|
||||
if (!queryContext) return null;
|
||||
if (!stableQueryContext) return null;
|
||||
const param: VerificationResultListRequest = {
|
||||
sewerBzmnCd: queryContext.sewerBzmnCd,
|
||||
ctpvNm: queryContext.ctpvNm,
|
||||
sggNm: queryContext.sggNm,
|
||||
statsYr: queryContext.statsYr,
|
||||
statsCd: queryContext.statsCd,
|
||||
pbcpAprvYn: 'N',
|
||||
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
|
||||
ctpvNm: stableQueryContext.ctpvNm,
|
||||
sggNm: stableQueryContext.sggNm,
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
pbcpAprvYn: "N",
|
||||
vrfcMthdCd,
|
||||
prcsYnList: "0,2,3",
|
||||
page: pageIndex + 1,
|
||||
size: 30,
|
||||
size: PAGE_SIZE,
|
||||
};
|
||||
return statisticsApi.getVerificationResultList(param);
|
||||
},
|
||||
[queryContext],
|
||||
[stableQueryContext],
|
||||
);
|
||||
|
||||
const applyPageResponse = useCallback(
|
||||
(tab: VerificationHistoryTabKey, pageIndex: number, list: VerificationResultReportItem[], totalPages: number) => {
|
||||
const pageCount = Math.max(totalPages, 1);
|
||||
const clampedIndex = Math.min(Math.max(pageIndex, 0), pageCount - 1);
|
||||
setEntriesByTab((prev) => ({ ...prev, [tab]: list }));
|
||||
setTabPages((prev) => ({
|
||||
...prev,
|
||||
[tab]: { pageIndex: clampedIndex, pageCount },
|
||||
}));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const loadTabPage = useCallback(
|
||||
async (tab: VerificationHistoryTabKey, pageIndex: number) => {
|
||||
if (!stableQueryContext) {
|
||||
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setLoadingByTab((prev) => ({ ...prev, [tab]: true }));
|
||||
try {
|
||||
const response = await fetchVerificationResultPage(tab, pageIndex);
|
||||
const list = response?.data?.verifyResultList ?? [];
|
||||
const totalPages = response?.data?.pageInfo?.totalPages ?? 1;
|
||||
const apiCurrentPage = response?.data?.pageInfo?.currentPage;
|
||||
const resolvedPageIndex =
|
||||
apiCurrentPage != null && Number.isFinite(apiCurrentPage)
|
||||
? Math.max(0, Number(apiCurrentPage) - 1)
|
||||
: pageIndex;
|
||||
applyPageResponse(tab, resolvedPageIndex, list, totalPages);
|
||||
} catch {
|
||||
const tabLabel =
|
||||
verificationHistoryTabs.find((t) => t.value === tab)?.label ?? tab;
|
||||
setErrorMessage(`${tabLabel} 조회 중 오류가 발생했습니다.`);
|
||||
} finally {
|
||||
setLoadingByTab((prev) => ({ ...prev, [tab]: false }));
|
||||
}
|
||||
},
|
||||
[applyPageResponse, fetchVerificationResultPage, stableQueryContext],
|
||||
);
|
||||
|
||||
const loadInitial = useCallback(async () => {
|
||||
if (!queryContext) {
|
||||
if (!stableQueryContext) {
|
||||
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setNormalLoading(true);
|
||||
setImrLoading(true);
|
||||
setHbLoading(true);
|
||||
setNormalPageIndex(0);
|
||||
setImrPageIndex(0);
|
||||
setHbPageIndex(0);
|
||||
setTabPages(initialTabPageState());
|
||||
setLoadingByTab({ NV: true, IMR: true, HB: true });
|
||||
try {
|
||||
const [nvRes, imrRes, hbRes] = await Promise.all([
|
||||
fetchVerificationResultPage("NV", 0),
|
||||
fetchVerificationResultPage("IMR", 0),
|
||||
fetchVerificationResultPage("HB", 0),
|
||||
]);
|
||||
setNormalEntries(nvRes?.data?.verifyResultList ?? []);
|
||||
setImrEntries(imrRes?.data?.verifyResultList ?? []);
|
||||
setHbEntries(hbRes?.data?.verifyResultList ?? []);
|
||||
setNormalPageCount(nvRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageCount(imrRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
setHbPageCount(hbRes?.data?.pageInfo?.totalPages ?? 1);
|
||||
const results = await Promise.all(
|
||||
verificationHistoryTabs.map((tab) =>
|
||||
fetchVerificationResultPage(tab.value, 0),
|
||||
),
|
||||
);
|
||||
setEntriesByTab({
|
||||
NV: results[0]?.data?.verifyResultList ?? [],
|
||||
IMR: results[1]?.data?.verifyResultList ?? [],
|
||||
HB: results[2]?.data?.verifyResultList ?? [],
|
||||
});
|
||||
setTabPages({
|
||||
NV: {
|
||||
pageIndex: 0,
|
||||
pageCount: Math.max(results[0]?.data?.pageInfo?.totalPages ?? 1, 1),
|
||||
},
|
||||
IMR: {
|
||||
pageIndex: 0,
|
||||
pageCount: Math.max(results[1]?.data?.pageInfo?.totalPages ?? 1, 1),
|
||||
},
|
||||
HB: {
|
||||
pageIndex: 0,
|
||||
pageCount: Math.max(results[2]?.data?.pageInfo?.totalPages ?? 1, 1),
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
setNormalEntries([]);
|
||||
setImrEntries([]);
|
||||
setHbEntries([]);
|
||||
setNormalPageCount(1);
|
||||
setImrPageCount(1);
|
||||
setHbPageCount(1);
|
||||
setEntriesByTab({ NV: [], IMR: [], HB: [] });
|
||||
setTabPages(initialTabPageState());
|
||||
setErrorMessage("검증 결과 내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setNormalLoading(false);
|
||||
setImrLoading(false);
|
||||
setHbLoading(false);
|
||||
setLoadingByTab({ NV: false, IMR: false, HB: false });
|
||||
}
|
||||
}, [fetchVerificationResultPage, queryContext]);
|
||||
}, [fetchVerificationResultPage, stableQueryContext]);
|
||||
|
||||
const handleNormalPageChange = useCallback(
|
||||
async (pageIndex: number) => {
|
||||
if (pageIndex === normalPageIndex || !queryContext) return;
|
||||
setErrorMessage(null);
|
||||
setNormalLoading(true);
|
||||
try {
|
||||
const response = await fetchVerificationResultPage("NV", pageIndex);
|
||||
setNormalEntries(response?.data?.verifyResultList ?? []);
|
||||
setNormalPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setNormalPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("일반검증 내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setNormalLoading(false);
|
||||
}
|
||||
const handleTabPageChange = useCallback(
|
||||
(tab: VerificationHistoryTabKey, pageIndex: number) => {
|
||||
if (pageIndex === tabPages[tab].pageIndex) return;
|
||||
void loadTabPage(tab, pageIndex);
|
||||
},
|
||||
[fetchVerificationResultPage, normalPageIndex, queryContext],
|
||||
);
|
||||
|
||||
const handleImrPageChange = useCallback(
|
||||
async (pageIndex: number) => {
|
||||
if (pageIndex === imrPageIndex || !queryContext) return;
|
||||
setErrorMessage(null);
|
||||
setImrLoading(true);
|
||||
try {
|
||||
const response = await fetchVerificationResultPage("IMR", pageIndex);
|
||||
setImrEntries(response?.data?.verifyResultList ?? []);
|
||||
setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("I-MR 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setImrLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchVerificationResultPage, imrPageIndex, queryContext],
|
||||
);
|
||||
|
||||
const handleHbPageChange = useCallback(
|
||||
async (pageIndex: number) => {
|
||||
if (pageIndex === hbPageIndex || !queryContext) return;
|
||||
setErrorMessage(null);
|
||||
setHbLoading(true);
|
||||
try {
|
||||
const response = await fetchVerificationResultPage("HB", pageIndex);
|
||||
setHbEntries(response?.data?.verifyResultList ?? []);
|
||||
setHbPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setHbPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("HB 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setHbLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchVerificationResultPage, hbPageIndex, queryContext],
|
||||
[loadTabPage, tabPages],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -243,36 +358,115 @@ export function VerificationResultHistoryDialog({
|
||||
void loadInitial();
|
||||
}, [loadInitial, open, resetState]);
|
||||
|
||||
const mapRows = (entries: VerificationResultReportItem[]): VerificationResultGridRow[] =>
|
||||
entries.map((item) => ({
|
||||
ctpvNm: item.ctpvNm,
|
||||
sggNm: item.sggNm,
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYnNm: item.prcsYnNm ?? "-",
|
||||
prcsYn: item.prcsYn ?? "",
|
||||
pbcpAprvYn: item.pbcpAprvYn,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
}));
|
||||
const updateCheckedVrfcForTab = useCallback(
|
||||
(tab: VerificationHistoryTabKey, updater: (map: Map<number, string>) => void) => {
|
||||
setCheckedVrfcByTab((prev) => {
|
||||
const nextMap = new Map(prev[tab]);
|
||||
updater(nextMap);
|
||||
return { ...prev, [tab]: nextMap };
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const normalRows = useMemo(() => mapRows(normalEntries), [normalEntries]);
|
||||
const imrRows = useMemo(() => mapRows(imrEntries), [imrEntries]);
|
||||
const hbRows = useMemo(() => mapRows(hbEntries), [hbEntries]);
|
||||
const mapRows = useCallback(
|
||||
(entries: VerificationResultReportItem[], checkedMap: Map<number, string>): VerificationResultGridRow[] =>
|
||||
entries.map((item) => {
|
||||
const vrfcSn = Number(item.vrfcSn);
|
||||
const isChecked = Number.isFinite(vrfcSn) && checkedMap.has(vrfcSn);
|
||||
return {
|
||||
vrfcSn: item.vrfcSn,
|
||||
__checked: isChecked ? 1 : 0,
|
||||
ctpvNm: item.ctpvNm,
|
||||
sggNm: item.sggNm,
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
compVlCn: item.compVlCn ?? "-",
|
||||
prcsYnNm: item.prcsYnNm ?? "-",
|
||||
prcsYn: item.prcsYn ?? "",
|
||||
pbcpAprvYn: item.pbcpAprvYn,
|
||||
regDt: (item.regDt ?? "").slice(0, 16),
|
||||
mdfcnDt: (item.mdfcnDt ?? "").slice(0, 16),
|
||||
};
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
/** 페이지(entries) 변경 시에만 __checked 반영 — 클릭마다 rowData를 바꾸지 않아 체크 UI가 안정적 */
|
||||
const rowsByTab = useMemo(
|
||||
() => {
|
||||
const checked = checkedVrfcByTabRef.current;
|
||||
return {
|
||||
NV: mapRows(entriesByTab.NV, checked.NV),
|
||||
IMR: mapRows(entriesByTab.IMR, checked.IMR),
|
||||
HB: mapRows(entriesByTab.HB, checked.HB),
|
||||
};
|
||||
},
|
||||
[entriesByTab, mapRows],
|
||||
);
|
||||
|
||||
const noopSetRows = ((_: SetStateAction<Record<string, string | number>[]>) => {}) as Dispatch<
|
||||
SetStateAction<Record<string, string | number>[]>
|
||||
>;
|
||||
|
||||
const renderGrid = (
|
||||
rows: VerificationResultGridRow[],
|
||||
pageIndex: number,
|
||||
pageCount: number,
|
||||
onPageIndexChange: (page: number) => void,
|
||||
loading: boolean,
|
||||
) => {
|
||||
const handleExceptionResetClick = useCallback(async () => {
|
||||
if (!stableQueryContext) {
|
||||
alert("조회 조건이 없습니다.");
|
||||
return;
|
||||
}
|
||||
const tab = activeTab;
|
||||
const currentEntries = entriesByTab[tab];
|
||||
const syncedMap = syncCheckedMapFromGrid(currentEntries, gridRefByTab[tab], checkedVrfcByTab[tab]);
|
||||
if (syncedMap.size === 0) {
|
||||
alert("예외 제출 취소할 항목을 체크해주세요.");
|
||||
return;
|
||||
}
|
||||
if (selectionHasNonExceptionResetAllowed(syncedMap, currentEntries)) {
|
||||
alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
|
||||
return;
|
||||
}
|
||||
setCheckedVrfcByTab((prev) => ({ ...prev, [tab]: syncedMap }));
|
||||
const vrfcSnList = Array.from(syncedMap.keys());
|
||||
let mdfrId: string;
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
mdfrId = userInfo.userUnqId;
|
||||
} catch {
|
||||
alert("사용자 정보를 불러오지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
setResetSubmitting(true);
|
||||
setErrorMessage(null);
|
||||
try {
|
||||
const response = await statisticsApi.StatiscicsExceptionReset({
|
||||
statsYr: stableQueryContext.statsYr,
|
||||
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
|
||||
statsCd: stableQueryContext.statsCd,
|
||||
mdfrId,
|
||||
vrfcSnList,
|
||||
});
|
||||
if (response.status !== "success") {
|
||||
alert(response.message ?? "예외 제출 취소 처리 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
alert("예외 제출 취소 처리되었습니다.");
|
||||
setCheckedVrfcByTab((prev) => ({ ...prev, [tab]: new Map() }));
|
||||
await loadTabPage(tab, tabPages[tab].pageIndex);
|
||||
} catch {
|
||||
alert("예외 제출 취소 처리 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setResetSubmitting(false);
|
||||
}
|
||||
}, [activeTab, alert, checkedVrfcByTab, entriesByTab, loadTabPage, stableQueryContext, tabPages]);
|
||||
|
||||
const renderActiveTabPanel = (tab: VerificationHistoryTabKey) => {
|
||||
const entries = entriesByTab[tab];
|
||||
const rows = rowsByTab[tab];
|
||||
const { pageIndex, pageCount } = tabPages[tab];
|
||||
const loading = loadingByTab[tab];
|
||||
const gridRef = gridRefByTab[tab];
|
||||
|
||||
if (!loading && rows.length === 0) {
|
||||
return <p className="p-6 text-center text-[1.3rem] text-muted-foreground">{emptyMessage}</p>;
|
||||
}
|
||||
@@ -280,19 +474,57 @@ export function VerificationResultHistoryDialog({
|
||||
return (
|
||||
<div className="relative space-y-2">
|
||||
<DataGrid
|
||||
key={`verification-history-grid-${tab}`}
|
||||
columnInfo={verificationResultColumnInfo}
|
||||
rowData={rows}
|
||||
setRowData={noopSetRows}
|
||||
isExcelImport={false}
|
||||
isExcelExport={false}
|
||||
showHeaderCheckbox={false}
|
||||
showHeaderCheckbox
|
||||
enableSort={false}
|
||||
getGridRef={gridRef}
|
||||
cellStyleCallback={verificationPrcsYnNmCellStyle}
|
||||
onItemChecked={(itemIndex, checked) => {
|
||||
const entry = entries[itemIndex];
|
||||
if (!entry) return;
|
||||
const vrfcSn = Number(entry.vrfcSn);
|
||||
if (!Number.isFinite(vrfcSn)) return;
|
||||
if (checked && !canExceptionReset(entry.prcsYn)) {
|
||||
alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
|
||||
uncheckGridRow(gridRef, itemIndex);
|
||||
return;
|
||||
}
|
||||
updateCheckedVrfcForTab(tab, (map) => {
|
||||
if (checked) map.set(vrfcSn, String(entry.prcsYn ?? ""));
|
||||
else map.delete(vrfcSn);
|
||||
});
|
||||
}}
|
||||
onItemAllChecked={(checked) => {
|
||||
updateCheckedVrfcForTab(tab, (map) => {
|
||||
entries.forEach((entry, itemIndex) => {
|
||||
const vrfcSn = Number(entry.vrfcSn);
|
||||
if (!Number.isFinite(vrfcSn)) return;
|
||||
if (!checked) {
|
||||
map.delete(vrfcSn);
|
||||
return;
|
||||
}
|
||||
if (canExceptionReset(entry.prcsYn)) {
|
||||
map.set(vrfcSn, String(entry.prcsYn ?? ""));
|
||||
} else {
|
||||
uncheckGridRow(gridRef, itemIndex);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (checked && entries.some((e) => !canExceptionReset(e.prcsYn))) {
|
||||
alert(EXCEPTION_RESET_ONLY_PRCS_YN_2_MESSAGE);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<VerificationResultHistoryPagination
|
||||
pageCount={Math.max(pageCount, 1)}
|
||||
pageIndex={Math.max(pageIndex, 0)}
|
||||
onPageIndexChange={onPageIndexChange}
|
||||
key={`verification-history-pagination-${tab}`}
|
||||
pageCount={pageCount}
|
||||
pageIndex={pageIndex}
|
||||
onPageIndexChange={(nextPage) => handleTabPageChange(tab, nextPage)}
|
||||
className="mx-2 mb-2"
|
||||
/>
|
||||
{loading && (
|
||||
@@ -318,7 +550,12 @@ export function VerificationResultHistoryDialog({
|
||||
{errorMessage}
|
||||
</p>
|
||||
)}
|
||||
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as VerificationHistoryTabKey)} className="w-full" variant="default">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(value) => setActiveTab(value as VerificationHistoryTabKey)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{verificationHistoryTabs.map((tab) => (
|
||||
<TabsTrigger key={tab.value} value={tab.value}>
|
||||
@@ -326,20 +563,28 @@ export function VerificationResultHistoryDialog({
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<TabsContent value="NV" className="rounded-md border border-border">
|
||||
{renderGrid(normalRows, normalPageIndex, normalPageCount, handleNormalPageChange, normalLoading)}
|
||||
</TabsContent>
|
||||
<TabsContent value="IMR" className="rounded-md border border-border">
|
||||
{renderGrid(imrRows, imrPageIndex, imrPageCount, handleImrPageChange, imrLoading)}
|
||||
</TabsContent>
|
||||
<TabsContent value="HB" className="rounded-md border border-border">
|
||||
{renderGrid(hbRows, hbPageIndex, hbPageCount, handleHbPageChange, hbLoading)}
|
||||
</TabsContent>
|
||||
{verificationHistoryTabs.map((tab) => (
|
||||
<TabsContent
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
className="rounded-md border border-border"
|
||||
>
|
||||
{activeTab === tab.value ? renderActiveTabPanel(tab.value) : null}
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
disabled={resetSubmitting || loadingByTab[activeTab]}
|
||||
onClick={() => void handleExceptionResetClick()}
|
||||
>
|
||||
예외 제출 취소
|
||||
</Button>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" color="gray">
|
||||
<Button type="button" color="gray" disabled={resetSubmitting}>
|
||||
닫기
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { VerificationResultListResponse } from "@/entities/statistics/model/statistics-interface";
|
||||
|
||||
import type { VerificationResultDialogQueryContext } from "./VerificationResultDialog";
|
||||
|
||||
const POST_SUBMIT_VRFC_MTHD_CODES = ["NV", "IMR", "HB"] as const;
|
||||
|
||||
export const POST_SUBMIT_COMPLETE_MESSAGE = "제출 완료되었습니다.";
|
||||
|
||||
export type OpenVerificationResultDialogOptions = {
|
||||
notify?: (message: string) => void;
|
||||
/** 미통과 검증 결과가 없을 때 제출 완료 알림 (기본 true, IMR 오류 catch 등에서는 false) */
|
||||
notifySubmitComplete?: boolean;
|
||||
submitCompleteMessage?: string;
|
||||
};
|
||||
|
||||
function responseHasVerificationResults(response: VerificationResultListResponse | null): boolean {
|
||||
const total = response?.data?.pageInfo?.totalElements;
|
||||
if (typeof total === "number" && total > 0) return true;
|
||||
const list = response?.data?.verifyResultList;
|
||||
return Array.isArray(list) && list.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 제출 직후 `VerificationResultDialog`에 표시할 미통과(prcsYn=0) 검증 결과가
|
||||
* 일반검증(NV)·I-MR(IMR)·HB 중 하나라도 있는지 조회합니다.
|
||||
*/
|
||||
export async function hasAnyPostSubmitVerificationResult(
|
||||
ctx: VerificationResultDialogQueryContext,
|
||||
): Promise<boolean> {
|
||||
const base = {
|
||||
sewerBzmnCd: ctx.sewerBzmnCd,
|
||||
ctpvNm: ctx.ctpvNm,
|
||||
sggNm: ctx.sggNm,
|
||||
statsYr: ctx.statsYr,
|
||||
statsCd: ctx.statsCd,
|
||||
pbcpAprvYn: "N" as const,
|
||||
prcsYnList: "0",
|
||||
page: 1,
|
||||
size: 1,
|
||||
};
|
||||
|
||||
const responses = await Promise.all(
|
||||
POST_SUBMIT_VRFC_MTHD_CODES.map((vrfcMthdCd) =>
|
||||
statisticsApi.getVerificationResultList({ ...base, vrfcMthdCd }),
|
||||
),
|
||||
);
|
||||
|
||||
return responses.some(responseHasVerificationResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* 미통과 검증 결과가 있으면 다이얼로그를 열고, 없으면 제출 완료 알림을 표시합니다.
|
||||
* @returns 다이얼로그를 열었으면 true
|
||||
*/
|
||||
export async function openVerificationResultDialogIfNeeded(
|
||||
ctx: VerificationResultDialogQueryContext | null | undefined,
|
||||
setOpen: (open: boolean) => void,
|
||||
options?: OpenVerificationResultDialogOptions,
|
||||
): Promise<boolean> {
|
||||
const {
|
||||
notify,
|
||||
notifySubmitComplete = true,
|
||||
submitCompleteMessage = POST_SUBMIT_COMPLETE_MESSAGE,
|
||||
} = options ?? {};
|
||||
|
||||
const notifyComplete = () => {
|
||||
if (notifySubmitComplete && notify) {
|
||||
notify(submitCompleteMessage);
|
||||
}
|
||||
};
|
||||
|
||||
if (!ctx) {
|
||||
notifyComplete();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (await hasAnyPostSubmitVerificationResult(ctx)) {
|
||||
setOpen(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
notifyComplete();
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user