es-grid-template 1.9.22 → 1.9.24
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.
- package/es/grid-component/type.d.ts +99 -9
- package/es/group-component/hook/utils.d.ts +3 -3
- package/es/table-component/TableContainer.js +9 -5
- package/es/table-component/TableContainerEdit.js +26 -20
- package/es/table-component/body/TableBodyCell.js +7 -1
- package/es/table-component/hook/utils.js +12 -4
- package/es/table-component/table/TableWrapper.js +1 -0
- package/lib/grid-component/type.d.ts +99 -9
- package/lib/table-component/TableContainer.js +9 -5
- package/lib/table-component/TableContainerEdit.js +23 -19
- package/lib/table-component/body/TableBodyCell.js +7 -1
- package/lib/table-component/hook/utils.js +12 -4
- package/lib/table-component/table/TableWrapper.js +1 -0
- package/package.json +1 -1
|
@@ -312,105 +312,195 @@ export type IFTheme = {
|
|
|
312
312
|
cssVariables?: BaseTableCSSVariables;
|
|
313
313
|
};
|
|
314
314
|
export type TableProps<RecordType = AnyObject> = {
|
|
315
|
+
/** Unique id của table */
|
|
315
316
|
id?: string;
|
|
317
|
+
/** Theme hiển thị của table */
|
|
316
318
|
theme?: IFTheme;
|
|
319
|
+
/** Hiển thị border bao quanh toàn bộ table */
|
|
317
320
|
useOuterBorder?: boolean;
|
|
321
|
+
/** Hiển thị header */
|
|
318
322
|
showHeader?: boolean;
|
|
323
|
+
/** CSS class cho container table */
|
|
319
324
|
className?: string;
|
|
325
|
+
/** Tiêu đề table */
|
|
320
326
|
title?: ReactNode | ((data: RecordType) => ReactNode);
|
|
327
|
+
/** Tiêu đề khi table ở chế độ fullscreen */
|
|
321
328
|
fullScreenTitle?: ReactNode | (() => ReactNode);
|
|
329
|
+
/** Tên field dùng làm key duy nhất cho mỗi record */
|
|
322
330
|
rowKey?: string;
|
|
331
|
+
/** Nguồn dữ liệu */
|
|
323
332
|
dataSource: RecordType[];
|
|
333
|
+
/** Danh sách cột */
|
|
324
334
|
columns: ColumnsTable<RecordType>;
|
|
335
|
+
/** Hiển thị loading */
|
|
325
336
|
loading?: boolean;
|
|
337
|
+
/** Chiều cao table */
|
|
326
338
|
height?: number;
|
|
339
|
+
/** Chiều cao tối thiểu table */
|
|
327
340
|
minHeight?: number;
|
|
341
|
+
/** Format dữ liệu mặc định */
|
|
328
342
|
format?: IFormat;
|
|
329
|
-
/**
|
|
343
|
+
/**
|
|
344
|
+
* Hàm translate
|
|
345
|
+
* useTranslation 'react-i18next'
|
|
346
|
+
* {t} = useTranslation()
|
|
347
|
+
* */
|
|
330
348
|
t?: any;
|
|
331
349
|
/** Language code: exp: vi || en || ja || zh .... */
|
|
332
350
|
lang?: string;
|
|
351
|
+
/** custom Locale cấu hình cho table */
|
|
333
352
|
locale?: Locale;
|
|
353
|
+
/** Cấu hình wrap text */
|
|
334
354
|
wrapSettings?: IWrapSettings;
|
|
355
|
+
/** Bật chế độ tải dữ liệu vô hạn */
|
|
335
356
|
infiniteScroll?: boolean;
|
|
357
|
+
/** Hàm gọi khi cuộn đến cuối */
|
|
336
358
|
next?: () => void;
|
|
359
|
+
/** Ngưỡng trigger load thêm dữ liệu */
|
|
337
360
|
onEndReachedThreshold?: number;
|
|
338
|
-
/**
|
|
361
|
+
/** Cho phép group dữ liệu */
|
|
339
362
|
groupAble?: boolean;
|
|
363
|
+
/** Danh sách cột dùng để group */
|
|
340
364
|
groupColumns?: string[];
|
|
365
|
+
/** Cấu hình group */
|
|
341
366
|
groupSetting?: IGroupSetting;
|
|
342
|
-
/**
|
|
367
|
+
/** Hiển thị toolbar */
|
|
343
368
|
showToolbar?: boolean;
|
|
369
|
+
/** Các action trên toolbar
|
|
370
|
+
*
|
|
371
|
+
* các Key mặc định:
|
|
372
|
+
* key: 'ADD' : Thêm dòng mới vào cuối
|
|
373
|
+
* key: 'DUPLICATE' : Nhân bản dòng được chọn (nếu có)
|
|
374
|
+
* key: 'INSERT_AFTER' : Chèn dòng mới sau dòng được chọn
|
|
375
|
+
* key: 'INSERT_BEFORE' : Chèn dòng mới trước dòng được chọn
|
|
376
|
+
* key: 'INSERT_CHILDREN' : Chèn dòng con vào dòng được chọn
|
|
377
|
+
* key: 'DELETE' : Xóa tất cả dòng
|
|
378
|
+
* key: 'DELETE_ROWS' : Xóa các dòng được chọn
|
|
379
|
+
*/
|
|
344
380
|
toolbarItems?: ToolbarItem[];
|
|
381
|
+
/** Chế độ hiển thị toolbar */
|
|
345
382
|
toolbarMode?: 'scroll';
|
|
383
|
+
/** Hiển thị chức năng chọn cột */
|
|
346
384
|
showColumnChoose?: boolean;
|
|
385
|
+
/** Callback khi thay đổi cột hiển thị */
|
|
347
386
|
onChooseColumns?: (props: IOnChooseColumns) => void;
|
|
387
|
+
/** Cho phép fullscreen */
|
|
348
388
|
fullScreen?: boolean;
|
|
389
|
+
/** Cấu hình phân trang, false để tắt */
|
|
349
390
|
pagination?: false | PaginationConfig;
|
|
350
|
-
showCustomTooltip?: boolean;
|
|
351
391
|
/** Context Menu */
|
|
392
|
+
/** Danh sách menu chuột phải */
|
|
352
393
|
contextMenuItems?: ContextMenuItem[];
|
|
353
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Danh sách menu cần ẩn
|
|
396
|
+
* Có thể trả về động theo row hiện tại
|
|
397
|
+
*/
|
|
354
398
|
contextMenuHidden?: string[] | ((args?: Omit<ContextInfo<RecordType>, 'item' | 'event'>) => string[]);
|
|
399
|
+
/** func khi mở context menu */
|
|
355
400
|
contextMenuOpen?: (args: Omit<ContextInfo<RecordType>, 'item'>) => void;
|
|
401
|
+
/** func khi click menu */
|
|
356
402
|
contextMenuClick?: (args: ContextInfo<RecordType>) => void;
|
|
403
|
+
/** Double click vào row */
|
|
357
404
|
recordDoubleClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
|
|
405
|
+
/** Click vào row */
|
|
358
406
|
recordClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
|
|
359
407
|
/** Filter */
|
|
408
|
+
/** Hiển thị filter nâng cao */
|
|
360
409
|
showAdvanceFilter?: boolean;
|
|
410
|
+
/** Cho phép filter */
|
|
361
411
|
allowFiltering?: boolean;
|
|
412
|
+
/** Filter mặc định */
|
|
362
413
|
defaultFilter?: FilterItem[];
|
|
414
|
+
/** func khi filter thay đổi */
|
|
363
415
|
onFilter?: (query: FilterItem[]) => void;
|
|
416
|
+
/** Nguồn dữ liệu filter */
|
|
364
417
|
dataSourceFilter?: SourceFilter[];
|
|
418
|
+
/**
|
|
419
|
+
* Custom popup filter cho từng cột
|
|
420
|
+
*/
|
|
365
421
|
onFilterClick?: (column: ColumnTable<RecordType>, callback: (key: string, data: any) => void) => void;
|
|
366
422
|
/** mặc định so sánh không dấu + lowercase
|
|
367
423
|
* ignoreAccents !== false => không khai báo hoặc ignoreAccents = true => so sánh không dấu
|
|
368
424
|
* ignoreAccents = false => so sánh có dấu + uperrcase sensitive
|
|
369
425
|
* **/
|
|
370
426
|
ignoreAccents?: boolean;
|
|
371
|
-
/**
|
|
372
|
-
sortMultiple?: boolean;
|
|
427
|
+
/** Cho phép sort */
|
|
373
428
|
allowSortering?: boolean;
|
|
429
|
+
/** Cho phép sort nhiều cột */
|
|
430
|
+
sortMultiple?: boolean;
|
|
431
|
+
/** func khi sort */
|
|
374
432
|
onSorter?: (args: Sorter[]) => void;
|
|
433
|
+
/** Sort mặc định */
|
|
375
434
|
defaultSorter?: Sorter[];
|
|
376
|
-
/** selected record feature */
|
|
377
435
|
selectionSettings?: SelectionSettings;
|
|
436
|
+
/** Cấu hình chọn row */
|
|
378
437
|
rowSelection?: RowSelection<RecordType>;
|
|
438
|
+
/** func khi chọn row */
|
|
379
439
|
rowSelected?: (args: {
|
|
380
440
|
type: string;
|
|
381
441
|
rowData: RecordType;
|
|
382
442
|
selected: RecordType | RecordType[];
|
|
383
443
|
}) => void;
|
|
384
|
-
/** resize
|
|
444
|
+
/** Cho phép resize độ rộng cột */
|
|
385
445
|
allowResizing?: boolean;
|
|
446
|
+
/** Hiển thị dòng tổng hợp */
|
|
386
447
|
summary?: boolean;
|
|
448
|
+
/** Dữ liệu footer */
|
|
387
449
|
footerDataSource?: any[];
|
|
450
|
+
/** Hiển thị text khi không có dữ liệu */
|
|
388
451
|
showEmptyText?: boolean;
|
|
452
|
+
/** Cấu hình command column */
|
|
389
453
|
commandSettings?: CommandSettings;
|
|
454
|
+
/**
|
|
455
|
+
* Func khi click command
|
|
456
|
+
* Ví dụ: Edit, Delete, Copy...
|
|
457
|
+
*/
|
|
390
458
|
commandClick?: (args: CommandClick<RecordType>) => void;
|
|
459
|
+
/** Callback khi expand/collapse row */
|
|
391
460
|
onExpandClick?: (args: {
|
|
392
461
|
expandedKeys: string[];
|
|
393
462
|
key: string;
|
|
394
463
|
rowData: any;
|
|
395
464
|
}) => void;
|
|
465
|
+
/** Cấu hình expandable row */
|
|
396
466
|
expandable?: ExpandableConfig<RecordType>;
|
|
467
|
+
/** custom Action area bên trái thanh toolbar */
|
|
397
468
|
actionTemplate?: ReactNode | ReactElement | (() => ReactNode | ReactElement);
|
|
469
|
+
/** Nội dung hiển thị cuối table */
|
|
398
470
|
bottom?: ReactNode;
|
|
399
471
|
rowClassName?: string | RowClassName<RecordType>;
|
|
400
472
|
onRowStyles?: CSSProperties | ((data: RecordType) => CSSProperties);
|
|
401
473
|
onRowHeaderStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
|
|
402
474
|
onRowFooterStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
|
|
475
|
+
/** Bật virtual scroll */
|
|
403
476
|
useVirtual?: {
|
|
477
|
+
/** Virtual theo chiều ngang */
|
|
404
478
|
horizontal?: boolean;
|
|
479
|
+
/** Virtual theo chiều dọc */
|
|
405
480
|
vertical?: boolean;
|
|
406
481
|
};
|
|
482
|
+
/** Cho phép chỉnh sửa dữ liệu */
|
|
407
483
|
editAble?: boolean;
|
|
484
|
+
/** Callback khi data thay đổi */
|
|
408
485
|
onDataChange?: (data: RecordType[]) => void;
|
|
486
|
+
/** Giá trị mặc định khi thêm mới */
|
|
409
487
|
defaultValue?: AnyObject | (() => AnyObject);
|
|
488
|
+
/** Callback xử lý paste dữ liệu */
|
|
410
489
|
onCellPaste?: ICellPasteModel<RecordType>;
|
|
490
|
+
/**
|
|
491
|
+
* Callback khi cell thay đổi giá trị
|
|
492
|
+
*
|
|
493
|
+
* handleCallback dùng để cập nhật giá trị sau khi xử lý custom
|
|
494
|
+
*/
|
|
411
495
|
onCellChange?: (args: CellChangeArgs<RecordType>, handleCallback: (rowData: any, index: any, value?: any) => void) => void;
|
|
496
|
+
/** Callback click cell */
|
|
412
497
|
onCellClick?: (args: ICellClick, callback?: any) => void;
|
|
498
|
+
/** Xác định row có được edit hay không */
|
|
413
499
|
rowEditable?: (rowData: RecordType) => boolean;
|
|
500
|
+
/** Validate dữ liệu
|
|
501
|
+
*
|
|
502
|
+
* exp: validate= yup.object().shape({})
|
|
503
|
+
*/
|
|
414
504
|
validate?: any;
|
|
415
505
|
onBlur?: (data: RecordType[]) => void;
|
|
416
506
|
};
|
|
@@ -194,10 +194,10 @@ export declare const fixColumnsLeft: <RecordType>(columns: ColumnTable<RecordTyp
|
|
|
194
194
|
value: any;
|
|
195
195
|
rowData: RecordType;
|
|
196
196
|
}) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | import("react").ReactNode);
|
|
197
|
-
onCellStyles?: Omit<CSSProperties, "
|
|
198
|
-
onCellHeaderStyles?: Omit<CSSProperties, "
|
|
197
|
+
onCellStyles?: Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth"> | ((cellValue: any, cell: import("@tanstack/react-table").Cell<RecordType, unknown>) => Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth">);
|
|
198
|
+
onCellHeaderStyles?: Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth"> | ((cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth">);
|
|
199
199
|
onCell?: (rowData: RecordType, index: number) => import("react").TdHTMLAttributes<HTMLTableCellElement>;
|
|
200
|
-
onCellFooterStyles?: Omit<CSSProperties, "
|
|
200
|
+
onCellFooterStyles?: Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth"> | ((cellValue: any, cell: import("@tanstack/react-table").Header<RecordType, unknown>) => Omit<CSSProperties, "position" | "left" | "right" | "display" | "width" | "minWidth">);
|
|
201
201
|
getValue?: (row: any, rowIndex: number) => any;
|
|
202
202
|
getCellProps?: (value: any, row: any, rowIndex: number) => import("./../../grid-component/type").CellProps;
|
|
203
203
|
headerCellProps?: import("./../../grid-component/type").CellProps;
|
|
@@ -112,11 +112,15 @@ const TableContainer = props => {
|
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
if (containerRef.current && tableBody && !tableBody.contains(event.target)) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
if (focusedCell) {
|
|
116
|
+
setFocusedCell(undefined);
|
|
117
|
+
}
|
|
118
|
+
if (isSelectionChange?.isChange) {
|
|
119
|
+
setIsSelectionChange(prev => ({
|
|
120
|
+
...prev,
|
|
121
|
+
isChange: false
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
120
124
|
}
|
|
121
125
|
};
|
|
122
126
|
document.addEventListener('mousedown', handleClickOutside);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import React, { Fragment } from "react";
|
|
3
3
|
import { useCopyToClipboard } from 'usehooks-ts';
|
|
4
|
-
import { checkDecimalSeparator, checkThousandSeparator, detectSeparators, findItemByKey, flattenArray, flattenData, getAllRowKey, getColIdsBetween, getDefaultValue, getEditType, getFormat, getRowIdsBetween, getSelectedCellMatrix, getTableHeight, isEditable, isFormattedNumber, newGuid,
|
|
4
|
+
import { checkDecimalSeparator, checkThousandSeparator, detectSeparators, findItemByKey, flattenArray, flattenData, getAllRowKey, getColIdsBetween, getDefaultValue, getEditType, getFormat, getRowIdsBetween, getSelectedCellMatrix, getTableHeight, isEditable, isFormattedNumber, newGuid, parseExcelClipboard,
|
|
5
|
+
// parseExcelClipboardText,
|
|
6
|
+
sumSize,
|
|
5
7
|
// sumSize,
|
|
6
8
|
unFlattenData, updateArrayByKey, updateColumnWidthsRecursive, updateOrInsert } from "./hook/utils";
|
|
7
9
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
@@ -522,7 +524,7 @@ const TableContainerEdit = props => {
|
|
|
522
524
|
triggerPaste?.(pastedRows, pastedColumnsArray, newDataSource, []);
|
|
523
525
|
}
|
|
524
526
|
}, [dataSource, format, onCellPaste?.maxRowsPaste, originData, rowKey, startCell, table, triggerPaste]);
|
|
525
|
-
const handlePasteToTable = React.useCallback(pasteData => {
|
|
527
|
+
const handlePasteToTable = React.useCallback((pasteData, e) => {
|
|
526
528
|
if (!startCell) return;
|
|
527
529
|
|
|
528
530
|
// const pastedRows = pasted.trim().split('\n').map(row => row.split('\t'));
|
|
@@ -535,8 +537,9 @@ const TableContainerEdit = props => {
|
|
|
535
537
|
|
|
536
538
|
// )
|
|
537
539
|
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
+
const rowsPasted = parseExcelClipboard(e);
|
|
541
|
+
// const rowsPasted2 = parseExcelClipboardText(pasteData)
|
|
542
|
+
|
|
540
543
|
if (rowsPasted.length > (onCellPaste?.maxRowsPaste ?? 200)) {
|
|
541
544
|
// bật popup thông báo
|
|
542
545
|
|
|
@@ -583,7 +586,7 @@ const TableContainerEdit = props => {
|
|
|
583
586
|
if (startCell && !editingKey) {
|
|
584
587
|
e.preventDefault(); // Chặn hành vi mặc định
|
|
585
588
|
const clipboardText = e.clipboardData?.getData('text/plain') ?? '';
|
|
586
|
-
handlePasteToTable(clipboardText);
|
|
589
|
+
handlePasteToTable(clipboardText, e);
|
|
587
590
|
}
|
|
588
591
|
};
|
|
589
592
|
document.addEventListener('paste', handlePaste);
|
|
@@ -608,27 +611,30 @@ const TableContainerEdit = props => {
|
|
|
608
611
|
if (editingKey) {
|
|
609
612
|
onBlur?.(dataSource);
|
|
610
613
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
614
|
+
if (editingKey) {
|
|
615
|
+
setTimeout(() => {
|
|
616
|
+
setEditingKey('');
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
if (endCell) {
|
|
620
|
+
setEndCell(undefined);
|
|
621
|
+
}
|
|
622
|
+
if (startCell) {
|
|
623
|
+
setStartCell(undefined);
|
|
624
|
+
}
|
|
625
|
+
if (rangeState) {
|
|
626
|
+
setRangeState(undefined);
|
|
627
|
+
}
|
|
628
|
+
if (focusedCell) {
|
|
629
|
+
setFocusedCell(undefined);
|
|
630
|
+
}
|
|
619
631
|
}
|
|
620
632
|
};
|
|
621
|
-
|
|
622
|
-
// document.addEventListener('click', handleClickOutside)
|
|
623
633
|
document.addEventListener('mousedown', handleClickOutside);
|
|
624
|
-
// document.addEventListener('touchstart', handleClickOutside)
|
|
625
|
-
|
|
626
634
|
return () => {
|
|
627
|
-
// document.removeEventListener('click', handleClickOutside)
|
|
628
635
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
629
|
-
// document.removeEventListener('touchstart', handleClickOutside)
|
|
630
636
|
};
|
|
631
|
-
}, [dataSource, editingKey, id, onBlur]);
|
|
637
|
+
}, [dataSource, editingKey, endCell, focusedCell, id, onBlur, rangeState, startCell]);
|
|
632
638
|
const columnSizingState = table.getState().columnSizing;
|
|
633
639
|
React.useEffect(() => {
|
|
634
640
|
requestAnimationFrame(() => {
|
|
@@ -399,7 +399,13 @@ const TableBodyCell = props => {
|
|
|
399
399
|
if (e.target.firstChild?.clientWidth < e.target.firstChild?.scrollWidth) {
|
|
400
400
|
setIsOpenTooltip(true);
|
|
401
401
|
}
|
|
402
|
-
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// onMouseLeave={() => {
|
|
405
|
+
// setIsOpenTooltip(false)
|
|
406
|
+
// }}
|
|
407
|
+
,
|
|
408
|
+
|
|
403
409
|
onKeyDown: e => {
|
|
404
410
|
const flatRows = table.getRowModel().flatRows;
|
|
405
411
|
if (e.key === 'ArrowDown' && rowNumber < flatRows.length - 1) {
|
|
@@ -2389,12 +2389,20 @@ export function parseExcelClipboardText(text) {
|
|
|
2389
2389
|
}
|
|
2390
2390
|
export function parseExcelClipboard(e) {
|
|
2391
2391
|
const text = e.clipboardData?.getData('text/plain') ?? '';
|
|
2392
|
-
if (!text)
|
|
2392
|
+
if (!text) {
|
|
2393
|
+
return [];
|
|
2394
|
+
}
|
|
2393
2395
|
|
|
2394
2396
|
// Excel thường dùng \r\n giữa dòng, \t giữa cột
|
|
2395
|
-
const
|
|
2396
|
-
|
|
2397
|
-
|
|
2397
|
+
const rawRows = text.split(/\r?\n/); // tách theo dòng
|
|
2398
|
+
|
|
2399
|
+
// Loại bỏ chỉ các dòng trống ở cuối (trailing empty rows), giữ nguyên dòng trống ở giữa
|
|
2400
|
+
let lastNonEmpty = rawRows.length - 1;
|
|
2401
|
+
while (lastNonEmpty >= 0 && rawRows[lastNonEmpty].trim() === '') {
|
|
2402
|
+
lastNonEmpty--;
|
|
2403
|
+
}
|
|
2404
|
+
const trimmedRows = lastNonEmpty >= 0 ? rawRows.slice(0, lastNonEmpty + 1) : [];
|
|
2405
|
+
const rows = trimmedRows.map(row => row.split('\t') // tách theo cột
|
|
2398
2406
|
.map(cell => cell.replace(/\r/g, '').replace(/\n/g, '\n')) // giữ xuống dòng trong ô
|
|
2399
2407
|
);
|
|
2400
2408
|
return rows;
|
|
@@ -312,105 +312,195 @@ export type IFTheme = {
|
|
|
312
312
|
cssVariables?: BaseTableCSSVariables;
|
|
313
313
|
};
|
|
314
314
|
export type TableProps<RecordType = AnyObject> = {
|
|
315
|
+
/** Unique id của table */
|
|
315
316
|
id?: string;
|
|
317
|
+
/** Theme hiển thị của table */
|
|
316
318
|
theme?: IFTheme;
|
|
319
|
+
/** Hiển thị border bao quanh toàn bộ table */
|
|
317
320
|
useOuterBorder?: boolean;
|
|
321
|
+
/** Hiển thị header */
|
|
318
322
|
showHeader?: boolean;
|
|
323
|
+
/** CSS class cho container table */
|
|
319
324
|
className?: string;
|
|
325
|
+
/** Tiêu đề table */
|
|
320
326
|
title?: ReactNode | ((data: RecordType) => ReactNode);
|
|
327
|
+
/** Tiêu đề khi table ở chế độ fullscreen */
|
|
321
328
|
fullScreenTitle?: ReactNode | (() => ReactNode);
|
|
329
|
+
/** Tên field dùng làm key duy nhất cho mỗi record */
|
|
322
330
|
rowKey?: string;
|
|
331
|
+
/** Nguồn dữ liệu */
|
|
323
332
|
dataSource: RecordType[];
|
|
333
|
+
/** Danh sách cột */
|
|
324
334
|
columns: ColumnsTable<RecordType>;
|
|
335
|
+
/** Hiển thị loading */
|
|
325
336
|
loading?: boolean;
|
|
337
|
+
/** Chiều cao table */
|
|
326
338
|
height?: number;
|
|
339
|
+
/** Chiều cao tối thiểu table */
|
|
327
340
|
minHeight?: number;
|
|
341
|
+
/** Format dữ liệu mặc định */
|
|
328
342
|
format?: IFormat;
|
|
329
|
-
/**
|
|
343
|
+
/**
|
|
344
|
+
* Hàm translate
|
|
345
|
+
* useTranslation 'react-i18next'
|
|
346
|
+
* {t} = useTranslation()
|
|
347
|
+
* */
|
|
330
348
|
t?: any;
|
|
331
349
|
/** Language code: exp: vi || en || ja || zh .... */
|
|
332
350
|
lang?: string;
|
|
351
|
+
/** custom Locale cấu hình cho table */
|
|
333
352
|
locale?: Locale;
|
|
353
|
+
/** Cấu hình wrap text */
|
|
334
354
|
wrapSettings?: IWrapSettings;
|
|
355
|
+
/** Bật chế độ tải dữ liệu vô hạn */
|
|
335
356
|
infiniteScroll?: boolean;
|
|
357
|
+
/** Hàm gọi khi cuộn đến cuối */
|
|
336
358
|
next?: () => void;
|
|
359
|
+
/** Ngưỡng trigger load thêm dữ liệu */
|
|
337
360
|
onEndReachedThreshold?: number;
|
|
338
|
-
/**
|
|
361
|
+
/** Cho phép group dữ liệu */
|
|
339
362
|
groupAble?: boolean;
|
|
363
|
+
/** Danh sách cột dùng để group */
|
|
340
364
|
groupColumns?: string[];
|
|
365
|
+
/** Cấu hình group */
|
|
341
366
|
groupSetting?: IGroupSetting;
|
|
342
|
-
/**
|
|
367
|
+
/** Hiển thị toolbar */
|
|
343
368
|
showToolbar?: boolean;
|
|
369
|
+
/** Các action trên toolbar
|
|
370
|
+
*
|
|
371
|
+
* các Key mặc định:
|
|
372
|
+
* key: 'ADD' : Thêm dòng mới vào cuối
|
|
373
|
+
* key: 'DUPLICATE' : Nhân bản dòng được chọn (nếu có)
|
|
374
|
+
* key: 'INSERT_AFTER' : Chèn dòng mới sau dòng được chọn
|
|
375
|
+
* key: 'INSERT_BEFORE' : Chèn dòng mới trước dòng được chọn
|
|
376
|
+
* key: 'INSERT_CHILDREN' : Chèn dòng con vào dòng được chọn
|
|
377
|
+
* key: 'DELETE' : Xóa tất cả dòng
|
|
378
|
+
* key: 'DELETE_ROWS' : Xóa các dòng được chọn
|
|
379
|
+
*/
|
|
344
380
|
toolbarItems?: ToolbarItem[];
|
|
381
|
+
/** Chế độ hiển thị toolbar */
|
|
345
382
|
toolbarMode?: 'scroll';
|
|
383
|
+
/** Hiển thị chức năng chọn cột */
|
|
346
384
|
showColumnChoose?: boolean;
|
|
385
|
+
/** Callback khi thay đổi cột hiển thị */
|
|
347
386
|
onChooseColumns?: (props: IOnChooseColumns) => void;
|
|
387
|
+
/** Cho phép fullscreen */
|
|
348
388
|
fullScreen?: boolean;
|
|
389
|
+
/** Cấu hình phân trang, false để tắt */
|
|
349
390
|
pagination?: false | PaginationConfig;
|
|
350
|
-
showCustomTooltip?: boolean;
|
|
351
391
|
/** Context Menu */
|
|
392
|
+
/** Danh sách menu chuột phải */
|
|
352
393
|
contextMenuItems?: ContextMenuItem[];
|
|
353
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Danh sách menu cần ẩn
|
|
396
|
+
* Có thể trả về động theo row hiện tại
|
|
397
|
+
*/
|
|
354
398
|
contextMenuHidden?: string[] | ((args?: Omit<ContextInfo<RecordType>, 'item' | 'event'>) => string[]);
|
|
399
|
+
/** func khi mở context menu */
|
|
355
400
|
contextMenuOpen?: (args: Omit<ContextInfo<RecordType>, 'item'>) => void;
|
|
401
|
+
/** func khi click menu */
|
|
356
402
|
contextMenuClick?: (args: ContextInfo<RecordType>) => void;
|
|
403
|
+
/** Double click vào row */
|
|
357
404
|
recordDoubleClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
|
|
405
|
+
/** Click vào row */
|
|
358
406
|
recordClick?: (args: RecordDoubleClickEventArgs<RecordType>) => void;
|
|
359
407
|
/** Filter */
|
|
408
|
+
/** Hiển thị filter nâng cao */
|
|
360
409
|
showAdvanceFilter?: boolean;
|
|
410
|
+
/** Cho phép filter */
|
|
361
411
|
allowFiltering?: boolean;
|
|
412
|
+
/** Filter mặc định */
|
|
362
413
|
defaultFilter?: FilterItem[];
|
|
414
|
+
/** func khi filter thay đổi */
|
|
363
415
|
onFilter?: (query: FilterItem[]) => void;
|
|
416
|
+
/** Nguồn dữ liệu filter */
|
|
364
417
|
dataSourceFilter?: SourceFilter[];
|
|
418
|
+
/**
|
|
419
|
+
* Custom popup filter cho từng cột
|
|
420
|
+
*/
|
|
365
421
|
onFilterClick?: (column: ColumnTable<RecordType>, callback: (key: string, data: any) => void) => void;
|
|
366
422
|
/** mặc định so sánh không dấu + lowercase
|
|
367
423
|
* ignoreAccents !== false => không khai báo hoặc ignoreAccents = true => so sánh không dấu
|
|
368
424
|
* ignoreAccents = false => so sánh có dấu + uperrcase sensitive
|
|
369
425
|
* **/
|
|
370
426
|
ignoreAccents?: boolean;
|
|
371
|
-
/**
|
|
372
|
-
sortMultiple?: boolean;
|
|
427
|
+
/** Cho phép sort */
|
|
373
428
|
allowSortering?: boolean;
|
|
429
|
+
/** Cho phép sort nhiều cột */
|
|
430
|
+
sortMultiple?: boolean;
|
|
431
|
+
/** func khi sort */
|
|
374
432
|
onSorter?: (args: Sorter[]) => void;
|
|
433
|
+
/** Sort mặc định */
|
|
375
434
|
defaultSorter?: Sorter[];
|
|
376
|
-
/** selected record feature */
|
|
377
435
|
selectionSettings?: SelectionSettings;
|
|
436
|
+
/** Cấu hình chọn row */
|
|
378
437
|
rowSelection?: RowSelection<RecordType>;
|
|
438
|
+
/** func khi chọn row */
|
|
379
439
|
rowSelected?: (args: {
|
|
380
440
|
type: string;
|
|
381
441
|
rowData: RecordType;
|
|
382
442
|
selected: RecordType | RecordType[];
|
|
383
443
|
}) => void;
|
|
384
|
-
/** resize
|
|
444
|
+
/** Cho phép resize độ rộng cột */
|
|
385
445
|
allowResizing?: boolean;
|
|
446
|
+
/** Hiển thị dòng tổng hợp */
|
|
386
447
|
summary?: boolean;
|
|
448
|
+
/** Dữ liệu footer */
|
|
387
449
|
footerDataSource?: any[];
|
|
450
|
+
/** Hiển thị text khi không có dữ liệu */
|
|
388
451
|
showEmptyText?: boolean;
|
|
452
|
+
/** Cấu hình command column */
|
|
389
453
|
commandSettings?: CommandSettings;
|
|
454
|
+
/**
|
|
455
|
+
* Func khi click command
|
|
456
|
+
* Ví dụ: Edit, Delete, Copy...
|
|
457
|
+
*/
|
|
390
458
|
commandClick?: (args: CommandClick<RecordType>) => void;
|
|
459
|
+
/** Callback khi expand/collapse row */
|
|
391
460
|
onExpandClick?: (args: {
|
|
392
461
|
expandedKeys: string[];
|
|
393
462
|
key: string;
|
|
394
463
|
rowData: any;
|
|
395
464
|
}) => void;
|
|
465
|
+
/** Cấu hình expandable row */
|
|
396
466
|
expandable?: ExpandableConfig<RecordType>;
|
|
467
|
+
/** custom Action area bên trái thanh toolbar */
|
|
397
468
|
actionTemplate?: ReactNode | ReactElement | (() => ReactNode | ReactElement);
|
|
469
|
+
/** Nội dung hiển thị cuối table */
|
|
398
470
|
bottom?: ReactNode;
|
|
399
471
|
rowClassName?: string | RowClassName<RecordType>;
|
|
400
472
|
onRowStyles?: CSSProperties | ((data: RecordType) => CSSProperties);
|
|
401
473
|
onRowHeaderStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
|
|
402
474
|
onRowFooterStyles?: Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'> | (() => Omit<React.CSSProperties, 'display' | 'transform' | 'gridTemplateColumns' | 'height' | 'minHeight'>);
|
|
475
|
+
/** Bật virtual scroll */
|
|
403
476
|
useVirtual?: {
|
|
477
|
+
/** Virtual theo chiều ngang */
|
|
404
478
|
horizontal?: boolean;
|
|
479
|
+
/** Virtual theo chiều dọc */
|
|
405
480
|
vertical?: boolean;
|
|
406
481
|
};
|
|
482
|
+
/** Cho phép chỉnh sửa dữ liệu */
|
|
407
483
|
editAble?: boolean;
|
|
484
|
+
/** Callback khi data thay đổi */
|
|
408
485
|
onDataChange?: (data: RecordType[]) => void;
|
|
486
|
+
/** Giá trị mặc định khi thêm mới */
|
|
409
487
|
defaultValue?: AnyObject | (() => AnyObject);
|
|
488
|
+
/** Callback xử lý paste dữ liệu */
|
|
410
489
|
onCellPaste?: ICellPasteModel<RecordType>;
|
|
490
|
+
/**
|
|
491
|
+
* Callback khi cell thay đổi giá trị
|
|
492
|
+
*
|
|
493
|
+
* handleCallback dùng để cập nhật giá trị sau khi xử lý custom
|
|
494
|
+
*/
|
|
411
495
|
onCellChange?: (args: CellChangeArgs<RecordType>, handleCallback: (rowData: any, index: any, value?: any) => void) => void;
|
|
496
|
+
/** Callback click cell */
|
|
412
497
|
onCellClick?: (args: ICellClick, callback?: any) => void;
|
|
498
|
+
/** Xác định row có được edit hay không */
|
|
413
499
|
rowEditable?: (rowData: RecordType) => boolean;
|
|
500
|
+
/** Validate dữ liệu
|
|
501
|
+
*
|
|
502
|
+
* exp: validate= yup.object().shape({})
|
|
503
|
+
*/
|
|
414
504
|
validate?: any;
|
|
415
505
|
onBlur?: (data: RecordType[]) => void;
|
|
416
506
|
};
|
|
@@ -119,11 +119,15 @@ const TableContainer = props => {
|
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
if (containerRef.current && tableBody && !tableBody.contains(event.target)) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
if (focusedCell) {
|
|
123
|
+
setFocusedCell(undefined);
|
|
124
|
+
}
|
|
125
|
+
if (isSelectionChange?.isChange) {
|
|
126
|
+
setIsSelectionChange(prev => ({
|
|
127
|
+
...prev,
|
|
128
|
+
isChange: false
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
127
131
|
}
|
|
128
132
|
};
|
|
129
133
|
document.addEventListener('mousedown', handleClickOutside);
|
|
@@ -529,7 +529,7 @@ const TableContainerEdit = props => {
|
|
|
529
529
|
triggerPaste?.(pastedRows, pastedColumnsArray, newDataSource, []);
|
|
530
530
|
}
|
|
531
531
|
}, [dataSource, format, onCellPaste?.maxRowsPaste, originData, rowKey, startCell, table, triggerPaste]);
|
|
532
|
-
const handlePasteToTable = _react.default.useCallback(pasteData => {
|
|
532
|
+
const handlePasteToTable = _react.default.useCallback((pasteData, e) => {
|
|
533
533
|
if (!startCell) return;
|
|
534
534
|
|
|
535
535
|
// const pastedRows = pasted.trim().split('\n').map(row => row.split('\t'));
|
|
@@ -542,8 +542,9 @@ const TableContainerEdit = props => {
|
|
|
542
542
|
|
|
543
543
|
// )
|
|
544
544
|
|
|
545
|
-
|
|
546
|
-
const
|
|
545
|
+
const rowsPasted = (0, _utils.parseExcelClipboard)(e);
|
|
546
|
+
// const rowsPasted2 = parseExcelClipboardText(pasteData)
|
|
547
|
+
|
|
547
548
|
if (rowsPasted.length > (onCellPaste?.maxRowsPaste ?? 200)) {
|
|
548
549
|
// bật popup thông báo
|
|
549
550
|
|
|
@@ -590,7 +591,7 @@ const TableContainerEdit = props => {
|
|
|
590
591
|
if (startCell && !editingKey) {
|
|
591
592
|
e.preventDefault(); // Chặn hành vi mặc định
|
|
592
593
|
const clipboardText = e.clipboardData?.getData('text/plain') ?? '';
|
|
593
|
-
handlePasteToTable(clipboardText);
|
|
594
|
+
handlePasteToTable(clipboardText, e);
|
|
594
595
|
}
|
|
595
596
|
};
|
|
596
597
|
document.addEventListener('paste', handlePaste);
|
|
@@ -615,27 +616,30 @@ const TableContainerEdit = props => {
|
|
|
615
616
|
if (editingKey) {
|
|
616
617
|
onBlur?.(dataSource);
|
|
617
618
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
619
|
+
if (editingKey) {
|
|
620
|
+
setTimeout(() => {
|
|
621
|
+
setEditingKey('');
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
if (endCell) {
|
|
625
|
+
setEndCell(undefined);
|
|
626
|
+
}
|
|
627
|
+
if (startCell) {
|
|
628
|
+
setStartCell(undefined);
|
|
629
|
+
}
|
|
630
|
+
if (rangeState) {
|
|
631
|
+
setRangeState(undefined);
|
|
632
|
+
}
|
|
633
|
+
if (focusedCell) {
|
|
634
|
+
setFocusedCell(undefined);
|
|
635
|
+
}
|
|
626
636
|
}
|
|
627
637
|
};
|
|
628
|
-
|
|
629
|
-
// document.addEventListener('click', handleClickOutside)
|
|
630
638
|
document.addEventListener('mousedown', handleClickOutside);
|
|
631
|
-
// document.addEventListener('touchstart', handleClickOutside)
|
|
632
|
-
|
|
633
639
|
return () => {
|
|
634
|
-
// document.removeEventListener('click', handleClickOutside)
|
|
635
640
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
636
|
-
// document.removeEventListener('touchstart', handleClickOutside)
|
|
637
641
|
};
|
|
638
|
-
}, [dataSource, editingKey, id, onBlur]);
|
|
642
|
+
}, [dataSource, editingKey, endCell, focusedCell, id, onBlur, rangeState, startCell]);
|
|
639
643
|
const columnSizingState = table.getState().columnSizing;
|
|
640
644
|
_react.default.useEffect(() => {
|
|
641
645
|
requestAnimationFrame(() => {
|
|
@@ -406,7 +406,13 @@ const TableBodyCell = props => {
|
|
|
406
406
|
if (e.target.firstChild?.clientWidth < e.target.firstChild?.scrollWidth) {
|
|
407
407
|
setIsOpenTooltip(true);
|
|
408
408
|
}
|
|
409
|
-
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// onMouseLeave={() => {
|
|
412
|
+
// setIsOpenTooltip(false)
|
|
413
|
+
// }}
|
|
414
|
+
,
|
|
415
|
+
|
|
410
416
|
onKeyDown: e => {
|
|
411
417
|
const flatRows = table.getRowModel().flatRows;
|
|
412
418
|
if (e.key === 'ArrowDown' && rowNumber < flatRows.length - 1) {
|
|
@@ -2518,12 +2518,20 @@ function parseExcelClipboardText(text) {
|
|
|
2518
2518
|
}
|
|
2519
2519
|
function parseExcelClipboard(e) {
|
|
2520
2520
|
const text = e.clipboardData?.getData('text/plain') ?? '';
|
|
2521
|
-
if (!text)
|
|
2521
|
+
if (!text) {
|
|
2522
|
+
return [];
|
|
2523
|
+
}
|
|
2522
2524
|
|
|
2523
2525
|
// Excel thường dùng \r\n giữa dòng, \t giữa cột
|
|
2524
|
-
const
|
|
2525
|
-
|
|
2526
|
-
|
|
2526
|
+
const rawRows = text.split(/\r?\n/); // tách theo dòng
|
|
2527
|
+
|
|
2528
|
+
// Loại bỏ chỉ các dòng trống ở cuối (trailing empty rows), giữ nguyên dòng trống ở giữa
|
|
2529
|
+
let lastNonEmpty = rawRows.length - 1;
|
|
2530
|
+
while (lastNonEmpty >= 0 && rawRows[lastNonEmpty].trim() === '') {
|
|
2531
|
+
lastNonEmpty--;
|
|
2532
|
+
}
|
|
2533
|
+
const trimmedRows = lastNonEmpty >= 0 ? rawRows.slice(0, lastNonEmpty + 1) : [];
|
|
2534
|
+
const rows = trimmedRows.map(row => row.split('\t') // tách theo cột
|
|
2527
2535
|
.map(cell => cell.replace(/\r/g, '').replace(/\n/g, '\n')) // giữ xuống dòng trong ô
|
|
2528
2536
|
);
|
|
2529
2537
|
return rows;
|
|
@@ -253,6 +253,7 @@ const TableWrapper = props => {
|
|
|
253
253
|
content,
|
|
254
254
|
activeAnchor
|
|
255
255
|
}) => activeAnchor?.getAttribute('data-tooltip-sort') || content
|
|
256
|
+
// float
|
|
256
257
|
}), /*#__PURE__*/_react.default.createElement(_reactTooltip.Tooltip, {
|
|
257
258
|
id: `${id}-tooltip-error`,
|
|
258
259
|
style: {
|