614 lines
24 KiB
TypeScript
614 lines
24 KiB
TypeScript
"use client";
|
|
|
|
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 {
|
|
Button,
|
|
Dialog,
|
|
DialogClose,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
LoadingWithAnimation,
|
|
Tabs,
|
|
TabsContent,
|
|
TabsList,
|
|
TabsTrigger,
|
|
} from "@common/components";
|
|
|
|
import DataGrid from "@/components/module/DataGrid";
|
|
import { cellContentTooltip } from "@/components/module/verificationResultCellTooltip";
|
|
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";
|
|
|
|
const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string }[] = [
|
|
{ value: "NV", label: "일반검증 내역" },
|
|
{ value: "IMR", label: "시계열 검증내역" },
|
|
{ value: "HB", label: "모집단 검증내역" },
|
|
];
|
|
|
|
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;
|
|
const s = String(raw).trim();
|
|
if (s === "") return undefined;
|
|
const n = Number.parseInt(s, 10);
|
|
return Number.isFinite(n) ? n : undefined;
|
|
}
|
|
|
|
/** 예외 제출 취소 가능: 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);
|
|
if (code === 0 || code === 3) return "rg-cell-vrf-prcs-bad";
|
|
return "rg-cell-vrf-prcs-ok";
|
|
}
|
|
|
|
const verificationResultColumnInfo: ColumnInfo = {
|
|
columns: [
|
|
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
|
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
|
|
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
|
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
|
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
|
{ name: "compVlCn", fieldName: "compVlCn", header: { text: "기준값" }, editable: false },
|
|
{ name: "prcsYnNm", fieldName: "prcsYnNm", header: { text: "처리여부" }, editable: false },
|
|
{ name: "stVrfRjtComNm", fieldName: "stVrfRjtComNm", header: { text: "반려상태" }, editable: false },
|
|
{ name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "처리의견" }, editable: false },
|
|
{ name: "rjctOpnnCn", fieldName: "rjctOpnnCn", header: { text: "반려의견" }, editable: false },
|
|
{ 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",
|
|
"admdstNm",
|
|
"vrfcTrgtNm",
|
|
"rsnExpln",
|
|
"nowVlCn",
|
|
"compVlCn",
|
|
"prcsYnNm",
|
|
"stVrfRjtComNm",
|
|
"prcsOpnnExpln",
|
|
"rjctOpnnCn",
|
|
"regDt",
|
|
"mdfcnDt",
|
|
"prcsYn",
|
|
"vrfcSn",
|
|
],
|
|
};
|
|
|
|
export interface VerificationResultHistoryDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
queryContext?: {
|
|
sewerBzmnCd: string;
|
|
ctpvNm: string;
|
|
sggNm: string;
|
|
statsYr: string;
|
|
statsCd: string;
|
|
} | null;
|
|
title?: string;
|
|
emptyMessage?: string;
|
|
/** 예외 제출 취소 API 성공 후 부모(입력 화면)에서 메인 데이터를 다시 조회할 때 사용 */
|
|
onExceptionResetSubmitted?: () => void | Promise<void>;
|
|
}
|
|
|
|
export function VerificationResultHistoryDialog({
|
|
open,
|
|
onOpenChange,
|
|
queryContext,
|
|
title = "검증 결과 내역",
|
|
emptyMessage = "표시할 검증 결과가 없습니다.",
|
|
onExceptionResetSubmitted,
|
|
}: 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 [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 loadedTabKeysRef = useRef<Set<VerificationHistoryTabKey>>(new Set());
|
|
|
|
const resetState = useCallback(() => {
|
|
setActiveTab("NV");
|
|
setEntriesByTab({ NV: [], IMR: [], HB: [] });
|
|
setTabPages(initialTabPageState());
|
|
setLoadingByTab({ NV: false, IMR: false, HB: false });
|
|
setErrorMessage(null);
|
|
setResetSubmitting(false);
|
|
setCheckedVrfcByTab(createEmptyCheckedVrfcByTab());
|
|
loadedTabKeysRef.current.clear();
|
|
}, []);
|
|
|
|
const fetchVerificationResultPage = useCallback(
|
|
async (vrfcMthdCd: VerificationHistoryTabKey, pageIndex: number) => {
|
|
if (!stableQueryContext) return null;
|
|
const param: VerificationResultListRequest = {
|
|
sewerBzmnCd: stableQueryContext.sewerBzmnCd,
|
|
ctpvNm: stableQueryContext.ctpvNm,
|
|
sggNm: stableQueryContext.sggNm,
|
|
statsYr: stableQueryContext.statsYr,
|
|
statsCd: stableQueryContext.statsCd,
|
|
vrfcMthdCd,
|
|
prcsYnList: "0,2",
|
|
rejectCodeList: "0,1,2",
|
|
page: pageIndex + 1,
|
|
size: PAGE_SIZE,
|
|
};
|
|
return statisticsApi.getVerificationResultList(param);
|
|
},
|
|
[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): Promise<boolean> => {
|
|
if (!stableQueryContext) {
|
|
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
|
return false;
|
|
}
|
|
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);
|
|
return true;
|
|
} catch {
|
|
const tabLabel =
|
|
verificationHistoryTabs.find((t) => t.value === tab)?.label ?? tab;
|
|
setErrorMessage(`${tabLabel} 조회 중 오류가 발생했습니다.`);
|
|
return false;
|
|
} finally {
|
|
setLoadingByTab((prev) => ({ ...prev, [tab]: false }));
|
|
}
|
|
},
|
|
[applyPageResponse, fetchVerificationResultPage, stableQueryContext],
|
|
);
|
|
|
|
const loadInitial = useCallback(async () => {
|
|
if (!stableQueryContext) {
|
|
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
|
return;
|
|
}
|
|
setErrorMessage(null);
|
|
setTabPages(initialTabPageState());
|
|
setEntriesByTab({ NV: [], IMR: [], HB: [] });
|
|
loadedTabKeysRef.current.clear();
|
|
const loaded = await loadTabPage("NV", 0);
|
|
if (loaded) {
|
|
loadedTabKeysRef.current.add("NV");
|
|
}
|
|
}, [loadTabPage, stableQueryContext]);
|
|
|
|
const handleActiveTabChange = useCallback(
|
|
(tab: VerificationHistoryTabKey) => {
|
|
setActiveTab(tab);
|
|
if (loadedTabKeysRef.current.has(tab)) return;
|
|
void (async () => {
|
|
const loaded = await loadTabPage(tab, 0);
|
|
if (loaded) {
|
|
loadedTabKeysRef.current.add(tab);
|
|
}
|
|
})();
|
|
},
|
|
[loadTabPage],
|
|
);
|
|
|
|
const handleTabPageChange = useCallback(
|
|
(tab: VerificationHistoryTabKey, pageIndex: number) => {
|
|
if (pageIndex === tabPages[tab].pageIndex) return;
|
|
void loadTabPage(tab, pageIndex);
|
|
},
|
|
[loadTabPage, tabPages],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
resetState();
|
|
return;
|
|
}
|
|
void loadInitial();
|
|
}, [loadInitial, open, resetState]);
|
|
|
|
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 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,
|
|
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
|
vrfcTrgtNm: item.vrfcTrgtNm,
|
|
rsnExpln: item.rsnExpln,
|
|
nowVlCn: item.nowVlCn ?? "-",
|
|
compVlCn: item.compVlCn ?? "-",
|
|
prcsYnNm: item.prcsYnNm ?? "-",
|
|
stVrfRjtComNm: item.stVrfRjtComNm ?? "-",
|
|
prcsOpnnExpln: item.prcsOpnnExpln ?? "-",
|
|
rjctOpnnCn: item.rjctOpnnCn ?? "-",
|
|
prcsYn: item.prcsYn ?? "",
|
|
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 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);
|
|
try {
|
|
await onExceptionResetSubmitted?.();
|
|
} catch {
|
|
alert(
|
|
"예외 제출 취소는 완료되었으나 화면 데이터를 다시 불러오지 못했습니다. 조회로 새로고침 해주세요.",
|
|
);
|
|
}
|
|
} catch {
|
|
alert("예외 제출 취소 처리 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setResetSubmitting(false);
|
|
}
|
|
}, [
|
|
activeTab,
|
|
alert,
|
|
checkedVrfcByTab,
|
|
entriesByTab,
|
|
loadTabPage,
|
|
onExceptionResetSubmitted,
|
|
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 h-[65vh] flex justify-center items-center">{emptyMessage}</p>;
|
|
}
|
|
|
|
return (
|
|
<div className="relative space-y-2">
|
|
<DataGrid
|
|
key={`verification-history-grid-${tab}`}
|
|
columnInfo={verificationResultColumnInfo}
|
|
rowData={rows}
|
|
setRowData={noopSetRows}
|
|
isExcelImport={false}
|
|
isExcelExport={false}
|
|
showHeaderCheckbox
|
|
enableSort={false}
|
|
getGridRef={gridRef}
|
|
cellStyleCallback={verificationPrcsYnNmCellStyle}
|
|
cellTooltipCallback={cellContentTooltip}
|
|
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
|
|
key={`verification-history-pagination-${tab}`}
|
|
pageCount={pageCount}
|
|
pageIndex={pageIndex}
|
|
onPageIndexChange={(nextPage) => handleTabPageChange(tab, nextPage)}
|
|
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>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-[80%]" aria-describedby={undefined}>
|
|
<DialogHeader>
|
|
<DialogTitle>{`${title} - ${queryContext?.statsYr} ${queryContext?.ctpvNm} ${queryContext?.sggNm}`}</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="max-h-[85vh] space-y-5 overflow-auto">
|
|
{errorMessage && (
|
|
<p className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-[1.2rem] text-destructive">
|
|
{errorMessage}
|
|
</p>
|
|
)}
|
|
<Tabs
|
|
value={activeTab}
|
|
onValueChange={(value) => handleActiveTabChange(value as VerificationHistoryTabKey)}
|
|
className="w-full"
|
|
variant="default"
|
|
>
|
|
<TabsList>
|
|
{verificationHistoryTabs.map((tab) => (
|
|
<TabsTrigger key={tab.value} value={tab.value}>
|
|
{tab.label}
|
|
</TabsTrigger>
|
|
))}
|
|
</TabsList>
|
|
{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" disabled={resetSubmitting}>
|
|
닫기
|
|
</Button>
|
|
</DialogClose>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|