es-grid-template 1.9.90 → 1.9.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/es/ali-table/InternalTable.js +1 -1
  2. package/es/grid-component/GridRefreshButton.css +9 -0
  3. package/es/grid-component/GridRefreshButton.d.ts +8 -0
  4. package/es/grid-component/GridRefreshButton.js +47 -0
  5. package/es/grid-component/TempTable.d.ts +1 -1
  6. package/es/grid-component/hooks/utils.js +1 -0
  7. package/es/grid-component/type.d.ts +26 -0
  8. package/es/group-component/TableContainer.js +8 -2
  9. package/es/group-component/TableContainerEdit.js +8 -2
  10. package/es/group-component/hook/utils.d.ts +3 -3
  11. package/es/group-component/hook/utils.js +1 -0
  12. package/es/group-component/table/Grid.js +1 -1
  13. package/es/table-component/TableContainer.js +8 -2
  14. package/es/table-component/hook/utils.js +1 -0
  15. package/es/table-component/table/Grid.js +1 -1
  16. package/es/table-virtuoso/InternalTable.js +13 -3
  17. package/es/table-virtuoso/hook/utils.js +1 -0
  18. package/es/table-virtuoso/table/Grid.js +1 -1
  19. package/es/table-virtuoso/table/TableContainer.js +8 -2
  20. package/es/table-virtuoso/table/TableWrapper.d.ts +2 -1
  21. package/es/table-virtuoso/table/TableWrapper.js +17 -3
  22. package/lib/ali-table/InternalTable.js +1 -1
  23. package/lib/grid-component/GridRefreshButton.css +9 -0
  24. package/lib/grid-component/GridRefreshButton.d.ts +8 -0
  25. package/lib/grid-component/GridRefreshButton.js +54 -0
  26. package/lib/grid-component/TempTable.d.ts +1 -1
  27. package/lib/grid-component/hooks/utils.js +1 -0
  28. package/lib/grid-component/type.d.ts +26 -0
  29. package/lib/group-component/TableContainer.js +8 -2
  30. package/lib/group-component/TableContainerEdit.js +8 -2
  31. package/lib/group-component/hook/utils.d.ts +9 -9
  32. package/lib/group-component/hook/utils.js +1 -0
  33. package/lib/group-component/table/Grid.js +1 -1
  34. package/lib/table-component/TableContainer.js +8 -2
  35. package/lib/table-component/hook/utils.js +1 -0
  36. package/lib/table-component/table/Grid.js +1 -1
  37. package/lib/table-virtuoso/InternalTable.js +13 -3
  38. package/lib/table-virtuoso/hook/utils.js +1 -0
  39. package/lib/table-virtuoso/table/Grid.js +1 -1
  40. package/lib/table-virtuoso/table/TableContainer.js +8 -2
  41. package/lib/table-virtuoso/table/TableWrapper.d.ts +2 -1
  42. package/lib/table-virtuoso/table/TableWrapper.js +17 -3
  43. package/package.json +1 -1
@@ -541,7 +541,7 @@ const InternalTable = props => {
541
541
  rowSelected?.({
542
542
  type,
543
543
  rowData,
544
- selected: rs
544
+ selected: rs.filter(x => !x.isGroupRow)
545
545
  });
546
546
  }
547
547
 
@@ -0,0 +1,9 @@
1
+ .ui-rc-grid-refresh-spin {
2
+ transform-origin: center;
3
+ transform-box: fill-box;
4
+ animation: ui-rc-grid-refresh-spin 0.8s linear infinite;
5
+ }
6
+
7
+ @keyframes ui-rc-grid-refresh-spin {
8
+ to { transform: rotate(360deg); }
9
+ }
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+ import './GridRefreshButton.css';
3
+ import type { TableProps } from './type';
4
+ type Props = Pick<TableProps<object>, 'onRefresh' | 't'> & {
5
+ id: string;
6
+ };
7
+ declare const GridRefreshButton: ({ id, onRefresh, t }: Props) => JSX.Element;
8
+ export default GridRefreshButton;
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import cn from 'classnames';
3
+ import { RefreshCw } from 'becoxy-icons';
4
+ import "./GridRefreshButton.css";
5
+ const GridRefreshButton = ({
6
+ id,
7
+ onRefresh,
8
+ t
9
+ }) => {
10
+ const [isRefreshing, setIsRefreshing] = React.useState(false);
11
+ const refreshPendingRef = React.useRef(false);
12
+ if (!onRefresh) {
13
+ return null;
14
+ }
15
+ const handleRefresh = async () => {
16
+ if (refreshPendingRef.current) {
17
+ return;
18
+ }
19
+ refreshPendingRef.current = true;
20
+ setIsRefreshing(true);
21
+ try {
22
+ await onRefresh();
23
+ } catch (error) {
24
+ console.error('Grid refresh failed:', error);
25
+ } finally {
26
+ refreshPendingRef.current = false;
27
+ setIsRefreshing(false);
28
+ }
29
+ };
30
+ const label = String(t?.('RefreshData') ?? 'Refresh');
31
+ return /*#__PURE__*/React.createElement(RefreshCw, {
32
+ className: cn('toolbar-icon', {
33
+ 'ui-rc-grid-refresh-spin': isRefreshing
34
+ }),
35
+ "data-tooltip-id": `${id}-tooltip-content`,
36
+ "data-tooltip-content": label,
37
+ color: isRefreshing ? '#9B9B9B' : '#555555',
38
+ fontSize: 21,
39
+ style: {
40
+ cursor: isRefreshing ? 'default' : 'pointer'
41
+ },
42
+ onClick: () => {
43
+ void handleRefresh();
44
+ }
45
+ });
46
+ };
47
+ export default GridRefreshButton;
@@ -1,4 +1,4 @@
1
1
  /// <reference types="react" />
2
- import type { TableProps } from "../grid-component";
2
+ import type { TableProps } from '../grid-component';
3
3
  declare const TempTable: <RecordType extends object>(props: TableProps<RecordType>) => JSX.Element;
4
4
  export default TempTable;
@@ -2414,6 +2414,7 @@ export function groupArrayByColumns(data, columns) {
2414
2414
  rowId: newGuid(),
2415
2415
  parentId: !isEmpty(item[columns[index - 1]]) ? item[columns[index - 1]] : null,
2416
2416
  // tự tính tổng rồi điền thuộc tính vào
2417
+ isGroupRow: true,
2417
2418
  children: []
2418
2419
  };
2419
2420
  currentLevel.push(newItem);
@@ -380,6 +380,18 @@ export type TableProps<RecordType = AnyObject> = {
380
380
  infiniteScroll?: boolean;
381
381
  /** Hàm gọi khi cuộn đến cuối */
382
382
  next?: () => void;
383
+ /**
384
+ * Thông báo khi phạm vi dòng đang render thay đổi (cuộn, mở/đóng cây hoặc cập nhật dữ liệu).
385
+ * - `rowIds`: ID dòng TanStack sau khi expand/collapse, gồm cả vùng đệm (overscan).
386
+ * - `startIndex` / `endIndex`: vị trí đầu/cuối trong danh sách dòng đang hiển thị (bao gồm hai đầu).
387
+ * Dùng để xác định dòng cần tải dữ liệu; callback không tự gọi API.
388
+ * Chỉ hỗ trợ khi `groupAble=true` và `editAble=false`. Không truyền thì giữ hành vi cũ.
389
+ */
390
+ onVisibleRowsChange?: (range: {
391
+ rowIds: string[];
392
+ startIndex: number;
393
+ endIndex: number;
394
+ }) => void;
383
395
  /** Ngưỡng trigger load thêm dữ liệu */
384
396
  onEndReachedThreshold?: number;
385
397
  /** Cho phép group dữ liệu */
@@ -388,6 +400,12 @@ export type TableProps<RecordType = AnyObject> = {
388
400
  groupColumns?: string[];
389
401
  /** Cấu hình group */
390
402
  groupSetting?: IGroupSetting;
403
+ /**
404
+ * Hiện nút làm mới trên toolbar khi truyền callback.
405
+ * Grid không tự gọi API: consumer quyết định dữ liệu cần tải lại.
406
+ * Hỗ trợ Promise (ví dụ refetch() của TanStack Query) để tự khóa nút khi đang tải.
407
+ */
408
+ onRefresh?: () => void | Promise<unknown>;
391
409
  /** Hiển thị toolbar */
392
410
  showToolbar?: boolean;
393
411
  /** Các action trên toolbar
@@ -550,6 +568,14 @@ export type ExpandableConfig<RecordType> = {
550
568
  onExpand?: (expanded: boolean, rowData: RecordType) => void;
551
569
  onExpandedRowsChange?: (expandedKeys: readonly Key[]) => void;
552
570
  defaultExpandAllRows?: boolean;
571
+ /**
572
+ * Giữ trạng thái mở/đóng cây do người dùng thao tác khi `dataSource` cập nhật.
573
+ * Khi `true`, chỉ áp dụng `defaultExpandAllRows` / `defaultExpandedRowKeys` một lần
574
+ * sau khi có dữ liệu; không tự mở lại những nhánh đã đóng khi tải thêm dòng.
575
+ * Mặc định `false`: dữ liệu thay đổi vẫn áp dụng lại trạng thái mở rộng mặc định như trước.
576
+ * Chỉ hỗ trợ Grid nhóm không chỉnh sửa (`groupAble=true`, `editAble=false`).
577
+ */
578
+ preserveExpandedOnDataChange?: boolean;
553
579
  expandIconColumnIndex?: number;
554
580
  showExpandAll?: boolean;
555
581
  showExpandColumn?: boolean;
@@ -5,6 +5,7 @@ import { TableContext } from "./useContext";
5
5
  import Pagination from 'rc-master-ui/es/pagination';
6
6
  import { Toolbar } from 'rc-master-ui';
7
7
  import { Collapse2, Expand2, Maximize, Minimize } from 'becoxy-icons';
8
+ import GridRefreshButton from "../grid-component/GridRefreshButton";
8
9
  // import { ColumnsChoose } from './ColumnsChoose'
9
10
  import { numericFormatter } from 'react-numeric-component';
10
11
  import TableWrapper from "./table/TableWrapper";
@@ -42,6 +43,7 @@ const TableContainer = props => {
42
43
  showToolbar,
43
44
  toolbarItems,
44
45
  actionTemplate,
46
+ onRefresh,
45
47
  pagination,
46
48
  fullScreen,
47
49
  showColumnChoose,
@@ -232,7 +234,7 @@ const TableContainer = props => {
232
234
  return /*#__PURE__*/React.createElement("div", {
233
235
  ref: containerRef,
234
236
  id: id
235
- }, (showToolbar !== false || fullScreen !== false || title) && /*#__PURE__*/React.createElement("div", {
237
+ }, (showToolbar !== false || fullScreen !== false || title || onRefresh) && /*#__PURE__*/React.createElement("div", {
236
238
  ref: topToolbarRef,
237
239
  className: classNames(`${prefix}-grid-top-toolbar`, {})
238
240
  }, /*#__PURE__*/React.createElement("div", {
@@ -269,7 +271,11 @@ const TableContainer = props => {
269
271
  showTotal: (totalItems, range) =>
270
272
  // @ts-ignore
271
273
  `${range[0]}-${range[1]} / ${totalItems} ${t ? t(pagination?.locale?.items ?? 'items') : 'items'}`
272
- }, pagination)), typeof actionTemplate === 'function' ? actionTemplate() : actionTemplate, fullScreen !== false && (isFullScreen ? /*#__PURE__*/React.createElement(Minimize, {
274
+ }, pagination)), typeof actionTemplate === 'function' ? actionTemplate() : actionTemplate, /*#__PURE__*/React.createElement(GridRefreshButton, {
275
+ id: id,
276
+ onRefresh: onRefresh,
277
+ t: t
278
+ }), fullScreen !== false && (isFullScreen ? /*#__PURE__*/React.createElement(Minimize, {
273
279
  fontSize: 16,
274
280
  onClick: () => {
275
281
  setIsFullScreen(!isFullScreen);
@@ -1,5 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import React, { Fragment } from "react";
3
+ import GridRefreshButton from "../grid-component/GridRefreshButton";
3
4
  import { useCopyToClipboard } from 'usehooks-ts';
4
5
  import { checkDecimalSeparator, checkThousandSeparator, detectSeparators, findItemByKey, flattenArray, flattenData, getAllRowKey, getColIdsBetween, getDefaultValue, getFormat, getRowIdsBetween, getSelectedCellMatrix, getTableHeight, isEditable, isFormattedNumber, newGuid, sumSize,
5
6
  // sumSize,
@@ -102,6 +103,7 @@ const TableContainerEdit = props => {
102
103
  pagination,
103
104
  showToolbar,
104
105
  actionTemplate,
106
+ onRefresh,
105
107
  showColumnChoose,
106
108
  height,
107
109
  minHeight,
@@ -2112,7 +2114,7 @@ const TableContainerEdit = props => {
2112
2114
  return /*#__PURE__*/React.createElement("div", {
2113
2115
  ref: containerRef,
2114
2116
  id: id
2115
- }, (showToolbar !== false || fullScreen !== false || showColumnChoose || title) && /*#__PURE__*/React.createElement("div", {
2117
+ }, (showToolbar !== false || fullScreen !== false || showColumnChoose || title || onRefresh) && /*#__PURE__*/React.createElement("div", {
2116
2118
  ref: topToolbarRef,
2117
2119
  className: classNames(`${prefix}-grid-top-toolbar`, {})
2118
2120
  }, /*#__PURE__*/React.createElement("div", {
@@ -2157,7 +2159,11 @@ const TableContainerEdit = props => {
2157
2159
  }, /*#__PURE__*/React.createElement(Toolbar, {
2158
2160
  items: toolbarItemTopRight,
2159
2161
  mode: 'responsive'
2160
- })), fullScreen !== false && (isFullScreen ? /*#__PURE__*/React.createElement(Minimize, {
2162
+ })), /*#__PURE__*/React.createElement(GridRefreshButton, {
2163
+ id: id,
2164
+ onRefresh: onRefresh,
2165
+ t: t
2166
+ }), fullScreen !== false && (isFullScreen ? /*#__PURE__*/React.createElement(Minimize, {
2161
2167
  fontSize: 16,
2162
2168
  onClick: () => {
2163
2169
  setIsFullScreen(!isFullScreen);
@@ -196,10 +196,10 @@ export declare const fixColumnsLeft: <RecordType>(columns: ColumnTable<RecordTyp
196
196
  value: any;
197
197
  rowData: RecordType;
198
198
  }) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
199
- onCellStyles?: Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width">);
200
- onCellHeaderStyles?: Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width">);
199
+ onCellStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
200
+ onCellHeaderStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
201
201
  onCell?: (rowData: RecordType, index: number) => import("react").TdHTMLAttributes<HTMLTableCellElement>;
202
- onCellFooterStyles?: Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width">);
202
+ onCellFooterStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
203
203
  getValue?: (row: any, rowIndex: number) => any;
204
204
  getCellProps?: (value: any, row: any, rowIndex: number) => import("./../../grid-component/type").CellProps;
205
205
  headerCellProps?: import("./../../grid-component/type").CellProps;
@@ -238,6 +238,7 @@ export function groupArrayByColumns(arr, columns) {
238
238
  parentId: !isEmpty(item[columns[index - 1]]) ? item[columns[index - 1]] : null,
239
239
  // parentId: item.rowId[index - 1],
240
240
  // indent: index,
241
+ isGroupRow: true,
241
242
  children: []
242
243
  };
243
244
  currentLevel.push(newItem);
@@ -197,7 +197,7 @@ const Grid = props => {
197
197
  rowSelected?.({
198
198
  type: isSelectionChange.type,
199
199
  rowData: isSelectionChange.rowData,
200
- selected: rs
200
+ selected: rs.filter(x => !x.isGroupRow)
201
201
  });
202
202
  }
203
203
  }, [isSelectionChange, rowSelection, table.getState().rowSelection]);
@@ -5,6 +5,7 @@ import { TableContext } from "./useContext";
5
5
  import Pagination from 'rc-master-ui/es/pagination';
6
6
  import { Toolbar } from 'rc-master-ui';
7
7
  import { Maximize, Minimize } from 'becoxy-icons';
8
+ import GridRefreshButton from "../grid-component/GridRefreshButton";
8
9
  import { numericFormatter } from 'react-numeric-component';
9
10
  import TableWrapper from "./table/TableWrapper";
10
11
  import { getTableHeight, sumSize, updateColumnWidthsRecursive } from "./hook/utils";
@@ -43,6 +44,7 @@ const TableContainer = props => {
43
44
  toolbarItems,
44
45
  toolbarMode,
45
46
  actionTemplate,
47
+ onRefresh,
46
48
  pagination,
47
49
  fullScreen,
48
50
  showColumnChoose,
@@ -232,7 +234,7 @@ const TableContainer = props => {
232
234
  return /*#__PURE__*/React.createElement("div", {
233
235
  ref: containerRef,
234
236
  id: id
235
- }, (showToolbar !== false || fullScreen !== false || title) && /*#__PURE__*/React.createElement("div", {
237
+ }, (showToolbar !== false || fullScreen !== false || title || onRefresh) && /*#__PURE__*/React.createElement("div", {
236
238
  ref: topToolbarRef,
237
239
  className: classNames(`${prefix}-grid-top-toolbar`, {})
238
240
  }, /*#__PURE__*/React.createElement("div", {
@@ -275,7 +277,11 @@ const TableContainer = props => {
275
277
  }, /*#__PURE__*/React.createElement(Toolbar, {
276
278
  items: toolbarItemTopRight,
277
279
  mode: toolbarMode
278
- })), fullScreen !== false && (isFullScreen ? /*#__PURE__*/React.createElement(Minimize, {
280
+ })), /*#__PURE__*/React.createElement(GridRefreshButton, {
281
+ id: id,
282
+ onRefresh: onRefresh,
283
+ t: t
284
+ }), fullScreen !== false && (isFullScreen ? /*#__PURE__*/React.createElement(Minimize, {
279
285
  fontSize: 16,
280
286
  onClick: () => {
281
287
  setIsFullScreen(!isFullScreen);
@@ -303,6 +303,7 @@ export function groupArrayByColumns(arr, columns) {
303
303
  parentId: !isEmpty(item[columns[index - 1]]) ? item[columns[index - 1]] : null,
304
304
  // parentId: item.rowId[index - 1],
305
305
  // indent: index,
306
+ isGroupRow: true,
306
307
  children: []
307
308
  };
308
309
  currentLevel.push(newItem);
@@ -269,7 +269,7 @@ const Grid = props => {
269
269
  rowSelected?.({
270
270
  type: isSelectionChange.type,
271
271
  rowData: isSelectionChange.rowData,
272
- selected: ssss
272
+ selected: ssss.filter(x => !x.isGroupRow)
273
273
  });
274
274
  }
275
275
  }, [isSelectionChange, rowSelection, table.getState().rowSelection]);
@@ -105,6 +105,8 @@ const InternalTable = props => {
105
105
  const [columns, setColumns] = React.useState([]);
106
106
  const [tableData, setTableData] = React.useState([]);
107
107
  const [expanded, setExpanded] = React.useState({});
108
+ // Đánh dấu đã áp dụng expansion mặc định, tránh mở lại nhánh người dùng đóng khi task cập nhật.
109
+ const initializedExpansionRef = React.useRef(false);
108
110
 
109
111
  // const [visible, setVisible] = React.useState({visible: false, column: ''})
110
112
 
@@ -209,7 +211,7 @@ const InternalTable = props => {
209
211
  expanded,
210
212
  columnPinning,
211
213
  columnVisibility: columnsHiddenKeys ? convertToObj(columnsHiddenKeys) : columnVisibility,
212
- pagination: pagination && !infiniteScroll ? paginationState : undefined,
214
+ pagination: paginationState,
213
215
  // grouping,
214
216
  columnSizing,
215
217
  // columnOrder,
@@ -266,7 +268,8 @@ const InternalTable = props => {
266
268
  getGroupedRowModel: getGroupedRowModel(),
267
269
  // onExpandedChange: setExpanded,
268
270
  getExpandedRowModel: getExpandedRowModel(),
269
- manualPagination: isServerPagination,
271
+ // Khi không bật phân trang client, giữ nguyên toàn bộ dữ liệu BE/viewport và tránh row model phân trang cũ được dùng lại.
272
+ manualPagination: !shouldUseClientPagination,
270
273
  // Chỉ để TanStack tự phân trang khi có pagination, không dùng infinite scroll và đang ở chế độ client.
271
274
  // Với server pagination, dữ liệu đã được BE phân trang nên không chạy getPaginationRowModel() để tránh phân trang lần 2.
272
275
  getPaginationRowModel: shouldUseClientPagination ? getPaginationRowModel() : undefined,
@@ -277,6 +280,13 @@ const InternalTable = props => {
277
280
  // debugTable: true
278
281
  });
279
282
  React.useEffect(() => {
283
+ if (expandable?.preserveExpandedOnDataChange) {
284
+ // Chờ cây có dữ liệu mới khởi tạo; những lần thay đổi tableData sau đó không reset expanded.
285
+ if (initializedExpansionRef.current || tableData.length === 0) {
286
+ return;
287
+ }
288
+ initializedExpansionRef.current = true;
289
+ }
280
290
  if (defaultExpandAllRows) {
281
291
  const allKeys = findAllChildrenKeys2(tableData, rowKey, 'children');
282
292
  setExpanded(convertToObjTrue(allKeys));
@@ -285,7 +295,7 @@ const InternalTable = props => {
285
295
  setExpanded(convertToObjTrue(defaultExpandedRowKeys ?? []));
286
296
  }
287
297
  }
288
- }, [defaultExpandAllRows, defaultExpandedRowKeys, tableData]);
298
+ }, [defaultExpandAllRows, defaultExpandedRowKeys, tableData, expandable?.preserveExpandedOnDataChange]);
289
299
  const triggerGroupColumns = groupedColumns => {
290
300
  // const abc = updateColumnsByGroup(columns, groupedColumns)
291
301
 
@@ -257,6 +257,7 @@ export function groupArrayByColumns(arr, columns) {
257
257
  id: groupRowId,
258
258
  parentId: parentChain[index - 1] ?? null,
259
259
  // Parent là rowId của level trước
260
+ isGroupRow: true,
260
261
  children: []
261
262
  };
262
263
  currentLevel.push(newItem);
@@ -85,7 +85,7 @@ const Grid = props => {
85
85
  rowSelected?.({
86
86
  type: isSelectionChange.type,
87
87
  rowData: isSelectionChange.rowData,
88
- selected: ssss
88
+ selected: ssss.filter(x => !x.isGroupRow)
89
89
  });
90
90
  }
91
91
  }, [isSelectionChange, rowSelection, table.getState().rowSelection]);
@@ -4,6 +4,7 @@ import classNames from 'classnames';
4
4
  import Pagination from 'rc-master-ui/es/pagination';
5
5
  import { Toolbar } from 'rc-master-ui';
6
6
  import { Collapse2, Expand2, Maximize, Minimize } from 'becoxy-icons';
7
+ import GridRefreshButton from "../../grid-component/GridRefreshButton";
7
8
  import { numericFormatter } from 'react-numeric-component';
8
9
  import TableWrapper from "./TableWrapper";
9
10
  import { ColumnsChoose } from "../../table-component/ColumnsChoose";
@@ -42,6 +43,7 @@ const TableContainer = props => {
42
43
  showToolbar,
43
44
  toolbarItems,
44
45
  actionTemplate,
46
+ onRefresh,
45
47
  pagination,
46
48
  fullScreen,
47
49
  showColumnChoose,
@@ -184,7 +186,7 @@ const TableContainer = props => {
184
186
  return /*#__PURE__*/React.createElement("div", {
185
187
  ref: containerRef,
186
188
  id: id
187
- }, (showToolbar !== false || fullScreen !== false || title) && /*#__PURE__*/React.createElement("div", {
189
+ }, (showToolbar !== false || fullScreen !== false || title || onRefresh) && /*#__PURE__*/React.createElement("div", {
188
190
  ref: topToolbarRef,
189
191
  className: classNames(`${prefix}-grid-top-toolbar`, {})
190
192
  }, /*#__PURE__*/React.createElement("div", {
@@ -228,7 +230,11 @@ const TableContainer = props => {
228
230
  }, /*#__PURE__*/React.createElement(Toolbar, {
229
231
  items: toolbarItemTopRight,
230
232
  mode: toolbarMode
231
- })), fullScreen !== false && (isFullScreen ? /*#__PURE__*/React.createElement(Minimize, {
233
+ })), /*#__PURE__*/React.createElement(GridRefreshButton, {
234
+ id: id,
235
+ onRefresh: onRefresh,
236
+ t: t
237
+ }), fullScreen !== false && (isFullScreen ? /*#__PURE__*/React.createElement(Minimize, {
232
238
  fontSize: 16,
233
239
  onClick: () => {
234
240
  setIsFullScreen(!isFullScreen);
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { ColumnDef, Table } from "@tanstack/react-table";
3
- import type { CommandClick, ContextInfo, ContextMenuItem, PaginationConfig } from '../../grid-component/type';
3
+ import type { CommandClick, ContextInfo, ContextMenuItem, PaginationConfig, TableProps } from '../../grid-component/type';
4
4
  type Props<T> = {
5
5
  tableContainerRef: React.RefObject<HTMLDivElement>;
6
6
  table: Table<T>;
@@ -22,6 +22,7 @@ type Props<T> = {
22
22
  next?: () => void;
23
23
  pagination?: false | PaginationConfig;
24
24
  infiniteScroll?: boolean;
25
+ onVisibleRowsChange?: TableProps<T>['onVisibleRowsChange'];
25
26
  };
26
27
  declare const TableWrapper: <RecordType extends object>(props: Props<RecordType>) => JSX.Element;
27
28
  export default TableWrapper;
@@ -21,6 +21,7 @@ const TableWrapper = props => {
21
21
  contextMenuHidden,
22
22
  contextMenuClick,
23
23
  contextMenuOpen,
24
+ onVisibleRowsChange,
24
25
  summary
25
26
  } = props;
26
27
  const menuRef = React.useRef(null);
@@ -124,11 +125,24 @@ const TableWrapper = props => {
124
125
  height,
125
126
  position: "relative"
126
127
  }
127
- }, /*#__PURE__*/React.createElement(TableVirtuoso, {
128
+ }, /*#__PURE__*/React.createElement(TableVirtuoso, _extends({
128
129
  data: table.getRowModel().rows,
129
130
  totalCount: rows.length
131
+ }, onVisibleRowsChange ? {
132
+ computeItemKey: (_index, row) => row.id,
133
+ rangeChanged: ({
134
+ startIndex,
135
+ endIndex
136
+ }) => {
137
+ // Dùng row ID sau expand/collapse của TanStack; index không phải vị trí trong cây dữ liệu gốc.
138
+ onVisibleRowsChange({
139
+ rowIds: rows.slice(startIndex, endIndex + 1).map(row => row.id),
140
+ startIndex,
141
+ endIndex
142
+ });
143
+ }
144
+ } : {}, {
130
145
  // isScrolling={}
131
- ,
132
146
  components: {
133
147
  Table: ({
134
148
  style,
@@ -242,7 +256,7 @@ const TableWrapper = props => {
242
256
  });
243
257
  }));
244
258
  } : undefined
245
- })), /*#__PURE__*/React.createElement(Tooltip, {
259
+ }))), /*#__PURE__*/React.createElement(Tooltip, {
246
260
  id: `${id}-tooltip-content`,
247
261
  style: {
248
262
  zIndex: 1999,
@@ -541,7 +541,7 @@ const InternalTable = props => {
541
541
  rowSelected?.({
542
542
  type,
543
543
  rowData,
544
- selected: rs
544
+ selected: rs.filter(x => !x.isGroupRow)
545
545
  });
546
546
  }
547
547
 
@@ -0,0 +1,9 @@
1
+ .ui-rc-grid-refresh-spin {
2
+ transform-origin: center;
3
+ transform-box: fill-box;
4
+ animation: ui-rc-grid-refresh-spin 0.8s linear infinite;
5
+ }
6
+
7
+ @keyframes ui-rc-grid-refresh-spin {
8
+ to { transform: rotate(360deg); }
9
+ }
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+ import './GridRefreshButton.css';
3
+ import type { TableProps } from './type';
4
+ type Props = Pick<TableProps<object>, 'onRefresh' | 't'> & {
5
+ id: string;
6
+ };
7
+ declare const GridRefreshButton: ({ id, onRefresh, t }: Props) => JSX.Element;
8
+ export default GridRefreshButton;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _react = _interopRequireDefault(require("react"));
9
+ var _classnames = _interopRequireDefault(require("classnames"));
10
+ var _becoxyIcons = require("becoxy-icons");
11
+ require("./GridRefreshButton.css");
12
+ const GridRefreshButton = ({
13
+ id,
14
+ onRefresh,
15
+ t
16
+ }) => {
17
+ const [isRefreshing, setIsRefreshing] = _react.default.useState(false);
18
+ const refreshPendingRef = _react.default.useRef(false);
19
+ if (!onRefresh) {
20
+ return null;
21
+ }
22
+ const handleRefresh = async () => {
23
+ if (refreshPendingRef.current) {
24
+ return;
25
+ }
26
+ refreshPendingRef.current = true;
27
+ setIsRefreshing(true);
28
+ try {
29
+ await onRefresh();
30
+ } catch (error) {
31
+ console.error('Grid refresh failed:', error);
32
+ } finally {
33
+ refreshPendingRef.current = false;
34
+ setIsRefreshing(false);
35
+ }
36
+ };
37
+ const label = String(t?.('RefreshData') ?? 'Refresh');
38
+ return /*#__PURE__*/_react.default.createElement(_becoxyIcons.RefreshCw, {
39
+ className: (0, _classnames.default)('toolbar-icon', {
40
+ 'ui-rc-grid-refresh-spin': isRefreshing
41
+ }),
42
+ "data-tooltip-id": `${id}-tooltip-content`,
43
+ "data-tooltip-content": label,
44
+ color: isRefreshing ? '#9B9B9B' : '#555555',
45
+ fontSize: 21,
46
+ style: {
47
+ cursor: isRefreshing ? 'default' : 'pointer'
48
+ },
49
+ onClick: () => {
50
+ void handleRefresh();
51
+ }
52
+ });
53
+ };
54
+ var _default = exports.default = GridRefreshButton;
@@ -1,4 +1,4 @@
1
1
  /// <reference types="react" />
2
- import type { TableProps } from "../grid-component";
2
+ import type { TableProps } from '../grid-component';
3
3
  declare const TempTable: <RecordType extends object>(props: TableProps<RecordType>) => JSX.Element;
4
4
  export default TempTable;
@@ -2541,6 +2541,7 @@ function groupArrayByColumns(data, columns) {
2541
2541
  rowId: newGuid(),
2542
2542
  parentId: !isEmpty(item[columns[index - 1]]) ? item[columns[index - 1]] : null,
2543
2543
  // tự tính tổng rồi điền thuộc tính vào
2544
+ isGroupRow: true,
2544
2545
  children: []
2545
2546
  };
2546
2547
  currentLevel.push(newItem);
@@ -380,6 +380,18 @@ export type TableProps<RecordType = AnyObject> = {
380
380
  infiniteScroll?: boolean;
381
381
  /** Hàm gọi khi cuộn đến cuối */
382
382
  next?: () => void;
383
+ /**
384
+ * Thông báo khi phạm vi dòng đang render thay đổi (cuộn, mở/đóng cây hoặc cập nhật dữ liệu).
385
+ * - `rowIds`: ID dòng TanStack sau khi expand/collapse, gồm cả vùng đệm (overscan).
386
+ * - `startIndex` / `endIndex`: vị trí đầu/cuối trong danh sách dòng đang hiển thị (bao gồm hai đầu).
387
+ * Dùng để xác định dòng cần tải dữ liệu; callback không tự gọi API.
388
+ * Chỉ hỗ trợ khi `groupAble=true` và `editAble=false`. Không truyền thì giữ hành vi cũ.
389
+ */
390
+ onVisibleRowsChange?: (range: {
391
+ rowIds: string[];
392
+ startIndex: number;
393
+ endIndex: number;
394
+ }) => void;
383
395
  /** Ngưỡng trigger load thêm dữ liệu */
384
396
  onEndReachedThreshold?: number;
385
397
  /** Cho phép group dữ liệu */
@@ -388,6 +400,12 @@ export type TableProps<RecordType = AnyObject> = {
388
400
  groupColumns?: string[];
389
401
  /** Cấu hình group */
390
402
  groupSetting?: IGroupSetting;
403
+ /**
404
+ * Hiện nút làm mới trên toolbar khi truyền callback.
405
+ * Grid không tự gọi API: consumer quyết định dữ liệu cần tải lại.
406
+ * Hỗ trợ Promise (ví dụ refetch() của TanStack Query) để tự khóa nút khi đang tải.
407
+ */
408
+ onRefresh?: () => void | Promise<unknown>;
391
409
  /** Hiển thị toolbar */
392
410
  showToolbar?: boolean;
393
411
  /** Các action trên toolbar
@@ -550,6 +568,14 @@ export type ExpandableConfig<RecordType> = {
550
568
  onExpand?: (expanded: boolean, rowData: RecordType) => void;
551
569
  onExpandedRowsChange?: (expandedKeys: readonly Key[]) => void;
552
570
  defaultExpandAllRows?: boolean;
571
+ /**
572
+ * Giữ trạng thái mở/đóng cây do người dùng thao tác khi `dataSource` cập nhật.
573
+ * Khi `true`, chỉ áp dụng `defaultExpandAllRows` / `defaultExpandedRowKeys` một lần
574
+ * sau khi có dữ liệu; không tự mở lại những nhánh đã đóng khi tải thêm dòng.
575
+ * Mặc định `false`: dữ liệu thay đổi vẫn áp dụng lại trạng thái mở rộng mặc định như trước.
576
+ * Chỉ hỗ trợ Grid nhóm không chỉnh sửa (`groupAble=true`, `editAble=false`).
577
+ */
578
+ preserveExpandedOnDataChange?: boolean;
553
579
  expandIconColumnIndex?: number;
554
580
  showExpandAll?: boolean;
555
581
  showExpandColumn?: boolean;
@@ -12,6 +12,7 @@ var _useContext = require("./useContext");
12
12
  var _pagination = _interopRequireDefault(require("rc-master-ui/es/pagination"));
13
13
  var _rcMasterUi = require("rc-master-ui");
14
14
  var _becoxyIcons = require("becoxy-icons");
15
+ var _GridRefreshButton = _interopRequireDefault(require("../grid-component/GridRefreshButton"));
15
16
  var _reactNumericComponent = require("react-numeric-component");
16
17
  var _TableWrapper = _interopRequireDefault(require("./table/TableWrapper"));
17
18
  var _utils = require("./hook/utils");
@@ -50,6 +51,7 @@ const TableContainer = props => {
50
51
  showToolbar,
51
52
  toolbarItems,
52
53
  actionTemplate,
54
+ onRefresh,
53
55
  pagination,
54
56
  fullScreen,
55
57
  showColumnChoose,
@@ -240,7 +242,7 @@ const TableContainer = props => {
240
242
  return /*#__PURE__*/_react.default.createElement("div", {
241
243
  ref: containerRef,
242
244
  id: id
243
- }, (showToolbar !== false || fullScreen !== false || title) && /*#__PURE__*/_react.default.createElement("div", {
245
+ }, (showToolbar !== false || fullScreen !== false || title || onRefresh) && /*#__PURE__*/_react.default.createElement("div", {
244
246
  ref: topToolbarRef,
245
247
  className: (0, _classnames.default)(`${prefix}-grid-top-toolbar`, {})
246
248
  }, /*#__PURE__*/_react.default.createElement("div", {
@@ -277,7 +279,11 @@ const TableContainer = props => {
277
279
  showTotal: (totalItems, range) =>
278
280
  // @ts-ignore
279
281
  `${range[0]}-${range[1]} / ${totalItems} ${t ? t(pagination?.locale?.items ?? 'items') : 'items'}`
280
- }, pagination)), typeof actionTemplate === 'function' ? actionTemplate() : actionTemplate, fullScreen !== false && (isFullScreen ? /*#__PURE__*/_react.default.createElement(_becoxyIcons.Minimize, {
282
+ }, pagination)), typeof actionTemplate === 'function' ? actionTemplate() : actionTemplate, /*#__PURE__*/_react.default.createElement(_GridRefreshButton.default, {
283
+ id: id,
284
+ onRefresh: onRefresh,
285
+ t: t
286
+ }), fullScreen !== false && (isFullScreen ? /*#__PURE__*/_react.default.createElement(_becoxyIcons.Minimize, {
281
287
  fontSize: 16,
282
288
  onClick: () => {
283
289
  setIsFullScreen(!isFullScreen);
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.default = void 0;
8
8
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
9
  var _react = _interopRequireWildcard(require("react"));
10
+ var _GridRefreshButton = _interopRequireDefault(require("../grid-component/GridRefreshButton"));
10
11
  var _usehooksTs = require("usehooks-ts");
11
12
  var _utils = require("./hook/utils");
12
13
  var _reactVirtual = require("@tanstack/react-virtual");
@@ -110,6 +111,7 @@ const TableContainerEdit = props => {
110
111
  pagination,
111
112
  showToolbar,
112
113
  actionTemplate,
114
+ onRefresh,
113
115
  showColumnChoose,
114
116
  height,
115
117
  minHeight,
@@ -2120,7 +2122,7 @@ const TableContainerEdit = props => {
2120
2122
  return /*#__PURE__*/_react.default.createElement("div", {
2121
2123
  ref: containerRef,
2122
2124
  id: id
2123
- }, (showToolbar !== false || fullScreen !== false || showColumnChoose || title) && /*#__PURE__*/_react.default.createElement("div", {
2125
+ }, (showToolbar !== false || fullScreen !== false || showColumnChoose || title || onRefresh) && /*#__PURE__*/_react.default.createElement("div", {
2124
2126
  ref: topToolbarRef,
2125
2127
  className: (0, _classnames.default)(`${prefix}-grid-top-toolbar`, {})
2126
2128
  }, /*#__PURE__*/_react.default.createElement("div", {
@@ -2165,7 +2167,11 @@ const TableContainerEdit = props => {
2165
2167
  }, /*#__PURE__*/_react.default.createElement(_rcMasterUi.Toolbar, {
2166
2168
  items: toolbarItemTopRight,
2167
2169
  mode: 'responsive'
2168
- })), fullScreen !== false && (isFullScreen ? /*#__PURE__*/_react.default.createElement(_becoxyIcons.Minimize, {
2170
+ })), /*#__PURE__*/_react.default.createElement(_GridRefreshButton.default, {
2171
+ id: id,
2172
+ onRefresh: onRefresh,
2173
+ t: t
2174
+ }), fullScreen !== false && (isFullScreen ? /*#__PURE__*/_react.default.createElement(_becoxyIcons.Minimize, {
2169
2175
  fontSize: 16,
2170
2176
  onClick: () => {
2171
2177
  setIsFullScreen(!isFullScreen);
@@ -167,7 +167,7 @@ export declare const fixColumnsLeft: <RecordType>(columns: ColumnTable<RecordTyp
167
167
  haveSum?: boolean;
168
168
  isSummary?: boolean;
169
169
  sumGroup?: boolean;
170
- summaryTemplate?: (data: number, key: string) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
170
+ summaryTemplate?: (data: number, key: string) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode;
171
171
  sorter?: boolean;
172
172
  allowFiltering?: boolean;
173
173
  allowSortering?: boolean;
@@ -179,27 +179,27 @@ export declare const fixColumnsLeft: <RecordType>(columns: ColumnTable<RecordTyp
179
179
  showFilterSearch?: boolean;
180
180
  hidden?: boolean;
181
181
  visible?: boolean;
182
- headerTooltip?: string | boolean | (() => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
182
+ headerTooltip?: string | boolean | (() => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
183
183
  columnGroupText?: string;
184
184
  headerTextAlign?: import("./../../grid-component/type").ITextAlign;
185
- headerTemplate?: import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | ((column: ColumnTable<RecordType>) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
185
+ headerTemplate?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode | ((column: ColumnTable<RecordType>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
186
186
  fixedType?: import("./../../grid-component/type").FixedType;
187
187
  headerTextWrap?: boolean;
188
188
  ellipsis?: boolean;
189
189
  allowResizing?: boolean;
190
190
  align?: import("./../../grid-component/type").ITextAlign;
191
191
  textAlign?: import("./../../grid-component/type").ITextAlign;
192
- template?: import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | ((args: import("./../../grid-component/type").ColumnTemplate<RecordType>) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
193
- linkTemplate?: import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | ((args: import("./../../grid-component/type").ColumnTemplate<RecordType>) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
192
+ template?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode | ((args: import("./../../grid-component/type").ColumnTemplate<RecordType>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
193
+ linkTemplate?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode | ((args: import("./../../grid-component/type").ColumnTemplate<RecordType>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
194
194
  showTooltip?: boolean;
195
195
  tooltipDescription?: string | ((args: {
196
196
  value: any;
197
197
  rowData: RecordType;
198
- }) => import("react").ReactNode | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>);
199
- onCellStyles?: Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width">);
200
- onCellHeaderStyles?: Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width">);
198
+ }) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
199
+ onCellStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
200
+ onCellHeaderStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
201
201
  onCell?: (rowData: RecordType, index: number) => import("react").TdHTMLAttributes<HTMLTableCellElement>;
202
- onCellFooterStyles?: Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "left" | "right" | "display" | "minWidth" | "position" | "width">);
202
+ onCellFooterStyles?: Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "width" | "minWidth" | "left" | "right" | "position" | "display">);
203
203
  getValue?: (row: any, rowIndex: number) => any;
204
204
  getCellProps?: (value: any, row: any, rowIndex: number) => import("./../../grid-component/type").CellProps;
205
205
  headerCellProps?: import("./../../grid-component/type").CellProps;
@@ -315,6 +315,7 @@ function groupArrayByColumns(arr, columns) {
315
315
  parentId: !isEmpty(item[columns[index - 1]]) ? item[columns[index - 1]] : null,
316
316
  // parentId: item.rowId[index - 1],
317
317
  // indent: index,
318
+ isGroupRow: true,
318
319
  children: []
319
320
  };
320
321
  currentLevel.push(newItem);
@@ -203,7 +203,7 @@ const Grid = props => {
203
203
  rowSelected?.({
204
204
  type: isSelectionChange.type,
205
205
  rowData: isSelectionChange.rowData,
206
- selected: rs
206
+ selected: rs.filter(x => !x.isGroupRow)
207
207
  });
208
208
  }
209
209
  }, [isSelectionChange, rowSelection, table.getState().rowSelection]);
@@ -12,6 +12,7 @@ var _useContext = require("./useContext");
12
12
  var _pagination = _interopRequireDefault(require("rc-master-ui/es/pagination"));
13
13
  var _rcMasterUi = require("rc-master-ui");
14
14
  var _becoxyIcons = require("becoxy-icons");
15
+ var _GridRefreshButton = _interopRequireDefault(require("../grid-component/GridRefreshButton"));
15
16
  var _reactNumericComponent = require("react-numeric-component");
16
17
  var _TableWrapper = _interopRequireDefault(require("./table/TableWrapper"));
17
18
  var _utils = require("./hook/utils");
@@ -50,6 +51,7 @@ const TableContainer = props => {
50
51
  toolbarItems,
51
52
  toolbarMode,
52
53
  actionTemplate,
54
+ onRefresh,
53
55
  pagination,
54
56
  fullScreen,
55
57
  showColumnChoose,
@@ -239,7 +241,7 @@ const TableContainer = props => {
239
241
  return /*#__PURE__*/_react.default.createElement("div", {
240
242
  ref: containerRef,
241
243
  id: id
242
- }, (showToolbar !== false || fullScreen !== false || title) && /*#__PURE__*/_react.default.createElement("div", {
244
+ }, (showToolbar !== false || fullScreen !== false || title || onRefresh) && /*#__PURE__*/_react.default.createElement("div", {
243
245
  ref: topToolbarRef,
244
246
  className: (0, _classnames.default)(`${prefix}-grid-top-toolbar`, {})
245
247
  }, /*#__PURE__*/_react.default.createElement("div", {
@@ -282,7 +284,11 @@ const TableContainer = props => {
282
284
  }, /*#__PURE__*/_react.default.createElement(_rcMasterUi.Toolbar, {
283
285
  items: toolbarItemTopRight,
284
286
  mode: toolbarMode
285
- })), fullScreen !== false && (isFullScreen ? /*#__PURE__*/_react.default.createElement(_becoxyIcons.Minimize, {
287
+ })), /*#__PURE__*/_react.default.createElement(_GridRefreshButton.default, {
288
+ id: id,
289
+ onRefresh: onRefresh,
290
+ t: t
291
+ }), fullScreen !== false && (isFullScreen ? /*#__PURE__*/_react.default.createElement(_becoxyIcons.Minimize, {
286
292
  fontSize: 16,
287
293
  onClick: () => {
288
294
  setIsFullScreen(!isFullScreen);
@@ -399,6 +399,7 @@ function groupArrayByColumns(arr, columns) {
399
399
  parentId: !isEmpty(item[columns[index - 1]]) ? item[columns[index - 1]] : null,
400
400
  // parentId: item.rowId[index - 1],
401
401
  // indent: index,
402
+ isGroupRow: true,
402
403
  children: []
403
404
  };
404
405
  currentLevel.push(newItem);
@@ -274,7 +274,7 @@ const Grid = props => {
274
274
  rowSelected?.({
275
275
  type: isSelectionChange.type,
276
276
  rowData: isSelectionChange.rowData,
277
- selected: ssss
277
+ selected: ssss.filter(x => !x.isGroupRow)
278
278
  });
279
279
  }
280
280
  }, [isSelectionChange, rowSelection, table.getState().rowSelection]);
@@ -115,6 +115,8 @@ const InternalTable = props => {
115
115
  const [columns, setColumns] = _react.default.useState([]);
116
116
  const [tableData, setTableData] = _react.default.useState([]);
117
117
  const [expanded, setExpanded] = _react.default.useState({});
118
+ // Đánh dấu đã áp dụng expansion mặc định, tránh mở lại nhánh người dùng đóng khi task cập nhật.
119
+ const initializedExpansionRef = _react.default.useRef(false);
118
120
 
119
121
  // const [visible, setVisible] = React.useState({visible: false, column: ''})
120
122
 
@@ -219,7 +221,7 @@ const InternalTable = props => {
219
221
  expanded,
220
222
  columnPinning,
221
223
  columnVisibility: columnsHiddenKeys ? (0, _utils.convertToObj)(columnsHiddenKeys) : columnVisibility,
222
- pagination: pagination && !infiniteScroll ? paginationState : undefined,
224
+ pagination: paginationState,
223
225
  // grouping,
224
226
  columnSizing,
225
227
  // columnOrder,
@@ -276,7 +278,8 @@ const InternalTable = props => {
276
278
  getGroupedRowModel: (0, _reactTable.getGroupedRowModel)(),
277
279
  // onExpandedChange: setExpanded,
278
280
  getExpandedRowModel: (0, _reactTable.getExpandedRowModel)(),
279
- manualPagination: isServerPagination,
281
+ // Khi không bật phân trang client, giữ nguyên toàn bộ dữ liệu BE/viewport và tránh row model phân trang cũ được dùng lại.
282
+ manualPagination: !shouldUseClientPagination,
280
283
  // Chỉ để TanStack tự phân trang khi có pagination, không dùng infinite scroll và đang ở chế độ client.
281
284
  // Với server pagination, dữ liệu đã được BE phân trang nên không chạy getPaginationRowModel() để tránh phân trang lần 2.
282
285
  getPaginationRowModel: shouldUseClientPagination ? (0, _reactTable.getPaginationRowModel)() : undefined,
@@ -287,6 +290,13 @@ const InternalTable = props => {
287
290
  // debugTable: true
288
291
  });
289
292
  _react.default.useEffect(() => {
293
+ if (expandable?.preserveExpandedOnDataChange) {
294
+ // Chờ cây có dữ liệu mới khởi tạo; những lần thay đổi tableData sau đó không reset expanded.
295
+ if (initializedExpansionRef.current || tableData.length === 0) {
296
+ return;
297
+ }
298
+ initializedExpansionRef.current = true;
299
+ }
290
300
  if (defaultExpandAllRows) {
291
301
  const allKeys = (0, _utils.findAllChildrenKeys2)(tableData, rowKey, 'children');
292
302
  setExpanded((0, _utils.convertToObjTrue)(allKeys));
@@ -295,7 +305,7 @@ const InternalTable = props => {
295
305
  setExpanded((0, _utils.convertToObjTrue)(defaultExpandedRowKeys ?? []));
296
306
  }
297
307
  }
298
- }, [defaultExpandAllRows, defaultExpandedRowKeys, tableData]);
308
+ }, [defaultExpandAllRows, defaultExpandedRowKeys, tableData, expandable?.preserveExpandedOnDataChange]);
299
309
  const triggerGroupColumns = groupedColumns => {
300
310
  // const abc = updateColumnsByGroup(columns, groupedColumns)
301
311
 
@@ -341,6 +341,7 @@ function groupArrayByColumns(arr, columns) {
341
341
  id: groupRowId,
342
342
  parentId: parentChain[index - 1] ?? null,
343
343
  // Parent là rowId của level trước
344
+ isGroupRow: true,
344
345
  children: []
345
346
  };
346
347
  currentLevel.push(newItem);
@@ -94,7 +94,7 @@ const Grid = props => {
94
94
  rowSelected?.({
95
95
  type: isSelectionChange.type,
96
96
  rowData: isSelectionChange.rowData,
97
- selected: ssss
97
+ selected: ssss.filter(x => !x.isGroupRow)
98
98
  });
99
99
  }
100
100
  }, [isSelectionChange, rowSelection, table.getState().rowSelection]);
@@ -11,6 +11,7 @@ var _classnames = _interopRequireDefault(require("classnames"));
11
11
  var _pagination = _interopRequireDefault(require("rc-master-ui/es/pagination"));
12
12
  var _rcMasterUi = require("rc-master-ui");
13
13
  var _becoxyIcons = require("becoxy-icons");
14
+ var _GridRefreshButton = _interopRequireDefault(require("../../grid-component/GridRefreshButton"));
14
15
  var _reactNumericComponent = require("react-numeric-component");
15
16
  var _TableWrapper = _interopRequireDefault(require("./TableWrapper"));
16
17
  var _ColumnsChoose = require("../../table-component/ColumnsChoose");
@@ -50,6 +51,7 @@ const TableContainer = props => {
50
51
  showToolbar,
51
52
  toolbarItems,
52
53
  actionTemplate,
54
+ onRefresh,
53
55
  pagination,
54
56
  fullScreen,
55
57
  showColumnChoose,
@@ -192,7 +194,7 @@ const TableContainer = props => {
192
194
  return /*#__PURE__*/_react.default.createElement("div", {
193
195
  ref: containerRef,
194
196
  id: id
195
- }, (showToolbar !== false || fullScreen !== false || title) && /*#__PURE__*/_react.default.createElement("div", {
197
+ }, (showToolbar !== false || fullScreen !== false || title || onRefresh) && /*#__PURE__*/_react.default.createElement("div", {
196
198
  ref: topToolbarRef,
197
199
  className: (0, _classnames.default)(`${prefix}-grid-top-toolbar`, {})
198
200
  }, /*#__PURE__*/_react.default.createElement("div", {
@@ -236,7 +238,11 @@ const TableContainer = props => {
236
238
  }, /*#__PURE__*/_react.default.createElement(_rcMasterUi.Toolbar, {
237
239
  items: toolbarItemTopRight,
238
240
  mode: toolbarMode
239
- })), fullScreen !== false && (isFullScreen ? /*#__PURE__*/_react.default.createElement(_becoxyIcons.Minimize, {
241
+ })), /*#__PURE__*/_react.default.createElement(_GridRefreshButton.default, {
242
+ id: id,
243
+ onRefresh: onRefresh,
244
+ t: t
245
+ }), fullScreen !== false && (isFullScreen ? /*#__PURE__*/_react.default.createElement(_becoxyIcons.Minimize, {
240
246
  fontSize: 16,
241
247
  onClick: () => {
242
248
  setIsFullScreen(!isFullScreen);
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { ColumnDef, Table } from "@tanstack/react-table";
3
- import type { CommandClick, ContextInfo, ContextMenuItem, PaginationConfig } from '../../grid-component/type';
3
+ import type { CommandClick, ContextInfo, ContextMenuItem, PaginationConfig, TableProps } from '../../grid-component/type';
4
4
  type Props<T> = {
5
5
  tableContainerRef: React.RefObject<HTMLDivElement>;
6
6
  table: Table<T>;
@@ -22,6 +22,7 @@ type Props<T> = {
22
22
  next?: () => void;
23
23
  pagination?: false | PaginationConfig;
24
24
  infiniteScroll?: boolean;
25
+ onVisibleRowsChange?: TableProps<T>['onVisibleRowsChange'];
25
26
  };
26
27
  declare const TableWrapper: <RecordType extends object>(props: Props<RecordType>) => JSX.Element;
27
28
  export default TableWrapper;
@@ -32,6 +32,7 @@ const TableWrapper = props => {
32
32
  contextMenuHidden,
33
33
  contextMenuClick,
34
34
  contextMenuOpen,
35
+ onVisibleRowsChange,
35
36
  summary
36
37
  } = props;
37
38
  const menuRef = _react.default.useRef(null);
@@ -135,11 +136,24 @@ const TableWrapper = props => {
135
136
  height,
136
137
  position: "relative"
137
138
  }
138
- }, /*#__PURE__*/_react.default.createElement(_reactVirtuoso.TableVirtuoso, {
139
+ }, /*#__PURE__*/_react.default.createElement(_reactVirtuoso.TableVirtuoso, (0, _extends2.default)({
139
140
  data: table.getRowModel().rows,
140
141
  totalCount: rows.length
142
+ }, onVisibleRowsChange ? {
143
+ computeItemKey: (_index, row) => row.id,
144
+ rangeChanged: ({
145
+ startIndex,
146
+ endIndex
147
+ }) => {
148
+ // Dùng row ID sau expand/collapse của TanStack; index không phải vị trí trong cây dữ liệu gốc.
149
+ onVisibleRowsChange({
150
+ rowIds: rows.slice(startIndex, endIndex + 1).map(row => row.id),
151
+ startIndex,
152
+ endIndex
153
+ });
154
+ }
155
+ } : {}, {
141
156
  // isScrolling={}
142
- ,
143
157
  components: {
144
158
  Table: ({
145
159
  style,
@@ -253,7 +267,7 @@ const TableWrapper = props => {
253
267
  });
254
268
  }));
255
269
  } : undefined
256
- })), /*#__PURE__*/_react.default.createElement(_reactTooltip.Tooltip, {
270
+ }))), /*#__PURE__*/_react.default.createElement(_reactTooltip.Tooltip, {
257
271
  id: `${id}-tooltip-content`,
258
272
  style: {
259
273
  zIndex: 1999,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "es-grid-template",
3
- "version": "1.9.90",
3
+ "version": "1.9.92",
4
4
  "description": "es-grid-template",
5
5
  "keywords": [
6
6
  "react",