"use client"; import { type Dispatch, type MutableRefObject, type SetStateAction, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import type { RealGridReact } from "realgrid-react"; import { Button, Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, LoadingWithAnimation, } from "@common/components"; import DataGrid from "@/components/module/DataGrid"; import { VerificationResultHistoryPagination } from "@/components/module/VerificationResultHistoryPagination"; 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"; import { useAlertStore } from "@/shared/lib"; type VerificationResultGridRow = Record; type VerificationMethodCd = "NV" | "IMR" | "HB"; const VERIFICATION_METHOD_CODES: VerificationMethodCd[] = ["NV", "IMR", "HB"]; const VRFC_MTHD_NAME_MAP: Record = { NV: "일반", IMR: "I-MR", HB: "HB", }; export type VerificationExceptionSubmitItem = { vrfcSn: number; prcsOpnnExpln: string; }; type GridViewWithCheckItem = { checkItem?: (itemIndex: number, checked: boolean, exclusive?: boolean) => void; }; function setRowCheckByItemIndex( gridRef: MutableRefObject, itemIndex: number, checked: boolean, ) { const gv = gridRef.current?.gridView as GridViewWithCheckItem | undefined; try { gv?.checkItem?.(itemIndex, checked, false); } catch { /* RealGrid 체크 API 미지원 등 */ } } const PRCS_YN_LABEL: Record = { "0": "미통과", "1": "통과", "2": "예외처리", "3": "반려", }; 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 }, { 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: "prcsYn", fieldName: "prcsYn", header: { text: "처리여부" }, editable: false }, { name: "pbcpAprvYn", fieldName: "pbcpAprvYn", header: { text: "공단승인여부" }, editable: false }, { name: "regDt", fieldName: "regDt", header: { text: "등록일시" }, editable: false }, { name: "mdfcnDt", fieldName: "mdfcnDt", header: { text: "갱신일시" }, editable: false }, { name: "prcsOpnnExpln", fieldName: "prcsOpnnExpln", header: { text: "예외사유" }, editable: true, }, ], columnLayout: [ "vrfcMthdNm", "ctpvNm", "sggNm", "vrfcTrgtNm", "rsnExpln", "nowVlCn", "compVlCn", "prcsYn", "pbcpAprvYn", "regDt", "mdfcnDt", "prcsOpnnExpln", ], }; export interface VerificationResultDialogQueryContext { sewerBzmnCd: string; ctpvNm: string; sggNm: string; statsYr: string; statsCd: string; } export interface VerificationResultDialogProps { open: boolean; onOpenChange: (open: boolean) => void; queryContext?: VerificationResultDialogQueryContext | null; title?: string; emptyMessage?: string; /** 예외 제출 API 성공 후 부모(입력 화면)에서 메인 데이터를 다시 조회할 때 사용 */ onExceptionSubmitted?: () => void | Promise; } export function VerificationResultDialog({ open, onOpenChange, queryContext, title = "검증 결과", emptyMessage = "표시할 검증 결과가 없습니다.", onExceptionSubmitted, }: VerificationResultDialogProps) { const stableQueryContext = useMemo((): VerificationResultDialogQueryContext | null => { if (!queryContext) return null; return queryContext; }, [ queryContext?.sewerBzmnCd, queryContext?.ctpvNm, queryContext?.sggNm, queryContext?.statsYr, queryContext?.statsCd, ]); const [pageIndex, setPageIndex] = useState(0); const [pageCount, setPageCount] = useState(1); const [loading, setLoading] = useState(false); const [errorMessage, setErrorMessage] = useState(null); const [rows, setRows] = useState([]); const [checkedIndices, setCheckedIndices] = useState>(() => new Set()); const gridRef = useRef(null); const getGridDataRef = useRef<(() => VerificationResultGridRow[]) | null>(null); const alert = useAlertStore((state) => state.setMessage); const onPrcsOpnnExplnEdited = useCallback((params: CellEditedCallbackParams) => { if (params.fieldName !== "prcsOpnnExpln") return; const text = String(params.newValue ?? ""); const hasValue = text.trim().length > 0; setRowCheckByItemIndex(gridRef, params.itemIndex, hasValue); setCheckedIndices((prev) => { const next = new Set(prev); if (hasValue) next.add(params.itemIndex); else next.delete(params.itemIndex); return next; }); }, []); const resetState = useCallback(() => { setPageIndex(0); setPageCount(1); setLoading(false); setErrorMessage(null); setRows([]); setCheckedIndices(new Set()); }, []); 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: VerificationMethodCd, page: number) => { if (!stableQueryContext) return null; const param: VerificationResultListRequest = { sewerBzmnCd: stableQueryContext.sewerBzmnCd, ctpvNm: stableQueryContext.ctpvNm, sggNm: stableQueryContext.sggNm, statsYr: stableQueryContext.statsYr, statsCd: stableQueryContext.statsCd, pbcpAprvYn: "N", vrfcMthdCd, prcsYnList: "0", page: page + 1, size: 10000, }; return statisticsApi.getVerificationResultList(param); }, [stableQueryContext], ); const loadPage = useCallback( async (nextPageIndex: number) => { if (!stableQueryContext) { setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다."); return; } setErrorMessage(null); setLoading(true); try { 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 { setRows([]); setPageCount(1); setErrorMessage("검증 결과 조회 중 오류가 발생했습니다."); } finally { setLoading(false); setCheckedIndices(new Set()); } }, [fetchVerificationResultPage, mapItemToRow, stableQueryContext], ); const loadInitial = useCallback(async () => { await loadPage(0); }, [loadPage]); const handlePageChange = useCallback( async (nextPageIndex: number) => { if (nextPageIndex === pageIndex || !stableQueryContext) return; await loadPage(nextPageIndex); }, [loadPage, pageIndex, stableQueryContext], ); useEffect(() => { if (!open) { resetState(); return; } void loadInitial(); }, [loadInitial, open, resetState]); useEffect(() => { if (loading) return; const id = requestAnimationFrame(() => { setCheckedIndices((prev) => { const next = new Set(prev); rows.forEach((row, itemIndex) => { const has = String(row.prcsOpnnExpln ?? "").trim().length > 0; setRowCheckByItemIndex(gridRef, itemIndex, has); if (has) next.add(itemIndex); }); return next; }); }); return () => cancelAnimationFrame(id); }, [rows, loading]); const handleExceptionSubmitClick = useCallback(async () => { const gridRows = getGridDataRef.current?.() ?? []; const payload: VerificationExceptionSubmitItem[] = []; 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; } if (!stableQueryContext) { alert("조회 조건이 없어 예외 제출을 할 수 없습니다."); return; } try { const userInfo = await getUserInfo(); const response = await statisticsApi.SubmitVerifyException({ sewerBzmnCd: stableQueryContext.sewerBzmnCd, statsYr: stableQueryContext.statsYr, statsCd: stableQueryContext.statsCd, userId: userInfo.userUnqId, exceptionList: payload, }); if (response.status === "success") { alert("예외 제출 처리되었습니다."); try { await onExceptionSubmitted?.(); } catch { alert("예외 제출은 완료되었으나 화면 데이터를 다시 불러오지 못했습니다. 조회로 새로고침 해주세요."); } onOpenChange(false); } else { alert(response.message ?? "예외 제출 처리 중 오류가 발생했습니다."); } } catch { alert("예외 제출 처리 중 오류가 발생했습니다."); } }, [ alert, checkedIndices, rows, onExceptionSubmitted, onOpenChange, stableQueryContext, ]); const titleSuffix = useMemo(() => { if (!stableQueryContext) return ""; const sgg = stableQueryContext.sggNm?.trim() ? ` ${stableQueryContext.sggNm}` : ""; return ` - ${stableQueryContext.statsYr} ${stableQueryContext.ctpvNm}${sgg}`; }, [stableQueryContext]); return ( {title} {titleSuffix}
{errorMessage && (

{errorMessage}

)}
{!loading && rows.length === 0 ? (

{emptyMessage}

) : (
[]> > } 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(), ); }} /> {loading && (
)}
)}
); }