624 lines
24 KiB
TypeScript
624 lines
24 KiB
TypeScript
"use client";
|
|
|
|
import { type Dispatch, type MutableRefObject, type SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
|
import { RealGridReact } from "realgrid-react";
|
|
|
|
import { getUserInfo } from "../../../../../packages/common/components/src/api";
|
|
import { useAlertStore } from "@/shared/lib";
|
|
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
|
import type {
|
|
ReviewInputSaveRequest,
|
|
VerificationResultListRequest,
|
|
VerificationResultReportItem,
|
|
} from "@/entities/statistics/model/statistics-interface";
|
|
import {
|
|
Button,
|
|
Dialog,
|
|
DialogClose,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
Label,
|
|
LoadingWithAnimation,
|
|
Tabs,
|
|
TabsContent,
|
|
TabsList,
|
|
TabsTrigger,
|
|
Textarea,
|
|
} from "@common/components";
|
|
|
|
import DataGrid from "@/components/module/DataGrid";
|
|
import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination";
|
|
import type { CellStyleCallbackParams, ColumnInfo } from "@/components/type/DataGrid.type";
|
|
|
|
/** 통계 입력 현황 > 행 클릭 시 사용자예외처리(prcsYn=2) 내역만 조회 */
|
|
export interface ExceptionProcessingHistoryQueryContext {
|
|
sewerBzmnCd: string;
|
|
ctpvNm: string;
|
|
sggNm: string;
|
|
statsYr: string;
|
|
statsCd: string;
|
|
}
|
|
|
|
export interface ExceptionProcessingHistoryDialogProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
queryContext?: ExceptionProcessingHistoryQueryContext | null;
|
|
emptyMessage?: string;
|
|
/** 반려 API 성공 후 (다이얼로그 내 목록 재조회 이후) 상위 화면 등에서 호출 */
|
|
onRejectSuccess?: () => void | Promise<void>;
|
|
}
|
|
|
|
type ExceptionHistoryGridRow = Record<string, string | number>;
|
|
type ExceptionHistoryTabKey = "NV" | "IMR" | "HB";
|
|
|
|
const exceptionHistoryTabs: { value: ExceptionHistoryTabKey; label: string }[] = [
|
|
{ value: "NV", label: "일반검증 내역" },
|
|
{ value: "IMR", label: "I-MR 검증내역" },
|
|
{ value: "HB", label: "HB 검증내역" },
|
|
];
|
|
|
|
const TITLE = "예외처리 내역 조회";
|
|
/** 예외처리 건만 */
|
|
const PRCS_YN_LIST = "2";
|
|
|
|
export type ExceptionRejectSubmitItem = {
|
|
vrfcSn: number;
|
|
statsYr: string;
|
|
sewerBzmnCd: string;
|
|
statsCd: string;
|
|
mdfrId: string;
|
|
rjctOpnnCn?: string;
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
function exceptionPrcsYnNmCellStyle({ fieldName, row }: CellStyleCallbackParams): string | undefined {
|
|
if (fieldName !== "prcsYnNm") return undefined;
|
|
const code = prcsYnCodeToNumber(row.prcsYn);
|
|
if (code === 0 || code === 4) return "rg-cell-vrf-prcs-bad";
|
|
return "rg-cell-vrf-prcs-ok";
|
|
}
|
|
|
|
const exceptionHistoryColumnInfo: 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: "individualReject",
|
|
fieldName: "individualReject",
|
|
header: { text: "개별 반려" },
|
|
editable: false,
|
|
width: 104,
|
|
styleName: "rg-cell-reject-mini-btn",
|
|
},
|
|
{ name: "prcsYn", fieldName: "prcsYn", header: { text: "검증결과상태코드" }, editable: false, visible: false },
|
|
{ name: "vrfcSn", fieldName: "vrfcSn", header: { text: "vrfcSn" }, editable: false, visible: false },
|
|
],
|
|
columnLayout: [
|
|
"ctpvNm",
|
|
"admdstNm",
|
|
"vrfcTrgtNm",
|
|
"rsnExpln",
|
|
"nowVlCn",
|
|
"compVlCn",
|
|
"prcsYnNm",
|
|
"stVrfRjtComNm",
|
|
"prcsOpnnExpln",
|
|
"rjctOpnnCn",
|
|
"regDt",
|
|
"mdfcnDt",
|
|
"individualReject",
|
|
"prcsYn",
|
|
"vrfcSn",
|
|
],
|
|
};
|
|
|
|
export function ExceptionProcessingHistoryDialog({
|
|
open,
|
|
onOpenChange,
|
|
queryContext,
|
|
emptyMessage = "표시할 예외처리 내역이 없습니다.",
|
|
onRejectSuccess,
|
|
}: ExceptionProcessingHistoryDialogProps) {
|
|
const { setMessage: alert } = useAlertStore((state) => state);
|
|
const nvGridRef = useRef<RealGridReact | null>(null);
|
|
const imrGridRef = useRef<RealGridReact | null>(null);
|
|
const hbGridRef = useRef<RealGridReact | null>(null);
|
|
const [rejectReasonDialogOpen, setRejectReasonDialogOpen] = useState(false);
|
|
const [pendingRejectVrfcSns, setPendingRejectVrfcSns] = useState<number[]>([]);
|
|
const [rejectReasonText, setRejectReasonText] = useState("");
|
|
const [rejectSubmitting, setRejectSubmitting] = useState(false);
|
|
|
|
const [activeTab, setActiveTab] = useState<ExceptionHistoryTabKey>("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 [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
|
|
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);
|
|
setErrorMessage(null);
|
|
setRejectReasonDialogOpen(false);
|
|
setPendingRejectVrfcSns([]);
|
|
setRejectReasonText("");
|
|
setRejectSubmitting(false);
|
|
}, []);
|
|
|
|
const fetchPage = useCallback(
|
|
async (vrfcMthdCd: ExceptionHistoryTabKey, pageIndex: number) => {
|
|
if (!queryContext) return null;
|
|
const param: VerificationResultListRequest = {
|
|
sewerBzmnCd: queryContext.sewerBzmnCd,
|
|
ctpvNm: queryContext.ctpvNm,
|
|
sggNm: queryContext.sggNm,
|
|
statsYr: queryContext.statsYr,
|
|
statsCd: queryContext.statsCd,
|
|
vrfcMthdCd,
|
|
prcsYnList: PRCS_YN_LIST,
|
|
rejectCodeList: "0,1,2",
|
|
page: pageIndex + 1,
|
|
size: 30,
|
|
};
|
|
return statisticsApi.getVerificationResultList(param);
|
|
},
|
|
[queryContext],
|
|
);
|
|
|
|
const loadInitial = useCallback(async () => {
|
|
if (!queryContext) {
|
|
setErrorMessage("조회 조건이 없어 예외처리 내역을 불러올 수 없습니다.");
|
|
return;
|
|
}
|
|
setErrorMessage(null);
|
|
setNormalLoading(true);
|
|
setImrLoading(true);
|
|
setHbLoading(true);
|
|
setNormalPageIndex(0);
|
|
setImrPageIndex(0);
|
|
setHbPageIndex(0);
|
|
try {
|
|
const [nvRes, imrRes, hbRes] = await Promise.all([
|
|
fetchPage("NV", 0),
|
|
fetchPage("IMR", 0),
|
|
fetchPage("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);
|
|
} catch {
|
|
setNormalEntries([]);
|
|
setImrEntries([]);
|
|
setHbEntries([]);
|
|
setNormalPageCount(1);
|
|
setImrPageCount(1);
|
|
setHbPageCount(1);
|
|
setErrorMessage("예외처리 내역 조회 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setNormalLoading(false);
|
|
setImrLoading(false);
|
|
setHbLoading(false);
|
|
}
|
|
}, [fetchPage, queryContext]);
|
|
|
|
const handleNormalPageChange = useCallback(
|
|
async (pageIndex: number) => {
|
|
if (pageIndex === normalPageIndex || !queryContext) return;
|
|
setErrorMessage(null);
|
|
setNormalLoading(true);
|
|
try {
|
|
const response = await fetchPage("NV", pageIndex);
|
|
setNormalEntries(response?.data?.verifyResultList ?? []);
|
|
setNormalPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
|
setNormalPageIndex(pageIndex);
|
|
} catch {
|
|
setErrorMessage("일반검증 내역 조회 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setNormalLoading(false);
|
|
}
|
|
},
|
|
[fetchPage, normalPageIndex, queryContext],
|
|
);
|
|
|
|
const handleImrPageChange = useCallback(
|
|
async (pageIndex: number) => {
|
|
if (pageIndex === imrPageIndex || !queryContext) return;
|
|
setErrorMessage(null);
|
|
setImrLoading(true);
|
|
try {
|
|
const response = await fetchPage("IMR", pageIndex);
|
|
setImrEntries(response?.data?.verifyResultList ?? []);
|
|
setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
|
setImrPageIndex(pageIndex);
|
|
} catch {
|
|
setErrorMessage("I-MR 검증내역 조회 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setImrLoading(false);
|
|
}
|
|
},
|
|
[fetchPage, imrPageIndex, queryContext],
|
|
);
|
|
|
|
const handleHbPageChange = useCallback(
|
|
async (pageIndex: number) => {
|
|
if (pageIndex === hbPageIndex || !queryContext) return;
|
|
setErrorMessage(null);
|
|
setHbLoading(true);
|
|
try {
|
|
const response = await fetchPage("HB", pageIndex);
|
|
setHbEntries(response?.data?.verifyResultList ?? []);
|
|
setHbPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
|
setHbPageIndex(pageIndex);
|
|
} catch {
|
|
setErrorMessage("HB 검증내역 조회 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setHbLoading(false);
|
|
}
|
|
},
|
|
[fetchPage, hbPageIndex, queryContext],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
resetState();
|
|
return;
|
|
}
|
|
void loadInitial();
|
|
}, [loadInitial, open, resetState]);
|
|
|
|
const mapRows = (entries: VerificationResultReportItem[]): ExceptionHistoryGridRow[] =>
|
|
entries.map((item) => ({
|
|
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),
|
|
individualReject: "개별 반려",
|
|
vrfcSn: item.vrfcSn,
|
|
}));
|
|
|
|
const normalRows = useMemo(() => mapRows(normalEntries), [normalEntries]);
|
|
const imrRows = useMemo(() => mapRows(imrEntries), [imrEntries]);
|
|
const hbRows = useMemo(() => mapRows(hbEntries), [hbEntries]);
|
|
|
|
const noopSetRows = ((_: SetStateAction<Record<string, string | number>[]>) => {}) as Dispatch<
|
|
SetStateAction<Record<string, string | number>[]>
|
|
>;
|
|
|
|
const handleOpenRejectReasonDialog = useCallback(() => {
|
|
if (!queryContext) {
|
|
alert("조회 조건이 없습니다.");
|
|
return;
|
|
}
|
|
const rows =
|
|
activeTab === "NV" ? normalRows : activeTab === "IMR" ? imrRows : hbRows;
|
|
const gridRef =
|
|
activeTab === "NV" ? nvGridRef : activeTab === "IMR" ? imrGridRef : hbGridRef;
|
|
const gridView = gridRef.current?.gridView;
|
|
if (!gridView) {
|
|
alert("데이터가 존재하지 않습니다.");
|
|
return;
|
|
}
|
|
const checkedIndices = (gridView.getCheckedRows?.(true, false, true) ?? []) as number[];
|
|
if (checkedIndices.length === 0) {
|
|
alert("반려할 항목을 체크해주세요.");
|
|
return;
|
|
}
|
|
const vrfcSns: number[] = [];
|
|
for (const itemIndex of checkedIndices) {
|
|
const row = rows[itemIndex];
|
|
if (!row) continue;
|
|
const vrfcSn = Number(row.vrfcSn);
|
|
if (!Number.isFinite(vrfcSn)) continue;
|
|
vrfcSns.push(vrfcSn);
|
|
}
|
|
if (vrfcSns.length === 0) {
|
|
alert("유효한 검증번호가 있는 행을 선택해주세요.");
|
|
return;
|
|
}
|
|
setPendingRejectVrfcSns(vrfcSns);
|
|
setRejectReasonText("");
|
|
setRejectSubmitting(false);
|
|
setRejectReasonDialogOpen(true);
|
|
}, [activeTab, alert, hbRows, imrRows, normalRows, queryContext]);
|
|
|
|
const resetRejectReasonDialog = useCallback(() => {
|
|
setRejectReasonDialogOpen(false);
|
|
setPendingRejectVrfcSns([]);
|
|
setRejectReasonText("");
|
|
setRejectSubmitting(false);
|
|
}, []);
|
|
|
|
const handleRejectReasonDialogOpenChange = useCallback(
|
|
(nextOpen: boolean) => {
|
|
if (!nextOpen) resetRejectReasonDialog();
|
|
},
|
|
[resetRejectReasonDialog],
|
|
);
|
|
|
|
const handleConfirmReject = useCallback(async () => {
|
|
if (!queryContext || pendingRejectVrfcSns.length === 0) {
|
|
alert("반려할 항목이 없습니다. 다시 선택해주세요.");
|
|
resetRejectReasonDialog();
|
|
return;
|
|
}
|
|
let userInfo;
|
|
try {
|
|
userInfo = await getUserInfo();
|
|
} catch {
|
|
alert("사용자 정보를 불러오지 못했습니다.");
|
|
return;
|
|
}
|
|
const mdfrId = userInfo.userUnqId;
|
|
const trimmedReason = rejectReasonText.trim();
|
|
|
|
const items: ReviewInputSaveRequest[] = pendingRejectVrfcSns.map((vrfcSn) => ({
|
|
type: "INPUT",
|
|
statsYr: queryContext.statsYr,
|
|
sewerBzmnCd: queryContext.sewerBzmnCd,
|
|
statsCd: queryContext.statsCd,
|
|
mdfrId,
|
|
vrfcSn: String(vrfcSn),
|
|
...(trimmedReason ? { rjctOpnnCn: trimmedReason } : {}),
|
|
}));
|
|
|
|
setRejectSubmitting(true);
|
|
try {
|
|
const response = await statisticsApi.saveInputReturn(items);
|
|
if (response.resultCode.toLowerCase() !== "success") {
|
|
alert(response.message ?? "반려 처리 중 오류가 발생했습니다.");
|
|
return;
|
|
}
|
|
alert("반려 처리되었습니다.");
|
|
resetRejectReasonDialog();
|
|
await Promise.all([
|
|
loadInitial(),
|
|
onRejectSuccess?.() ?? Promise.resolve(),
|
|
]);
|
|
} catch {
|
|
alert("반려 처리 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setRejectSubmitting(false);
|
|
}
|
|
}, [
|
|
alert,
|
|
loadInitial,
|
|
onRejectSuccess,
|
|
pendingRejectVrfcSns,
|
|
queryContext,
|
|
rejectReasonText,
|
|
resetRejectReasonDialog,
|
|
]);
|
|
|
|
const handleGridRowClick = useCallback(
|
|
(rowData: Record<string, string | number>) => {
|
|
const gridRef =
|
|
activeTab === "NV" ? nvGridRef : activeTab === "IMR" ? imrGridRef : hbGridRef;
|
|
const current = gridRef.current?.gridView?.getCurrent?.() as
|
|
| { fieldName?: string; column?: string }
|
|
| undefined;
|
|
const fieldName = current?.fieldName ?? current?.column;
|
|
if (fieldName !== "individualReject") return;
|
|
const vrfcSn = Number(rowData.vrfcSn);
|
|
if (!Number.isFinite(vrfcSn)) {
|
|
alert("유효한 검증번호가 있는 행을 선택해주세요.");
|
|
return;
|
|
}
|
|
setPendingRejectVrfcSns([vrfcSn]);
|
|
setRejectReasonText("");
|
|
setRejectSubmitting(false);
|
|
setRejectReasonDialogOpen(true);
|
|
},
|
|
[activeTab, alert],
|
|
);
|
|
|
|
const renderGrid = (
|
|
rows: ExceptionHistoryGridRow[],
|
|
pageIndex: number,
|
|
pageCount: number,
|
|
onPageIndexChange: (page: number) => void,
|
|
loading: boolean,
|
|
gridRef: MutableRefObject<RealGridReact | null>,
|
|
) => {
|
|
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
|
|
columnInfo={exceptionHistoryColumnInfo}
|
|
rowData={rows}
|
|
setRowData={noopSetRows}
|
|
isExcelImport={false}
|
|
isExcelExport={false}
|
|
showHeaderCheckbox={true}
|
|
enableSort={false}
|
|
getGridRef={gridRef}
|
|
cellStyleCallback={exceptionPrcsYnNmCellStyle}
|
|
onRowClick={handleGridRowClick}
|
|
/>
|
|
<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>
|
|
);
|
|
};
|
|
|
|
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) => setActiveTab(value as ExceptionHistoryTabKey)}
|
|
className="w-full"
|
|
variant="default"
|
|
>
|
|
<TabsList>
|
|
{exceptionHistoryTabs.map((tab) => (
|
|
<TabsTrigger key={tab.value} value={tab.value}>
|
|
{tab.label}
|
|
</TabsTrigger>
|
|
))}
|
|
</TabsList>
|
|
<TabsContent value="NV" className="rounded-md border border-border">
|
|
{renderGrid(
|
|
normalRows,
|
|
normalPageIndex,
|
|
normalPageCount,
|
|
handleNormalPageChange,
|
|
normalLoading,
|
|
nvGridRef,
|
|
)}
|
|
</TabsContent>
|
|
<TabsContent value="IMR" className="rounded-md border border-border">
|
|
{renderGrid(
|
|
imrRows,
|
|
imrPageIndex,
|
|
imrPageCount,
|
|
handleImrPageChange,
|
|
imrLoading,
|
|
imrGridRef,
|
|
)}
|
|
</TabsContent>
|
|
<TabsContent value="HB" className="rounded-md border border-border">
|
|
{renderGrid(
|
|
hbRows,
|
|
hbPageIndex,
|
|
hbPageCount,
|
|
handleHbPageChange,
|
|
hbLoading,
|
|
hbGridRef,
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
color="red"
|
|
onClick={handleOpenRejectReasonDialog}
|
|
disabled={rejectSubmitting}
|
|
>
|
|
일괄 반려
|
|
</Button>
|
|
<DialogClose asChild>
|
|
<Button type="button" color="gray">
|
|
닫기
|
|
</Button>
|
|
</DialogClose>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<Dialog open={rejectReasonDialogOpen} onOpenChange={handleRejectReasonDialogOpenChange}>
|
|
<DialogContent className="max-w-[56rem]" aria-describedby={undefined}>
|
|
<DialogHeader>
|
|
<DialogTitle>예외처리 반려</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-2 py-2">
|
|
<Label htmlFor="exception-reject-reason" className="text-label">
|
|
반려사유 (선택, 체크한 모든 행에 동일 적용)
|
|
</Label>
|
|
<Textarea
|
|
id="exception-reject-reason"
|
|
value={rejectReasonText}
|
|
onChange={(e) => setRejectReasonText(e.target.value)}
|
|
rows={4}
|
|
className="min-h-[8rem] p-2 !text-xl"
|
|
placeholder='미입력 시 "검증 리포트를 확인하여 주시기 바랍니다." 자동 주입'
|
|
disabled={rejectSubmitting}
|
|
/>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
color="red"
|
|
disabled={rejectSubmitting}
|
|
onClick={() => void handleConfirmReject()}
|
|
>
|
|
반려 확정
|
|
</Button>
|
|
<DialogClose asChild>
|
|
<Button type="button" color="gray" disabled={rejectSubmitting}>
|
|
취소
|
|
</Button>
|
|
</DialogClose>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|