st: fix: 로그 조회, Grid cell style 수정, 입력,검토,집계표 작업중
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user