[ST] - 공단 오류 및 개선 요청사항 처리
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useLayoutEffect, useRef, type MutableRefObject } from "react";
|
||||
import { useEffect, useLayoutEffect, useRef, useState, type MutableRefObject } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import JSZip from "jszip";
|
||||
import { ValueType } from "realgrid";
|
||||
@@ -385,6 +386,20 @@ function toNum(v: unknown): number {
|
||||
return parseFloat(String(v).replace(/,/g, "")) || NaN;
|
||||
}
|
||||
|
||||
/** RealGrid 기본 hover가 안 되는 빈 셀 여부 */
|
||||
function isEmptyGridCellValue(value: unknown): boolean {
|
||||
if (value == null) return true;
|
||||
if (typeof value === "number") return Number.isNaN(value);
|
||||
return String(value).trim() === "";
|
||||
}
|
||||
|
||||
type EmptyCellHoverTooltip = {
|
||||
message: string;
|
||||
x: number;
|
||||
y: number;
|
||||
key: string;
|
||||
};
|
||||
|
||||
/** onSorting: mergeCells: true인 컬럼이 있으면 그룹 내 순서 유지 정렬, 없으면 RealGrid 기본 정렬 */
|
||||
function createOnSortingHandler(
|
||||
columns: {
|
||||
@@ -761,6 +776,7 @@ const DataGrid = ({
|
||||
}: DataGridProps) => {
|
||||
const { columns, columnLayout } = columnInfo;
|
||||
const gridRef = useRef<RealGridReact>(null);
|
||||
const gridHostRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const columnsRef = useRef(columns);
|
||||
const sortDirRef = useRef<Record<string, "asc" | "desc" | "none">>({});
|
||||
@@ -784,6 +800,10 @@ const DataGrid = ({
|
||||
useRef<typeof onItemAllChecked>(onItemAllChecked);
|
||||
const isSyncingChecksFromRowDataRef = useRef(false);
|
||||
const columnsFieldsSigRef = useRef("");
|
||||
const emptyCellHoverKeyRef = useRef("");
|
||||
const emptyCellHoverRafRef = useRef<number | null>(null);
|
||||
const [emptyCellHoverTooltip, setEmptyCellHoverTooltip] =
|
||||
useState<EmptyCellHoverTooltip | null>(null);
|
||||
columnsRef.current = columns;
|
||||
rowDataRef.current = rowData;
|
||||
cellStyleCallbackRef.current = cellStyleCallback;
|
||||
@@ -1758,34 +1778,43 @@ useEffect(() => {
|
||||
const fieldName =
|
||||
cellIndex?.fieldName ?? cellIndex?.column ?? "";
|
||||
// cellTooltipCallback 있으면 해당 셀 메시지로 showMessage (공식 문서: TooltipOptions + CellIndex)
|
||||
// 빈 셀은 hover 오버레이로만 표시하므로 클릭 showMessage는 생략 (이중 표출 방지)
|
||||
if (cellTooltipCallbackRef.current && row && cellIndex) {
|
||||
const msg = cellTooltipCallbackRef.current({
|
||||
row: row as Record<string, string | number>,
|
||||
fieldName,
|
||||
});
|
||||
if (msg) {
|
||||
const colName =
|
||||
columns.find(
|
||||
(c) =>
|
||||
c.fieldName === fieldName ||
|
||||
c.name === cellIndex.column,
|
||||
)?.name ??
|
||||
cellIndex.column ??
|
||||
fieldName;
|
||||
(
|
||||
gridView as {
|
||||
showMessage?: (
|
||||
opts: {
|
||||
message: string;
|
||||
hideDuration?: number;
|
||||
},
|
||||
idx: { itemIndex: number; column: string },
|
||||
) => void;
|
||||
}
|
||||
).showMessage?.(
|
||||
{ message: msg, hideDuration: 5000 },
|
||||
{ itemIndex: cellIndex.itemIndex, column: colName },
|
||||
);
|
||||
if (!isEmptyGridCellValue(row[fieldName])) {
|
||||
const msg = cellTooltipCallbackRef.current({
|
||||
row: row as Record<string, string | number>,
|
||||
fieldName,
|
||||
});
|
||||
if (msg) {
|
||||
const colName =
|
||||
columns.find(
|
||||
(c) =>
|
||||
c.fieldName === fieldName ||
|
||||
c.name === cellIndex.column,
|
||||
)?.name ??
|
||||
cellIndex.column ??
|
||||
fieldName;
|
||||
(
|
||||
gridView as {
|
||||
showMessage?: (
|
||||
opts: {
|
||||
message: string;
|
||||
hideDuration?: number;
|
||||
},
|
||||
idx: {
|
||||
itemIndex: number;
|
||||
column: string;
|
||||
},
|
||||
) => void;
|
||||
}
|
||||
).showMessage?.(
|
||||
{ message: msg, hideDuration: 5000 },
|
||||
{
|
||||
itemIndex: cellIndex.itemIndex,
|
||||
column: colName,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (onDataCellClickRef.current && row !== undefined) {
|
||||
@@ -1807,6 +1836,144 @@ useEffect(() => {
|
||||
};
|
||||
}, [onRowClick, onDataCellClick, cellTooltipCallback, columns]);
|
||||
|
||||
// 빈 셀은 RealGrid hover 툴팁이 안 뜸 → React 오버레이로만 보완 (값 있는 셀은 기존 onShowTooltip)
|
||||
useEffect(() => {
|
||||
if (!cellTooltipCallback) {
|
||||
emptyCellHoverKeyRef.current = "";
|
||||
setEmptyCellHoverTooltip(null);
|
||||
return;
|
||||
}
|
||||
const host = gridHostRef.current;
|
||||
if (!host) return;
|
||||
|
||||
const clearEmptyCellHoverTooltip = () => {
|
||||
if (emptyCellHoverKeyRef.current === "") return;
|
||||
emptyCellHoverKeyRef.current = "";
|
||||
setEmptyCellHoverTooltip(null);
|
||||
};
|
||||
|
||||
const handleMouseMove = (ev: MouseEvent) => {
|
||||
if (emptyCellHoverRafRef.current != null) {
|
||||
cancelAnimationFrame(emptyCellHoverRafRef.current);
|
||||
}
|
||||
emptyCellHoverRafRef.current = requestAnimationFrame(() => {
|
||||
const gridView = gridRef.current?.gridView as
|
||||
| {
|
||||
mouseToIndex?: (
|
||||
x: number,
|
||||
y: number,
|
||||
) => {
|
||||
cellType?: string;
|
||||
itemIndex?: number;
|
||||
column?: string;
|
||||
fieldName?: string;
|
||||
} | null;
|
||||
}
|
||||
| undefined;
|
||||
if (!gridView?.mouseToIndex) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = host.getBoundingClientRect();
|
||||
let index: {
|
||||
cellType?: string;
|
||||
itemIndex?: number;
|
||||
column?: string;
|
||||
fieldName?: string;
|
||||
} | null = null;
|
||||
try {
|
||||
index = gridView.mouseToIndex(
|
||||
ev.clientX - rect.left,
|
||||
ev.clientY - rect.top,
|
||||
);
|
||||
} catch {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!index ||
|
||||
(index.cellType != null && index.cellType !== "data") ||
|
||||
index.itemIndex == null ||
|
||||
index.itemIndex < 0
|
||||
) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
const row = rowDataRef.current[index.itemIndex];
|
||||
if (!row) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
const col = columnsRef.current.find(
|
||||
(c) =>
|
||||
c.name === index.column ||
|
||||
c.fieldName === index.column ||
|
||||
c.name === index.fieldName ||
|
||||
c.fieldName === index.fieldName,
|
||||
);
|
||||
const fieldName =
|
||||
col?.fieldName ?? index.fieldName ?? index.column ?? "";
|
||||
if (!fieldName) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
// 값이 있으면 RealGrid 기본 hover에 맡김
|
||||
if (!isEmptyGridCellValue(row[fieldName])) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = cellTooltipCallbackRef.current?.({
|
||||
row: row as Record<string, string | number>,
|
||||
fieldName,
|
||||
});
|
||||
if (!msg) {
|
||||
clearEmptyCellHoverTooltip();
|
||||
return;
|
||||
}
|
||||
|
||||
// 값 있는 셀 RealGrid 툴팁과 같이: 본문 다음 줄에 (검증 기준값...)이 오도록
|
||||
// 템플릿 리터럴 들여쓰기 공백은 제거
|
||||
const normalizedMessage = msg.replace(/\n[ \t]+/g, "\n");
|
||||
|
||||
const key = `${index.itemIndex}:${fieldName}`;
|
||||
if (emptyCellHoverKeyRef.current === key) return;
|
||||
emptyCellHoverKeyRef.current = key;
|
||||
setEmptyCellHoverTooltip({
|
||||
message: normalizedMessage,
|
||||
x: ev.clientX + 12,
|
||||
y: ev.clientY + 16,
|
||||
key,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (emptyCellHoverRafRef.current != null) {
|
||||
cancelAnimationFrame(emptyCellHoverRafRef.current);
|
||||
emptyCellHoverRafRef.current = null;
|
||||
}
|
||||
clearEmptyCellHoverTooltip();
|
||||
};
|
||||
|
||||
host.addEventListener("mousemove", handleMouseMove);
|
||||
host.addEventListener("mouseleave", handleMouseLeave);
|
||||
return () => {
|
||||
host.removeEventListener("mousemove", handleMouseMove);
|
||||
host.removeEventListener("mouseleave", handleMouseLeave);
|
||||
if (emptyCellHoverRafRef.current != null) {
|
||||
cancelAnimationFrame(emptyCellHoverRafRef.current);
|
||||
emptyCellHoverRafRef.current = null;
|
||||
}
|
||||
clearEmptyCellHoverTooltip();
|
||||
};
|
||||
}, [cellTooltipCallback]);
|
||||
|
||||
useEffect(() => {
|
||||
const gridView = gridRef.current?.gridView;
|
||||
if (!gridView || !onScrollToBottom) return;
|
||||
@@ -1996,7 +2163,7 @@ useEffect(() => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ height: `${gridHeight}vh` }}>
|
||||
<div ref={gridHostRef} className="relative" style={{ height: `${gridHeight}vh` }}>
|
||||
<RealGridReact
|
||||
ref={gridRef}
|
||||
autoGenerateField={true}
|
||||
@@ -2206,6 +2373,18 @@ useEffect(() => {
|
||||
>
|
||||
{columns.map((column) => {
|
||||
const baseStyleName = column.styleName;
|
||||
const numberFormat =
|
||||
column.dataFormat && column.dataFormat.trim() !== ""
|
||||
? column.dataFormat
|
||||
: "#,##0.000";
|
||||
const resolvedEditor =
|
||||
column.editor ??
|
||||
(column.valueType === ValueType.NUMBER
|
||||
? {
|
||||
type: "number" as const,
|
||||
editFormat: numberFormat,
|
||||
}
|
||||
: undefined);
|
||||
|
||||
return (
|
||||
<RGDataColumn
|
||||
@@ -2218,17 +2397,15 @@ useEffect(() => {
|
||||
: ValueType.TEXT
|
||||
}
|
||||
{...(column.valueType === ValueType.NUMBER && {
|
||||
numberFormat: column.dataFormat
|
||||
? column.dataFormat
|
||||
: "#,##0.000",
|
||||
numberFormat,
|
||||
})}
|
||||
header={realGridHeaderWithTooltip({
|
||||
...column.header,
|
||||
tooltip: resolveColumnTooltip(column),
|
||||
})}
|
||||
editable={column.editable}
|
||||
{...(column.editor != null && {
|
||||
editor: column.editor,
|
||||
{...(resolvedEditor != null && {
|
||||
editor: resolvedEditor,
|
||||
})}
|
||||
{...(column.values != null && {
|
||||
values: column.values,
|
||||
@@ -2287,6 +2464,21 @@ useEffect(() => {
|
||||
);
|
||||
})}
|
||||
</RealGridReact>
|
||||
{emptyCellHoverTooltip &&
|
||||
createPortal(
|
||||
<div
|
||||
className="rg-tooltip pointer-events-none !visible fixed z-[9999]"
|
||||
style={{
|
||||
left: emptyCellHoverTooltip.x,
|
||||
top: emptyCellHoverTooltip.y,
|
||||
whiteSpace: "pre-wrap",
|
||||
}}
|
||||
role="tooltip"
|
||||
>
|
||||
{emptyCellHoverTooltip.message}
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -56,8 +56,8 @@ type ExceptionHistoryTabKey = "NV" | "IMR" | "HB";
|
||||
|
||||
const exceptionHistoryTabs: { value: ExceptionHistoryTabKey; label: string }[] = [
|
||||
{ value: "NV", label: "일반검증 내역" },
|
||||
{ value: "IMR", label: "I-MR 검증내역" },
|
||||
{ value: "HB", label: "HB 검증내역" },
|
||||
{ value: "IMR", label: "시계열 검증내역" },
|
||||
{ value: "HB", label: "모집단 검증내역" },
|
||||
];
|
||||
|
||||
const TITLE = "예외처리 내역 조회";
|
||||
@@ -274,7 +274,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
setImrPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setImrPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("I-MR 검증내역 조회 중 오류가 발생했습니다.");
|
||||
setErrorMessage("시계열 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setImrLoading(false);
|
||||
}
|
||||
@@ -293,7 +293,7 @@ export function ExceptionProcessingHistoryDialog({
|
||||
setHbPageCount(response?.data?.pageInfo?.totalPages ?? 1);
|
||||
setHbPageIndex(pageIndex);
|
||||
} catch {
|
||||
setErrorMessage("HB 검증내역 조회 중 오류가 발생했습니다.");
|
||||
setErrorMessage("모집단 검증내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setHbLoading(false);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
LoadingWithAnimation,
|
||||
} from "@common/components";
|
||||
import CompactDataGrid from "./CompactDataGrid";
|
||||
import SaveButton from "../button/SaveButton";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -182,6 +181,7 @@ const SidoSggFacilityNameSearchForm = ({
|
||||
Record<string, string | number> | null
|
||||
>(null);
|
||||
const didAutoFetchRef = useRef(false);
|
||||
const isSavingConnectionRef = useRef(false);
|
||||
|
||||
const sggKey: [string, string] | null =
|
||||
sido && sido !== "ALL" ? ["sgg-all", sido] : null;
|
||||
@@ -329,119 +329,213 @@ const SidoSggFacilityNameSearchForm = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveAdminRegions = async () => {
|
||||
const canEditFactConnection = async (): Promise<boolean> => {
|
||||
const statusCodeResponse = await statisticsApi.CheckStatusCode({
|
||||
sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "",
|
||||
statsCd: 'ST00020002',
|
||||
statsYr: statsYr?.trim() ?? ''
|
||||
statsCd: "ST00020002",
|
||||
statsYr: statsYr?.trim() ?? "",
|
||||
});
|
||||
|
||||
if (statusCodeResponse.status === 'success') {
|
||||
if (!['0', '1', '4'].includes(statusCodeResponse.data)) {
|
||||
const codeName = statusCodeResponse.data === '2' ? '제출' : (statusCodeResponse.data === '3' ? '승인' : '검증중');
|
||||
alert(`시설별 설치 현황 데이터가 ${codeName} 상태일 때는 처리 시설 정보를 변경할 수 없습니다.`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
alert('오류가 발생했습니다.\n관리자에게 문의하세요')
|
||||
if (statusCodeResponse.status === "success") {
|
||||
if (!["0", "1", "4"].includes(statusCodeResponse.data)) {
|
||||
const codeName =
|
||||
statusCodeResponse.data === "2"
|
||||
? "제출"
|
||||
: statusCodeResponse.data === "3"
|
||||
? "승인"
|
||||
: "검증중";
|
||||
alert(
|
||||
`시설별 설치 현황 데이터가 ${codeName} 상태일 때는 처리 시설 정보를 변경할 수 없습니다.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
alert("오류가 발생했습니다.\n관리자에게 문의하세요");
|
||||
return false;
|
||||
};
|
||||
|
||||
const persistFactConnection = async (
|
||||
saveList: SeweragePipeFactConnectionSaveItem[],
|
||||
deleteList: string[],
|
||||
): Promise<boolean> => {
|
||||
if (!statsYr?.trim()) {
|
||||
alert("상단에서 통계연도로 조회한 뒤 이용해 주세요.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!selectedFacilityRow) {
|
||||
alert("처리시설을 먼저 선택해주세요.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (saveList.length === 0 && deleteList.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (!(await canEditFactConnection())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasChanged = adminRegionRows.some(
|
||||
(row) =>
|
||||
(row.__checked === "1" && row.isConnection !== "1") ||
|
||||
(row.__checked !== "1" && row.isConnection === "1"),
|
||||
);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const request: SeweragePipeFactConnectionSaveRequest = {
|
||||
ctpvNm: String(selectedFacilityRow.ctpvNm ?? ""),
|
||||
sggNm: String(selectedFacilityRow.sggNm ?? ""),
|
||||
fcltSn: String(selectedFacilityRow.fcltSn ?? ""),
|
||||
statsYr: statsYr.trim(),
|
||||
swtrmtFcltCd: String(selectedFacilityRow.fcltCd ?? ""),
|
||||
swtrmtFcltNm: String(selectedFacilityRow.fcltNm ?? ""),
|
||||
prcsFcltCpct: Number(selectedFacilityRow.fcltCpct ?? 0),
|
||||
sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
saveList,
|
||||
deleteList,
|
||||
};
|
||||
|
||||
const saveList: SeweragePipeFactConnectionSaveItem[] = adminRegionRows
|
||||
.filter((row) => row.__checked === "1")
|
||||
.map((row) => ({
|
||||
admdstCd: String(row.admdstCd ?? ""),
|
||||
admdstNm: String(row.emdNm ?? ""),
|
||||
}))
|
||||
.filter((row) => row.admdstCd && row.admdstNm);
|
||||
|
||||
const deleteList = adminRegionRows
|
||||
.filter((row) => row.__checked !== "1" && row.isConnection === "1")
|
||||
.map((row) => String(row.admdstCd ?? ""))
|
||||
.filter(Boolean);
|
||||
|
||||
if (!hasChanged) {
|
||||
alert("변경된 연결 정보가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const request: SeweragePipeFactConnectionSaveRequest = {
|
||||
ctpvNm: String(selectedFacilityRow.ctpvNm ?? ""),
|
||||
sggNm: String(selectedFacilityRow.sggNm ?? ""),
|
||||
fcltSn: String(selectedFacilityRow.fcltSn ?? ""),
|
||||
statsYr: statsYr.trim(),
|
||||
swtrmtFcltCd: String(selectedFacilityRow.fcltCd ?? ""),
|
||||
swtrmtFcltNm: String(selectedFacilityRow.fcltNm ?? ""),
|
||||
prcsFcltCpct: Number(selectedFacilityRow.fcltCpct ?? 0),
|
||||
sewerBzmnCd: initialSearchValues?.bsnmCd?.trim() ?? "",
|
||||
userId: userInfo.userUnqId,
|
||||
saveList,
|
||||
deleteList,
|
||||
};
|
||||
|
||||
const response =
|
||||
await statisticsInputSewPipeApiService.saveFactConnection(
|
||||
request,
|
||||
);
|
||||
|
||||
if (response.status !== "success") {
|
||||
alert(response.message ?? "연결관계 저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setAdminRegionRows((prev) =>
|
||||
prev.map((row) => ({
|
||||
...row,
|
||||
isConnection: row.__checked === "1" ? "1" : "0",
|
||||
})),
|
||||
const response =
|
||||
await statisticsInputSewPipeApiService.saveFactConnection(
|
||||
request,
|
||||
);
|
||||
await onSaveSuccess?.();
|
||||
alert("저장되었습니다.");
|
||||
} catch {
|
||||
alert("연결관계 저장 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
void run();
|
||||
if (response.status !== "success") {
|
||||
alert(response.message ?? "연결관계 저장에 실패했습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
await onSaveSuccess?.();
|
||||
return true;
|
||||
} catch {
|
||||
alert("연결관계 저장 중 오류가 발생했습니다.");
|
||||
return false;
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdminRegionItemChecked = (
|
||||
const syncConnectionFlags = (
|
||||
rows: Record<string, string | number>[],
|
||||
): Record<string, string | number>[] =>
|
||||
rows.map((row) => ({
|
||||
...row,
|
||||
isConnection: row.__checked === "1" ? "1" : "0",
|
||||
}));
|
||||
|
||||
const revertCheckedToConnection = (
|
||||
rows: Record<string, string | number>[],
|
||||
): Record<string, string | number>[] =>
|
||||
rows.map((row) => ({
|
||||
...row,
|
||||
__checked: row.isConnection === "1" ? "1" : "0",
|
||||
}));
|
||||
|
||||
const handleAdminRegionItemChecked = async (
|
||||
itemIndex: number,
|
||||
checked: boolean,
|
||||
) => {
|
||||
if (isSavingConnectionRef.current) {
|
||||
// RealGrid가 이미 토글했을 수 있어 __checked 기준으로 재동기화
|
||||
setAdminRegionRows((prev) => prev.map((row) => ({ ...row })));
|
||||
return;
|
||||
}
|
||||
|
||||
const target = adminRegionRows[itemIndex];
|
||||
if (!target) return;
|
||||
|
||||
const nextChecked = checked ? "1" : "0";
|
||||
const prevChecked = target.isConnection === "1" ? "1" : "0";
|
||||
|
||||
setAdminRegionRows((prev) =>
|
||||
prev.map((row, idx) =>
|
||||
idx === itemIndex
|
||||
? { ...row, __checked: checked ? "1" : "0" }
|
||||
? { ...row, __checked: nextChecked }
|
||||
: row,
|
||||
),
|
||||
);
|
||||
|
||||
// 서버 상태와 동일하면 API 호출 없이 UI만 반영
|
||||
if (nextChecked === prevChecked) {
|
||||
return;
|
||||
}
|
||||
|
||||
const saveList: SeweragePipeFactConnectionSaveItem[] =
|
||||
checked
|
||||
? [
|
||||
{
|
||||
admdstCd: String(target.admdstCd ?? ""),
|
||||
admdstNm: String(target.emdNm ?? ""),
|
||||
},
|
||||
].filter((row) => row.admdstCd && row.admdstNm)
|
||||
: [];
|
||||
const deleteList =
|
||||
!checked && String(target.admdstCd ?? "")
|
||||
? [String(target.admdstCd)]
|
||||
: [];
|
||||
|
||||
if (saveList.length === 0 && deleteList.length === 0) {
|
||||
setAdminRegionRows((prev) => revertCheckedToConnection(prev));
|
||||
alert("행정구역 코드가 없어 저장할 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
isSavingConnectionRef.current = true;
|
||||
const ok = await persistFactConnection(saveList, deleteList);
|
||||
isSavingConnectionRef.current = false;
|
||||
|
||||
if (!ok) {
|
||||
setAdminRegionRows((prev) => revertCheckedToConnection(prev));
|
||||
return;
|
||||
}
|
||||
|
||||
setAdminRegionRows((prev) => syncConnectionFlags(prev));
|
||||
};
|
||||
|
||||
const handleAdminRegionItemAllChecked = (checked: boolean) => {
|
||||
const handleAdminRegionItemAllChecked = async (checked: boolean) => {
|
||||
if (isSavingConnectionRef.current) {
|
||||
setAdminRegionRows((prev) => prev.map((row) => ({ ...row })));
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = adminRegionRows;
|
||||
if (snapshot.length === 0) return;
|
||||
|
||||
setAdminRegionRows((prev) =>
|
||||
prev.map((row) => ({ ...row, __checked: checked ? "1" : "0" })),
|
||||
prev.map((row) => ({
|
||||
...row,
|
||||
__checked: checked ? "1" : "0",
|
||||
})),
|
||||
);
|
||||
|
||||
const saveList: SeweragePipeFactConnectionSaveItem[] = checked
|
||||
? snapshot
|
||||
.filter((row) => row.isConnection !== "1")
|
||||
.map((row) => ({
|
||||
admdstCd: String(row.admdstCd ?? ""),
|
||||
admdstNm: String(row.emdNm ?? ""),
|
||||
}))
|
||||
.filter((row) => row.admdstCd && row.admdstNm)
|
||||
: [];
|
||||
const deleteList = !checked
|
||||
? snapshot
|
||||
.filter((row) => row.isConnection === "1")
|
||||
.map((row) => String(row.admdstCd ?? ""))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
if (saveList.length === 0 && deleteList.length === 0) {
|
||||
setAdminRegionRows((prev) => syncConnectionFlags(prev));
|
||||
return;
|
||||
}
|
||||
|
||||
isSavingConnectionRef.current = true;
|
||||
const ok = await persistFactConnection(saveList, deleteList);
|
||||
isSavingConnectionRef.current = false;
|
||||
|
||||
if (!ok) {
|
||||
setAdminRegionRows((prev) => revertCheckedToConnection(prev));
|
||||
return;
|
||||
}
|
||||
|
||||
setAdminRegionRows((prev) => syncConnectionFlags(prev));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -575,8 +669,10 @@ const SidoSggFacilityNameSearchForm = ({
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<SaveButton saveFunc={handleSaveAdminRegions} />
|
||||
<div>
|
||||
<span className="text-lg text-gray-500 float-end">
|
||||
* 체크 박스를 선택 / 해제 시 반영됩니다.
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
{isLoading && (
|
||||
|
||||
@@ -41,8 +41,8 @@ const VERIFICATION_METHOD_CODES: VerificationMethodCd[] = ["NV", "IMR", "HB"];
|
||||
|
||||
const VRFC_MTHD_NAME_MAP: Record<VerificationMethodCd, string> = {
|
||||
NV: "일반",
|
||||
IMR: "I-MR",
|
||||
HB: "HB",
|
||||
IMR: "시계열",
|
||||
HB: "모집단",
|
||||
};
|
||||
|
||||
export type VerificationExceptionSubmitItem = {
|
||||
|
||||
@@ -40,8 +40,8 @@ type VerificationHistoryTabKey = "NV" | "IMR" | "HB";
|
||||
|
||||
const verificationHistoryTabs: { value: VerificationHistoryTabKey; label: string }[] = [
|
||||
{ value: "NV", label: "일반검증 내역" },
|
||||
{ value: "IMR", label: "I-MR 검증내역" },
|
||||
{ value: "HB", label: "HB 검증내역" },
|
||||
{ value: "IMR", label: "시계열 검증내역" },
|
||||
{ value: "HB", label: "모집단 검증내역" },
|
||||
];
|
||||
|
||||
const PAGE_SIZE = 30;
|
||||
|
||||
Reference in New Issue
Block a user