[st] - 검토단 작업 완료 및 입력6-1, 10-1 운영데이터업데이트 기능 추가, 텝 영역 우클릭 후 텝 닫기 기능 추가
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import { useEffect, useLayoutEffect, useRef, type MutableRefObject } from "react";
|
||||
|
||||
import JSZip from "jszip";
|
||||
import { ValueType } from "realgrid";
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
CellStyleCallbackParams,
|
||||
CellStyleCallbackResult,
|
||||
CellTooltipCallback,
|
||||
ColumnConfig,
|
||||
ColumnLayoutItem,
|
||||
DataGridProps,
|
||||
FooterItemWithCallback,
|
||||
@@ -48,6 +49,63 @@ function mergeCellStyleCallbackResult(
|
||||
};
|
||||
}
|
||||
|
||||
type GridColumnWithStyleCallback = {
|
||||
styleCallback?: (
|
||||
grid: unknown,
|
||||
dataCell: {
|
||||
itemIndex?: number;
|
||||
index?: { itemIndex?: number };
|
||||
dataField?: string;
|
||||
fieldName?: string;
|
||||
},
|
||||
) => CellStyleCallbackResult;
|
||||
};
|
||||
|
||||
function applyCellStyleCallbacksToColumns(
|
||||
gridView: unknown,
|
||||
columns: ColumnConfig[],
|
||||
rowDataRef: MutableRefObject<Record<string, string | number>[]>,
|
||||
cellStyleCallbackRef: MutableRefObject<CellStyleCallback | undefined>,
|
||||
) {
|
||||
if (!cellStyleCallbackRef.current || !gridView) return;
|
||||
|
||||
const gv = gridView as {
|
||||
columnByName?: (name: string) => GridColumnWithStyleCallback | null | undefined;
|
||||
};
|
||||
if (typeof gv.columnByName !== "function") return;
|
||||
|
||||
columns.forEach((column) => {
|
||||
const baseStyleName = column.styleName;
|
||||
let gridCol: GridColumnWithStyleCallback | null | undefined;
|
||||
try {
|
||||
// columnByName은 GridView 인스턴스 메서드 — this 바인딩을 위해 gv에서 직접 호출
|
||||
gridCol = gv.columnByName!(column.name);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (!gridCol) return;
|
||||
|
||||
gridCol.styleCallback = (grid, dataCell) => {
|
||||
const itemIndex =
|
||||
dataCell?.index?.itemIndex ?? dataCell?.itemIndex ?? -1;
|
||||
const fieldName =
|
||||
dataCell?.dataField ?? dataCell?.fieldName ?? column.fieldName;
|
||||
const data = rowDataRef.current;
|
||||
if (itemIndex < 0 || itemIndex >= data.length) {
|
||||
return mergeCellStyleCallbackResult(baseStyleName, undefined);
|
||||
}
|
||||
const row = data[itemIndex] as Record<string, string | number>;
|
||||
const extra = cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
});
|
||||
return mergeCellStyleCallbackResult(baseStyleName, extra);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// RealGrid 타입 확장
|
||||
declare module "realgrid" {
|
||||
interface GridView {
|
||||
@@ -725,9 +783,16 @@ const DataGrid = ({
|
||||
}, [highlightedRowId, rowData, idFieldName]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!cellStyleCallback || !gridRef.current?.gridView) return;
|
||||
gridRef.current.gridView.refresh();
|
||||
}, [cellStyleCallback, rowData]);
|
||||
const gv = gridRef.current?.gridView;
|
||||
if (!gv || !cellStyleCallback) return;
|
||||
applyCellStyleCallbacksToColumns(
|
||||
gv,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
(gv as { refresh?: () => void }).refresh?.();
|
||||
}, [cellStyleCallback, columns, rowData]);
|
||||
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const activeTabId = useTabStore((state) => state.activeTabId);
|
||||
@@ -1049,6 +1114,10 @@ const DataGrid = ({
|
||||
|
||||
const gridView = gridRef.current.gridView;
|
||||
const dataProvider = gridRef.current.dataProvider;
|
||||
const footerTimerIds: ReturnType<typeof setTimeout>[] = [];
|
||||
const scheduleFooterTask = (task: () => void, delayMs: number) => {
|
||||
footerTimerIds.push(setTimeout(task, delayMs));
|
||||
};
|
||||
applyDefaultGridEditOptions(gridView as GridViewWithEditOptions);
|
||||
|
||||
const columnsFieldsSig = columns.map((c) => c.fieldName).join("\u0001");
|
||||
@@ -1189,8 +1258,10 @@ const DataGrid = ({
|
||||
});
|
||||
|
||||
const applyFooterSettings = (
|
||||
targetGridView: typeof gridView,
|
||||
targetGridView: typeof gridView | null | undefined,
|
||||
) => {
|
||||
if (!targetGridView?.setOptions) return;
|
||||
|
||||
const hasFooter = columns.some((col) => col.footer);
|
||||
if (!hasFooter) return;
|
||||
|
||||
@@ -1308,7 +1379,7 @@ const DataGrid = ({
|
||||
};
|
||||
|
||||
// setColumnLayout은 그리드가 완전히 렌더링된 후 호출해야 함
|
||||
setTimeout(() => {
|
||||
scheduleFooterTask(() => {
|
||||
if (gridRef.current?.gridView && columnLayout) {
|
||||
const safeLayout = sanitizeColumnLayout(columnLayout as unknown[]);
|
||||
if (safeLayout.length === 0) return;
|
||||
@@ -1327,10 +1398,9 @@ const DataGrid = ({
|
||||
|
||||
// footer 설정 (배열 또는 단일 객체 모두 처리)
|
||||
// setFooters 호출 후 footer 배열 설정
|
||||
setTimeout(() => {
|
||||
scheduleFooterTask(() => {
|
||||
const currentGridView = gridRef.current?.gridView;
|
||||
if (!currentGridView || !currentGridView.columnByName) {
|
||||
console.warn("GridView not available for footer setup");
|
||||
if (!currentGridView?.columnByName) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1385,19 +1455,28 @@ const DataGrid = ({
|
||||
col,
|
||||
),
|
||||
);
|
||||
applyCellStyleCallbacksToColumns(
|
||||
currentGridView,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
applyFooterSettings(currentGridView);
|
||||
if (hasFooterUserSpans) {
|
||||
currentGridView.setColumnLayout?.(columnLayout);
|
||||
}
|
||||
|
||||
// setColumns 호출 후 footer 배열의 valueCallback을 다시 설정
|
||||
setTimeout(() => {
|
||||
scheduleFooterTask(() => {
|
||||
const liveGridView = gridRef.current?.gridView;
|
||||
if (!liveGridView?.columnByName) return;
|
||||
|
||||
columns.forEach((col) => {
|
||||
if (!col.footer || !Array.isArray(col.footer))
|
||||
return;
|
||||
|
||||
try {
|
||||
const column = currentGridView.columnByName(
|
||||
const column = liveGridView.columnByName(
|
||||
col.name,
|
||||
);
|
||||
if (!column) return;
|
||||
@@ -1429,11 +1508,17 @@ const DataGrid = ({
|
||||
);
|
||||
}
|
||||
});
|
||||
applyFooterSettings(currentGridView);
|
||||
applyCellStyleCallbacksToColumns(
|
||||
liveGridView,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
applyFooterSettings(liveGridView);
|
||||
if (hasFooterUserSpans) {
|
||||
currentGridView.setColumnLayout?.(columnLayout);
|
||||
liveGridView.setColumnLayout?.(columnLayout);
|
||||
}
|
||||
currentGridView.refresh?.();
|
||||
liveGridView.refresh?.();
|
||||
}, 50);
|
||||
} catch (e) {
|
||||
console.warn("Failed to set columns with footer array:", e);
|
||||
@@ -1495,6 +1580,7 @@ const DataGrid = ({
|
||||
}
|
||||
|
||||
return () => {
|
||||
footerTimerIds.forEach(clearTimeout);
|
||||
if (getGridDataRef) getGridDataRef.current = null;
|
||||
};
|
||||
}, [
|
||||
@@ -1939,19 +2025,9 @@ const DataGrid = ({
|
||||
};
|
||||
}
|
||||
columns.forEach((column) => {
|
||||
const baseStyleName = column.styleName;
|
||||
const gridCol = gv.columnByName(
|
||||
column.name,
|
||||
) as {
|
||||
styleCallback?: (
|
||||
g: unknown,
|
||||
dataCell: {
|
||||
itemIndex?: number;
|
||||
index?: { itemIndex?: number };
|
||||
dataField?: string;
|
||||
fieldName?: string;
|
||||
},
|
||||
) => CellStyleCallbackResult;
|
||||
renderer?: { showTooltip?: boolean };
|
||||
} | null;
|
||||
if (gridCol) {
|
||||
@@ -1990,43 +2066,14 @@ const DataGrid = ({
|
||||
{ showTooltip: true },
|
||||
);
|
||||
}
|
||||
gridCol.styleCallback = (grid, dataCell) => {
|
||||
const itemIndex =
|
||||
dataCell?.index?.itemIndex ??
|
||||
dataCell?.itemIndex ??
|
||||
-1;
|
||||
const fieldName =
|
||||
dataCell?.dataField ??
|
||||
dataCell?.fieldName ??
|
||||
column.fieldName;
|
||||
const data = rowDataRef.current;
|
||||
if (
|
||||
itemIndex < 0 ||
|
||||
itemIndex >= data.length
|
||||
) {
|
||||
return mergeCellStyleCallbackResult(
|
||||
baseStyleName,
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
const row = data[itemIndex] as Record<
|
||||
string,
|
||||
string | number
|
||||
>;
|
||||
const extra =
|
||||
cellStyleCallbackRef.current?.({
|
||||
rowIndex: itemIndex,
|
||||
fieldName,
|
||||
row,
|
||||
grid: grid as CellStyleCallbackParams["grid"],
|
||||
});
|
||||
return mergeCellStyleCallbackResult(
|
||||
baseStyleName,
|
||||
extra,
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
applyCellStyleCallbacksToColumns(
|
||||
gv,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
gv.refresh();
|
||||
}, 0);
|
||||
}}
|
||||
|
||||
@@ -234,6 +234,7 @@ export function VerificationResultHistoryDialog({
|
||||
};
|
||||
const checkedVrfcByTabRef = useRef(checkedVrfcByTab);
|
||||
checkedVrfcByTabRef.current = checkedVrfcByTab;
|
||||
const loadedTabKeysRef = useRef<Set<VerificationHistoryTabKey>>(new Set());
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setActiveTab("NV");
|
||||
@@ -243,6 +244,7 @@ export function VerificationResultHistoryDialog({
|
||||
setErrorMessage(null);
|
||||
setResetSubmitting(false);
|
||||
setCheckedVrfcByTab(createEmptyCheckedVrfcByTab());
|
||||
loadedTabKeysRef.current.clear();
|
||||
}, []);
|
||||
|
||||
const fetchVerificationResultPage = useCallback(
|
||||
@@ -279,10 +281,10 @@ export function VerificationResultHistoryDialog({
|
||||
);
|
||||
|
||||
const loadTabPage = useCallback(
|
||||
async (tab: VerificationHistoryTabKey, pageIndex: number) => {
|
||||
async (tab: VerificationHistoryTabKey, pageIndex: number): Promise<boolean> => {
|
||||
if (!stableQueryContext) {
|
||||
setErrorMessage("조회 조건이 없어 검증 결과를 불러올 수 없습니다.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setLoadingByTab((prev) => ({ ...prev, [tab]: true }));
|
||||
@@ -296,10 +298,12 @@ export function VerificationResultHistoryDialog({
|
||||
? 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 }));
|
||||
}
|
||||
@@ -314,40 +318,27 @@ export function VerificationResultHistoryDialog({
|
||||
}
|
||||
setErrorMessage(null);
|
||||
setTabPages(initialTabPageState());
|
||||
setLoadingByTab({ NV: true, IMR: true, HB: true });
|
||||
try {
|
||||
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 {
|
||||
setEntriesByTab({ NV: [], IMR: [], HB: [] });
|
||||
setTabPages(initialTabPageState());
|
||||
setErrorMessage("검증 결과 내역 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setLoadingByTab({ NV: false, IMR: false, HB: false });
|
||||
setEntriesByTab({ NV: [], IMR: [], HB: [] });
|
||||
loadedTabKeysRef.current.clear();
|
||||
const loaded = await loadTabPage("NV", 0);
|
||||
if (loaded) {
|
||||
loadedTabKeysRef.current.add("NV");
|
||||
}
|
||||
}, [fetchVerificationResultPage, stableQueryContext]);
|
||||
}, [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) => {
|
||||
@@ -577,7 +568,7 @@ export function VerificationResultHistoryDialog({
|
||||
)}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(value) => setActiveTab(value as VerificationHistoryTabKey)}
|
||||
onValueChange={(value) => handleActiveTabChange(value as VerificationHistoryTabKey)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user