st: fix: 로그 조회, Grid cell style 수정, 입력,검토,집계표 작업중

This commit is contained in:
2026-04-10 17:23:43 +09:00
parent 207268b7fc
commit a1f02d3717
29 changed files with 2073 additions and 493 deletions
+121 -15
View File
@@ -1,19 +1,22 @@
import { useRef, useEffect, useState } from "react"
import { ValueType } from "realgrid"
import { useCallback,useEffect, useLayoutEffect, useRef, useState } from "react"
import JSZip from "jszip"
import { ValueType } from "realgrid"
import RealGrid, { GridFitStyle } from "realgrid"
import { RealGridReact, RGDataColumn } from "realgrid-react"
import type { TabDataGridProps, ColumnInfo, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback, ColumnLayoutItem } from "../type/DataGrid.type"
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
import JSZip from "jszip"
import * as XLSX from "xlsx"
import { useAlertStore } from "@/shared/lib";
import { useTabStore } from "@/store/tab-store"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components";
import { useAlertStore } from "@/shared/lib";
import { Tabs, TabsContent,TabsList, TabsTrigger } from "@common/components";
import { Button } from "@common/components";
import SplitDataGrid from "./SplitDataGrid";
import type { CellStyleCallback,ColumnConfig, ColumnInfo, ColumnLayoutItem, FooterItemWithCallback, GridViewWithSetColumns, RealGridColumnInfo, TabDataGridProps } from "../type/DataGrid.type"
import GridTopActionButton from "./GridTopActionButton";
import SplitDataGrid from "./SplitDataGrid";
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks"
import { useTabStore } from "@/store/tab-store"
// RealGrid 타입 확장
declare module "realgrid" {
@@ -50,6 +53,30 @@ if (typeof window !== "undefined") {
RealGrid.setLicenseKey(process.env.NEXT_PUBLIC_ST_REALGRID_KEY || '');
/** RealGrid 내부 해제(_view 등) 타이밍에서 refresh가 터질 수 있어 try/catch */
function safeGridViewRefresh(gv: { refresh?: () => void } | null | undefined) {
if (!gv?.refresh) return;
try {
gv.refresh();
} catch {
/* noop */
}
}
/** columnByName은 그리드 _view 준비 전에 호출되면 "Cannot read ... '_view'"로 터짐 */
function safeColumnByName(
gv: { columnByName?: (name: string) => unknown } | null | undefined,
name: string,
): { styleCallback?: (g: unknown, dataCell: Record<string, unknown>) => string } | null {
if (!gv?.columnByName) return null;
try {
const col = gv.columnByName(name) as { styleCallback?: (g: unknown, dataCell: Record<string, unknown>) => string } | null;
return col ?? null;
} catch {
return null;
}
}
/** 정렬 필드 값을 숫자로 파싱 (쉼표 제거) */
function toNum(v: unknown): number {
if (v == null) return NaN;
@@ -228,6 +255,7 @@ const TabDataGrid = ({
getGridDataRef,
enableSort = false,
facilitySearchStatsYr,
cellStyleCallback,
}: TabDataGridProps) => {
// 각 탭별로 그리드 ref와 데이터를 관리
const gridRefs = useRef<Map<string, RealGridReact>>(new Map());
@@ -240,6 +268,69 @@ const TabDataGrid = ({
const activeTabId = useTabStore((state) => state.activeTabId);
const menuList = useTabStore((state) => state.menuList);
const rowDataMapRef = useRef(rowDataMap);
rowDataMapRef.current = rowDataMap;
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(undefined);
cellStyleCallbackRef.current = cellStyleCallback;
const attachCellStyleCallbacksForTab = useCallback((tabKey: string, columns: ColumnConfig[], attempt = 0) => {
if (!cellStyleCallbackRef.current) return;
const el = gridRefs.current.get(tabKey);
const gv = el?.gridView;
if (!gv) return;
let attachedCount = 0;
for (const column of columns) {
const gridCol = safeColumnByName(gv, column.name);
if (!gridCol) continue;
const baseStyleName = column.styleName;
gridCol.styleCallback = (_g, dataCell) => {
const dc = dataCell as {
itemIndex?: number;
index?: { itemIndex?: number };
dataField?: string;
fieldName?: string;
};
const itemIndex = dc?.index?.itemIndex ?? dc?.itemIndex ?? -1;
const fieldName = dc?.dataField ?? dc?.fieldName ?? column.fieldName;
const data = rowDataMapRef.current.get(tabKey) || [];
if (itemIndex < 0 || itemIndex >= data.length) {
return baseStyleName ?? "";
}
const row = data[itemIndex] as Record<string, string | number>;
const extra = cellStyleCallbackRef.current?.({
rowIndex: itemIndex,
fieldName,
row,
});
if (!extra) return baseStyleName ?? "";
return baseStyleName ? `${extra} ${baseStyleName}` : extra;
};
attachedCount += 1;
}
if (attachedCount === 0 && attempt < 20) {
setTimeout(() => attachCellStyleCallbacksForTab(tabKey, columns, attempt + 1), 100);
return;
}
setTimeout(() => {
const latestGv = gridRefs.current.get(tabKey)?.gridView;
safeGridViewRefresh(latestGv ?? null);
}, 0);
}, []);
/** 스타일 콜백은 ref로 최신 row를 읽음 — 여기서는 재부착 없이 refresh만 (이른 columnByName 호출 방지) */
useLayoutEffect(() => {
if (!cellStyleCallback) return;
const id = requestAnimationFrame(() => {
gridRefs.current.forEach((ref) => {
safeGridViewRefresh(ref?.gridView ?? null);
});
});
return () => cancelAnimationFrame(id);
}, [cellStyleCallback, rowDataMap]);
const [internalActiveTab, setInternalActiveTab] = useState<string>(() => {
const firstTab = columnInfo[0];
return firstTab?.tabKey || '';
@@ -559,25 +650,34 @@ const TabDataGrid = ({
// rowDataMap이 변경될 때 그리드에 데이터 반영 (Excel import 등)
useEffect(() => {
const timeoutIds: ReturnType<typeof setTimeout>[] = [];
columnInfo.forEach((info) => {
const tabKey = info.tabKey;
const gridRef = gridRefs.current.get(tabKey);
const currentData = rowDataMap.get(tabKey);
if (gridRef?.gridView && gridRef?.dataProvider && currentData && currentData.length > 0) {
setTimeout(() => {
const id = setTimeout(() => {
try {
const dp = gridRef.dataProvider as RealGridDataProviderFill | null;
if (dp?.fillJsonData) {
dp.fillJsonData(currentData, { fillMode: "set" });
}
gridRef.gridView.refresh();
const latestRef = gridRefs.current.get(tabKey);
const gv = latestRef?.gridView;
const dp = latestRef?.dataProvider as RealGridDataProviderFill | null;
if (!gv || !dp?.fillJsonData || !currentData.length) return;
dp.fillJsonData(currentData, { fillMode: "set" });
gv.refresh();
} catch (error) {
console.error("그리드 데이터 설정 오류:", error);
}
}, 100);
timeoutIds.push(id);
}
});
return () => {
timeoutIds.forEach((tid) => clearTimeout(tid));
};
}, [rowDataMap, columnInfo]);
// getGridDataRef: 현재 활성 탭 그리드 데이터 반환 함수 할당
@@ -1096,6 +1196,12 @@ const TabDataGrid = ({
const onSortingHandler = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef, findGridElement);
(gridView as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = enableSort ? onSortingHandler : () => false;
if (cellStyleCallbackRef.current) {
setTimeout(() => {
attachCellStyleCallbacksForTab(tabKey, columns);
}, 100);
}
}, 150);
}
} else {