implementation of datatable
This commit is contained in:
@@ -25,7 +25,7 @@
|
|||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"sass": "^1.70.0",
|
"sass": "^1.75.0",
|
||||||
"stylis": "^4.3.1",
|
"stylis": "^4.3.1",
|
||||||
"stylis-plugin-rtl": "^2.1.1"
|
"stylis-plugin-rtl": "^2.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
207
src/core/components/DataTable/body/TableBody.js
Normal file
207
src/core/components/DataTable/body/TableBody.js
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { TableBody, Typography } from "@mui/material";
|
||||||
|
import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table";
|
||||||
|
import { memo, useMemo } from "react";
|
||||||
|
import DataTable_TableBodyRow, { Memo_DataTable_TableBodyRow } from "./TableBodyRow";
|
||||||
|
|
||||||
|
const DataTable_TableBody = ({
|
||||||
|
columnVirtualizer,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getBottomRows,
|
||||||
|
getIsSomeRowsPinned,
|
||||||
|
getRowModel,
|
||||||
|
getState,
|
||||||
|
getTopRows,
|
||||||
|
options: {
|
||||||
|
enableStickyFooter,
|
||||||
|
enableStickyHeader,
|
||||||
|
layoutMode,
|
||||||
|
localization,
|
||||||
|
memoMode,
|
||||||
|
muiTableBodyProps,
|
||||||
|
renderDetailPanel,
|
||||||
|
renderEmptyRowsFallback,
|
||||||
|
rowPinningDisplayMode,
|
||||||
|
},
|
||||||
|
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
|
||||||
|
} = table;
|
||||||
|
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
|
||||||
|
|
||||||
|
const tableBodyProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableHeadHeight =
|
||||||
|
((enableStickyHeader || isFullScreen) &&
|
||||||
|
tableHeadRef.current?.clientHeight) ||
|
||||||
|
0;
|
||||||
|
const tableFooterHeight =
|
||||||
|
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||||
|
|
||||||
|
const pinnedRowIds = useMemo(() => {
|
||||||
|
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
|
||||||
|
return getRowModel()
|
||||||
|
.rows.filter((row) => row.getIsPinned())
|
||||||
|
.map((r) => r.id);
|
||||||
|
}, [rowPinning, getRowModel().rows]);
|
||||||
|
|
||||||
|
const rows = useMRT_Rows(table);
|
||||||
|
|
||||||
|
const rowVirtualizer = useMRT_RowVirtualizer(table, rows);
|
||||||
|
|
||||||
|
const { virtualRows } = rowVirtualizer ?? {};
|
||||||
|
|
||||||
|
const commonRowProps = {
|
||||||
|
columnVirtualizer,
|
||||||
|
numRows: rows.length,
|
||||||
|
table,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!rowPinningDisplayMode?.includes('sticky') &&
|
||||||
|
getIsSomeRowsPinned('top') && (
|
||||||
|
<TableBody
|
||||||
|
{...tableBodyProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
|
||||||
|
position: 'sticky',
|
||||||
|
top: tableHeadHeight - 1,
|
||||||
|
zIndex: 1,
|
||||||
|
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{getTopRows().map((row, staticRowIndex) => {
|
||||||
|
const props = {
|
||||||
|
...commonRowProps,
|
||||||
|
row,
|
||||||
|
staticRowIndex,
|
||||||
|
};
|
||||||
|
return memoMode === 'rows' ? (
|
||||||
|
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
)}
|
||||||
|
<TableBody
|
||||||
|
{...tableBodyProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
|
||||||
|
height: rowVirtualizer
|
||||||
|
? `${rowVirtualizer.getTotalSize()}px`
|
||||||
|
: undefined,
|
||||||
|
minHeight: !rows.length ? '100px' : undefined,
|
||||||
|
position: 'relative',
|
||||||
|
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{tableBodyProps?.children ??
|
||||||
|
(!rows.length ? (
|
||||||
|
<tr
|
||||||
|
style={{
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
colSpan={table.getVisibleLeafColumns().length}
|
||||||
|
style={{
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderEmptyRowsFallback?.({ table }) ?? (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: 'text.secondary',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360
|
||||||
|
}px)`,
|
||||||
|
py: '2rem',
|
||||||
|
textAlign: 'center',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{globalFilter || columnFilters.length
|
||||||
|
? localization.noResultsFound
|
||||||
|
: localization.noRecordsToDisplay}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
|
||||||
|
let row = rowOrVirtualRow;
|
||||||
|
if (rowVirtualizer) {
|
||||||
|
if (renderDetailPanel) {
|
||||||
|
if (rowOrVirtualRow.index % 2 === 1) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
staticRowIndex = rowOrVirtualRow.index / 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
staticRowIndex = rowOrVirtualRow.index;
|
||||||
|
}
|
||||||
|
row = rows[staticRowIndex];
|
||||||
|
}
|
||||||
|
const props = {
|
||||||
|
...commonRowProps,
|
||||||
|
pinnedRowIds,
|
||||||
|
row,
|
||||||
|
rowVirtualizer,
|
||||||
|
staticRowIndex,
|
||||||
|
virtualRow: rowVirtualizer
|
||||||
|
? (rowOrVirtualRow)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
const key = `${row.id}-${row.index}`;
|
||||||
|
return memoMode === 'rows' ? (
|
||||||
|
<Memo_DataTable_TableBodyRow key={key} {...props} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyRow key={key} {...props} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
{!rowPinningDisplayMode?.includes('sticky') &&
|
||||||
|
getIsSomeRowsPinned('bottom') && (
|
||||||
|
<TableBody
|
||||||
|
{...tableBodyProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
bottom: tableFooterHeight - 1,
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
|
||||||
|
position: 'sticky',
|
||||||
|
zIndex: 1,
|
||||||
|
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{getBottomRows().map((row, staticRowIndex) => {
|
||||||
|
const props = {
|
||||||
|
...commonRowProps,
|
||||||
|
row,
|
||||||
|
staticRowIndex,
|
||||||
|
};
|
||||||
|
return memoMode === 'rows' ? (
|
||||||
|
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_TableBody
|
||||||
|
|
||||||
|
export const Memo_DataTable_TableBody = memo(
|
||||||
|
DataTable_TableBody,
|
||||||
|
(prev, next) => prev.table.options.data === next.table.options.data,
|
||||||
|
);
|
||||||
307
src/core/components/DataTable/body/TableBodyCell.js
Normal file
307
src/core/components/DataTable/body/TableBodyCell.js
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
import { isCellEditable } from "material-react-table";
|
||||||
|
|
||||||
|
const { parseFromValuesOrFunc, getCommonMRTCellStyles } = require("@/core/utils/utils");
|
||||||
|
const { useTheme } = require("@emotion/react");
|
||||||
|
const { TableCell, Skeleton } = require("@mui/material");
|
||||||
|
const { useState, useEffect, useMemo, memo } = require("react");
|
||||||
|
const { default: DataTable_CopyButton } = require("../buttons/CopyButton");
|
||||||
|
const { default: DataTable_TableBodyCellValue } = require("./TableBodyCellValue");
|
||||||
|
|
||||||
|
const DataTable_TableBodyCell = ({
|
||||||
|
cell,
|
||||||
|
numRows,
|
||||||
|
rowRef,
|
||||||
|
staticColumnIndex,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
columnResizeDirection,
|
||||||
|
columnResizeMode,
|
||||||
|
createDisplayMode,
|
||||||
|
editDisplayMode,
|
||||||
|
enableCellActions,
|
||||||
|
enableClickToCopy,
|
||||||
|
enableColumnOrdering,
|
||||||
|
enableColumnPinning,
|
||||||
|
enableGrouping,
|
||||||
|
layoutMode,
|
||||||
|
mrtTheme: { draggingBorderColor },
|
||||||
|
muiSkeletonProps,
|
||||||
|
muiTableBodyCellProps,
|
||||||
|
},
|
||||||
|
setHoveredColumn,
|
||||||
|
} = table;
|
||||||
|
const {
|
||||||
|
actionCell,
|
||||||
|
columnSizingInfo,
|
||||||
|
creatingRow,
|
||||||
|
density,
|
||||||
|
draggingColumn,
|
||||||
|
draggingRow,
|
||||||
|
editingCell,
|
||||||
|
editingRow,
|
||||||
|
hoveredColumn,
|
||||||
|
hoveredRow,
|
||||||
|
isLoading,
|
||||||
|
showSkeletons,
|
||||||
|
} = getState();
|
||||||
|
const { column, row } = cell;
|
||||||
|
const { columnDef } = column;
|
||||||
|
const { columnDefType } = columnDef;
|
||||||
|
|
||||||
|
const args = { cell, column, row, table };
|
||||||
|
const tableCellProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableBodyCellProps, args),
|
||||||
|
...parseFromValuesOrFunc(columnDef.muiTableBodyCellProps, args),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const skeletonProps = parseFromValuesOrFunc(muiSkeletonProps, {
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [skeletonWidth, setSkeletonWidth] = useState(100);
|
||||||
|
useEffect(() => {
|
||||||
|
if ((!isLoading && !showSkeletons) || skeletonWidth !== 100) return;
|
||||||
|
const size = column.getSize();
|
||||||
|
setSkeletonWidth(
|
||||||
|
columnDefType === 'display'
|
||||||
|
? size / 2
|
||||||
|
: Math.round(Math.random() * (size - size / 3) + size / 3),
|
||||||
|
);
|
||||||
|
}, [isLoading, showSkeletons]);
|
||||||
|
|
||||||
|
const draggingBorders = useMemo(() => {
|
||||||
|
const isDraggingColumn = draggingColumn?.id === column.id;
|
||||||
|
const isHoveredColumn = hoveredColumn?.id === column.id;
|
||||||
|
const isDraggingRow = draggingRow?.id === row.id;
|
||||||
|
const isHoveredRow = hoveredRow?.id === row.id;
|
||||||
|
const isFirstColumn = column.getIsFirstColumn();
|
||||||
|
const isLastColumn = column.getIsLastColumn();
|
||||||
|
const isLastRow = numRows && staticRowIndex === numRows - 1;
|
||||||
|
const isResizingColumn = columnSizingInfo.isResizingColumn === column.id;
|
||||||
|
const showResizeBorder =
|
||||||
|
isResizingColumn && columnResizeMode === 'onChange';
|
||||||
|
|
||||||
|
const borderStyle = showResizeBorder
|
||||||
|
? `2px solid ${draggingBorderColor} !important`
|
||||||
|
: isDraggingColumn || isDraggingRow
|
||||||
|
? `1px dashed ${theme.palette.grey[500]} !important`
|
||||||
|
: isHoveredColumn || isHoveredRow || isResizingColumn
|
||||||
|
? `2px dashed ${draggingBorderColor} !important`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (showResizeBorder) {
|
||||||
|
return columnResizeDirection === 'ltr'
|
||||||
|
? { borderRight: borderStyle }
|
||||||
|
: { borderLeft: borderStyle };
|
||||||
|
}
|
||||||
|
|
||||||
|
return borderStyle
|
||||||
|
? {
|
||||||
|
borderBottom:
|
||||||
|
isDraggingRow || isHoveredRow || (isLastRow && !isResizingColumn)
|
||||||
|
? borderStyle
|
||||||
|
: undefined,
|
||||||
|
borderLeft:
|
||||||
|
isDraggingColumn ||
|
||||||
|
isHoveredColumn ||
|
||||||
|
((isDraggingRow || isHoveredRow) && isFirstColumn)
|
||||||
|
? borderStyle
|
||||||
|
: undefined,
|
||||||
|
borderRight:
|
||||||
|
isDraggingColumn ||
|
||||||
|
isHoveredColumn ||
|
||||||
|
((isDraggingRow || isHoveredRow) && isLastColumn)
|
||||||
|
? borderStyle
|
||||||
|
: undefined,
|
||||||
|
borderTop: isDraggingRow || isHoveredRow ? borderStyle : undefined,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
}, [
|
||||||
|
columnSizingInfo.isResizingColumn,
|
||||||
|
draggingColumn,
|
||||||
|
draggingRow,
|
||||||
|
hoveredColumn,
|
||||||
|
hoveredRow,
|
||||||
|
staticRowIndex,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isColumnPinned =
|
||||||
|
enableColumnPinning &&
|
||||||
|
columnDef.columnDefType !== 'group' &&
|
||||||
|
column.getIsPinned();
|
||||||
|
|
||||||
|
const isEditable = isCellEditable({ cell, table });
|
||||||
|
|
||||||
|
const isEditing =
|
||||||
|
isEditable &&
|
||||||
|
!['custom', 'modal'].includes(editDisplayMode) &&
|
||||||
|
(editDisplayMode === 'table' ||
|
||||||
|
editingRow?.id === row.id ||
|
||||||
|
editingCell?.id === cell.id) &&
|
||||||
|
!row.getIsGrouped();
|
||||||
|
|
||||||
|
const isCreating =
|
||||||
|
isEditable && createDisplayMode === 'row' && creatingRow?.id === row.id;
|
||||||
|
|
||||||
|
const showClickToCopyButton =
|
||||||
|
(parseFromValuesOrFunc(enableClickToCopy, cell) === true ||
|
||||||
|
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === true) &&
|
||||||
|
!['context-menu', false].includes(
|
||||||
|
// @ts-ignore
|
||||||
|
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRightClickable = parseFromValuesOrFunc(enableCellActions, cell);
|
||||||
|
|
||||||
|
const cellValueProps = {
|
||||||
|
cell,
|
||||||
|
table,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDoubleClick = (event) => {
|
||||||
|
tableCellProps?.onDoubleClick?.(event);
|
||||||
|
openEditingCell({ cell, table });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnter = (e) => {
|
||||||
|
tableCellProps?.onDragEnter?.(e);
|
||||||
|
if (enableGrouping && hoveredColumn?.id === 'drop-zone') {
|
||||||
|
setHoveredColumn(null);
|
||||||
|
}
|
||||||
|
if (enableColumnOrdering && draggingColumn) {
|
||||||
|
setHoveredColumn(
|
||||||
|
columnDef.enableColumnOrdering !== false ? column : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
if (columnDef.enableColumnOrdering !== false) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContextMenu = (e) => {
|
||||||
|
tableCellProps?.onContextMenu?.(e);
|
||||||
|
if (isRightClickable) {
|
||||||
|
e.preventDefault();
|
||||||
|
table.setActionCell(cell);
|
||||||
|
table.refs.actionCellRef.current = e.currentTarget;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
align={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||||
|
data-index={staticColumnIndex}
|
||||||
|
data-pinned={!!isColumnPinned || undefined}
|
||||||
|
{...tableCellProps}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
sx={(theme) => ({
|
||||||
|
'&:hover': {
|
||||||
|
outline:
|
||||||
|
actionCell?.id === cell.id ||
|
||||||
|
(editDisplayMode === 'cell' && isEditable) ||
|
||||||
|
(editDisplayMode === 'table' && (isCreating || isEditing))
|
||||||
|
? `1px solid ${theme.palette.grey[500]}`
|
||||||
|
: undefined,
|
||||||
|
textOverflow: 'clip',
|
||||||
|
},
|
||||||
|
alignItems: layoutMode?.startsWith('grid') ? 'center' : undefined,
|
||||||
|
cursor: isRightClickable
|
||||||
|
? 'context-menu'
|
||||||
|
: isEditable && editDisplayMode === 'cell'
|
||||||
|
? 'pointer'
|
||||||
|
: 'inherit',
|
||||||
|
outline:
|
||||||
|
actionCell?.id === cell.id
|
||||||
|
? `1px solid ${theme.palette.grey[500]}`
|
||||||
|
: undefined,
|
||||||
|
outlineOffset: '-1px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
p:
|
||||||
|
density === 'compact'
|
||||||
|
? columnDefType === 'display'
|
||||||
|
? '0 0.5rem'
|
||||||
|
: '0.5rem'
|
||||||
|
: density === 'comfortable'
|
||||||
|
? columnDefType === 'display'
|
||||||
|
? '0.5rem 0.75rem'
|
||||||
|
: '1rem'
|
||||||
|
: columnDefType === 'display'
|
||||||
|
? '1rem 1.25rem'
|
||||||
|
: '1.5rem',
|
||||||
|
|
||||||
|
textOverflow: columnDefType !== 'display' ? 'ellipsis' : undefined,
|
||||||
|
whiteSpace:
|
||||||
|
row.getIsPinned() || density === 'compact' ? 'nowrap' : 'normal',
|
||||||
|
...getCommonMRTCellStyles({
|
||||||
|
column,
|
||||||
|
table,
|
||||||
|
tableCellProps,
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
...draggingBorders,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{tableCellProps.children ?? (
|
||||||
|
<>
|
||||||
|
{cell.getIsPlaceholder() ? (
|
||||||
|
columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null
|
||||||
|
) : showSkeletons !== false && (isLoading || showSkeletons) ? (
|
||||||
|
<Skeleton
|
||||||
|
animation="wave"
|
||||||
|
height={20}
|
||||||
|
width={skeletonWidth}
|
||||||
|
{...skeletonProps}
|
||||||
|
/>
|
||||||
|
) : columnDefType === 'display' &&
|
||||||
|
(['mrt-row-expand', 'mrt-row-numbers', 'mrt-row-select'].includes(
|
||||||
|
column.id,
|
||||||
|
) ||
|
||||||
|
!row.getIsGrouped()) ? (
|
||||||
|
columnDef.Cell?.({
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
renderedCellValue: cell.renderValue(),
|
||||||
|
row,
|
||||||
|
rowRef,
|
||||||
|
staticColumnIndex,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
})
|
||||||
|
) : showClickToCopyButton && columnDef.enableClickToCopy !== false ? (
|
||||||
|
<DataTable_CopyButton cell={cell} table={table}>
|
||||||
|
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||||
|
</DataTable_CopyButton>
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||||
|
)}
|
||||||
|
{cell.getIsGrouped() && !columnDef.GroupedCell && (
|
||||||
|
<> ({row.subRows?.length})</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_TableBodyCell;
|
||||||
|
|
||||||
|
export const Memo_DataTable_TableBodyCell = memo(
|
||||||
|
DataTable_TableBodyCell,
|
||||||
|
(prev, next) => next.cell === prev.cell,
|
||||||
|
);
|
||||||
112
src/core/components/DataTable/body/TableBodyCellValue.js
Normal file
112
src/core/components/DataTable/body/TableBodyCellValue.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
const allowedTypes = ["string", "number"];
|
||||||
|
|
||||||
|
const DataTable_TableBodyCellValue = ({
|
||||||
|
cell,
|
||||||
|
rowRef,
|
||||||
|
staticColumnIndex,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableFilterMatchHighlighting,
|
||||||
|
mrtTheme: { matchHighlightColor },
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
const { column, row } = cell;
|
||||||
|
const { columnDef } = column;
|
||||||
|
const { globalFilter, globalFilterFn } = getState();
|
||||||
|
const filterValue = column.getFilterValue();
|
||||||
|
|
||||||
|
let renderedCellValue =
|
||||||
|
cell.getIsAggregated() && columnDef.AggregatedCell
|
||||||
|
? columnDef.AggregatedCell({
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
})
|
||||||
|
: row.getIsGrouped() && !cell.getIsGrouped()
|
||||||
|
? null
|
||||||
|
: cell.getIsGrouped() && columnDef.GroupedCell
|
||||||
|
? columnDef.GroupedCell({
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const isGroupedValue = renderedCellValue !== undefined;
|
||||||
|
|
||||||
|
if (!isGroupedValue) {
|
||||||
|
renderedCellValue = cell.renderValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
enableFilterMatchHighlighting &&
|
||||||
|
columnDef.enableFilterMatchHighlighting !== false &&
|
||||||
|
String(renderedCellValue) &&
|
||||||
|
allowedTypes.includes(typeof renderedCellValue) &&
|
||||||
|
((filterValue &&
|
||||||
|
allowedTypes.includes(typeof filterValue) &&
|
||||||
|
["autocomplete", "text"].includes(columnDef.filterVariant)) ||
|
||||||
|
(globalFilter &&
|
||||||
|
allowedTypes.includes(typeof globalFilter) &&
|
||||||
|
column.getCanGlobalFilter()))
|
||||||
|
) {
|
||||||
|
const chunks = highlightWords?.({
|
||||||
|
matchExactly:
|
||||||
|
(filterValue ? columnDef._filterFn : globalFilterFn) !== "fuzzy",
|
||||||
|
query: (filterValue ?? globalFilter ?? "").toString(),
|
||||||
|
text: renderedCellValue?.toString(),
|
||||||
|
});
|
||||||
|
if (chunks?.length > 1 || chunks?.[0]?.match) {
|
||||||
|
renderedCellValue = (
|
||||||
|
<span aria-label={renderedCellValue} role="note">
|
||||||
|
{chunks?.map(({ key, match, text }) => (
|
||||||
|
<Box
|
||||||
|
aria-hidden="true"
|
||||||
|
component="span"
|
||||||
|
key={key}
|
||||||
|
sx={
|
||||||
|
match
|
||||||
|
? {
|
||||||
|
backgroundColor: matchHighlightColor,
|
||||||
|
borderRadius: "2px",
|
||||||
|
color: (theme) =>
|
||||||
|
theme.palette.mode === "dark"
|
||||||
|
? theme.palette.common.white
|
||||||
|
: theme.palette.common.black,
|
||||||
|
padding: "2px 1px",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Box>
|
||||||
|
)) ?? renderedCellValue}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnDef.Cell && !isGroupedValue) {
|
||||||
|
renderedCellValue = columnDef.Cell({
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
renderedCellValue,
|
||||||
|
row,
|
||||||
|
rowRef,
|
||||||
|
staticColumnIndex,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderedCellValue;
|
||||||
|
};
|
||||||
|
export default DataTable_TableBodyCellValue;
|
||||||
254
src/core/components/DataTable/body/TableBodyRow.js
Normal file
254
src/core/components/DataTable/body/TableBodyRow.js
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
import { commonCellBeforeAfterStyles, getCommonPinnedCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { TableRow, alpha, darken, lighten } from "@mui/material";
|
||||||
|
import { getIsRowSelected } from "material-react-table";
|
||||||
|
import { memo, useMemo, useRef } from "react";
|
||||||
|
import DataTable_TableBodyCell, { Memo_DataTable_TableBodyCell } from "./TableBodyCell";
|
||||||
|
import DataTable_TableDetailPanel from "./TableDetailPanel";
|
||||||
|
|
||||||
|
const DataTable_TableBodyRow = ({
|
||||||
|
columnVirtualizer,
|
||||||
|
numRows,
|
||||||
|
pinnedRowIds,
|
||||||
|
row,
|
||||||
|
rowVirtualizer,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
virtualRow,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableRowOrdering,
|
||||||
|
enableRowPinning,
|
||||||
|
enableStickyFooter,
|
||||||
|
enableStickyHeader,
|
||||||
|
layoutMode,
|
||||||
|
memoMode,
|
||||||
|
mrtTheme: {
|
||||||
|
baseBackgroundColor,
|
||||||
|
pinnedRowBackgroundColor,
|
||||||
|
selectedRowBackgroundColor,
|
||||||
|
},
|
||||||
|
muiTableBodyRowProps,
|
||||||
|
renderDetailPanel,
|
||||||
|
rowPinningDisplayMode,
|
||||||
|
},
|
||||||
|
refs: { tableFooterRef, tableHeadRef },
|
||||||
|
setHoveredRow,
|
||||||
|
} = table;
|
||||||
|
const {
|
||||||
|
density,
|
||||||
|
draggingColumn,
|
||||||
|
draggingRow,
|
||||||
|
editingCell,
|
||||||
|
editingRow,
|
||||||
|
hoveredRow,
|
||||||
|
isFullScreen,
|
||||||
|
rowPinning,
|
||||||
|
} = getState();
|
||||||
|
|
||||||
|
const visibleCells = row.getVisibleCells();
|
||||||
|
|
||||||
|
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
|
||||||
|
columnVirtualizer ?? {};
|
||||||
|
|
||||||
|
const isRowSelected = getIsRowSelected({ row, table });
|
||||||
|
const isRowPinned = enableRowPinning && row.getIsPinned();
|
||||||
|
const isDraggingRow = draggingRow?.id === row.id;
|
||||||
|
const isHoveredRow = hoveredRow?.id === row.id;
|
||||||
|
|
||||||
|
const tableRowProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||||
|
row,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
|
||||||
|
if (
|
||||||
|
!enableRowPinning ||
|
||||||
|
!rowPinningDisplayMode?.includes('sticky') ||
|
||||||
|
!pinnedRowIds ||
|
||||||
|
!row.getIsPinned()
|
||||||
|
)
|
||||||
|
return [];
|
||||||
|
return [
|
||||||
|
[...pinnedRowIds].reverse().indexOf(row.id),
|
||||||
|
pinnedRowIds.indexOf(row.id),
|
||||||
|
];
|
||||||
|
}, [pinnedRowIds, rowPinning]);
|
||||||
|
|
||||||
|
const tableHeadHeight =
|
||||||
|
((enableStickyHeader || isFullScreen) &&
|
||||||
|
tableHeadRef.current?.clientHeight) ||
|
||||||
|
0;
|
||||||
|
const tableFooterHeight =
|
||||||
|
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||||
|
|
||||||
|
const sx = parseFromValuesOrFunc(tableRowProps?.sx, theme);
|
||||||
|
|
||||||
|
const defaultRowHeight =
|
||||||
|
density === 'compact' ? 37 : density === 'comfortable' ? 53 : 69;
|
||||||
|
|
||||||
|
const customRowHeight =
|
||||||
|
// @ts-ignore
|
||||||
|
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
|
||||||
|
|
||||||
|
const rowHeight = customRowHeight || defaultRowHeight;
|
||||||
|
|
||||||
|
const handleDragEnter = (_e) => {
|
||||||
|
if (enableRowOrdering && draggingRow) {
|
||||||
|
setHoveredRow(row);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const rowRef = useRef(null);
|
||||||
|
|
||||||
|
const cellHighlightColor = isRowSelected
|
||||||
|
? selectedRowBackgroundColor
|
||||||
|
: isRowPinned
|
||||||
|
? pinnedRowBackgroundColor
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const cellHighlightColorHover =
|
||||||
|
tableRowProps?.hover !== false
|
||||||
|
? isRowSelected
|
||||||
|
? cellHighlightColor
|
||||||
|
: theme.palette.mode === 'dark'
|
||||||
|
? `${lighten(baseBackgroundColor, 0.3)}`
|
||||||
|
: `${darken(baseBackgroundColor, 0.3)}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableRow
|
||||||
|
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
|
||||||
|
data-pinned={!!isRowPinned || undefined}
|
||||||
|
data-selected={isRowSelected || undefined}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
ref={(node) => {
|
||||||
|
if (node) {
|
||||||
|
rowRef.current = node;
|
||||||
|
rowVirtualizer?.measureElement(node);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
selected={isRowSelected}
|
||||||
|
{...tableRowProps}
|
||||||
|
style={{
|
||||||
|
transform: virtualRow
|
||||||
|
? `translateY(${virtualRow.start}px)`
|
||||||
|
: undefined,
|
||||||
|
...tableRowProps?.style,
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
'&:hover td:after': cellHighlightColorHover
|
||||||
|
? {
|
||||||
|
backgroundColor: alpha(cellHighlightColorHover, 0.3),
|
||||||
|
...commonCellBeforeAfterStyles,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
backgroundColor: `${baseBackgroundColor} !important`,
|
||||||
|
bottom:
|
||||||
|
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
|
||||||
|
? `${bottomPinnedIndex * rowHeight +
|
||||||
|
(enableStickyFooter ? tableFooterHeight - 1 : 0)
|
||||||
|
}px`
|
||||||
|
: undefined,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
|
||||||
|
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
|
||||||
|
position: virtualRow
|
||||||
|
? 'absolute'
|
||||||
|
: rowPinningDisplayMode?.includes('sticky') && isRowPinned
|
||||||
|
? 'sticky'
|
||||||
|
: 'relative',
|
||||||
|
td: {
|
||||||
|
...getCommonPinnedCellStyles({ table, theme }),
|
||||||
|
},
|
||||||
|
'td:after': cellHighlightColor
|
||||||
|
? {
|
||||||
|
backgroundColor: cellHighlightColor,
|
||||||
|
...commonCellBeforeAfterStyles,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
top: virtualRow
|
||||||
|
? 0
|
||||||
|
: topPinnedIndex !== undefined && isRowPinned
|
||||||
|
? `${topPinnedIndex * rowHeight +
|
||||||
|
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
|
||||||
|
}px`
|
||||||
|
: undefined,
|
||||||
|
transition: virtualRow ? 'none' : 'all 150ms ease-in-out',
|
||||||
|
width: '100%',
|
||||||
|
zIndex:
|
||||||
|
rowPinningDisplayMode?.includes('sticky') && isRowPinned ? 2 : 0,
|
||||||
|
...(sx),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{virtualPaddingLeft ? (
|
||||||
|
<td style={{ display: 'flex', width: virtualPaddingLeft }} />
|
||||||
|
) : null}
|
||||||
|
{(virtualColumns ?? visibleCells).map(
|
||||||
|
(cellOrVirtualCell, staticColumnIndex) => {
|
||||||
|
let cell = cellOrVirtualCell;
|
||||||
|
if (columnVirtualizer) {
|
||||||
|
staticColumnIndex = (cellOrVirtualCell).index;
|
||||||
|
cell = visibleCells[staticColumnIndex];
|
||||||
|
}
|
||||||
|
const props = {
|
||||||
|
cell,
|
||||||
|
numRows,
|
||||||
|
rowRef,
|
||||||
|
staticColumnIndex,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
};
|
||||||
|
return cell ? (
|
||||||
|
memoMode === 'cells' &&
|
||||||
|
cell.column.columnDef.columnDefType === 'data' &&
|
||||||
|
!draggingColumn &&
|
||||||
|
!draggingRow &&
|
||||||
|
editingCell?.id !== cell.id &&
|
||||||
|
editingRow?.id !== row.id ? (
|
||||||
|
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyCell key={cell.id} {...props} />
|
||||||
|
)
|
||||||
|
) : null;
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
{virtualPaddingRight ? (
|
||||||
|
<td style={{ display: 'flex', width: virtualPaddingRight }} />
|
||||||
|
) : null}
|
||||||
|
</TableRow>
|
||||||
|
{renderDetailPanel && !row.getIsGrouped() && (
|
||||||
|
<DataTable_TableDetailPanel
|
||||||
|
parentRowRef={rowRef}
|
||||||
|
row={row}
|
||||||
|
rowVirtualizer={rowVirtualizer}
|
||||||
|
staticRowIndex={staticRowIndex}
|
||||||
|
table={table}
|
||||||
|
virtualRow={virtualRow}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_TableBodyRow
|
||||||
|
|
||||||
|
export const Memo_DataTable_TableBodyRow = memo(
|
||||||
|
DataTable_TableBodyRow,
|
||||||
|
(prev, next) =>
|
||||||
|
prev.row === next.row && prev.staticRowIndex === next.staticRowIndex,
|
||||||
|
);
|
||||||
93
src/core/components/DataTable/body/TableDetailPanel.js
Normal file
93
src/core/components/DataTable/body/TableDetailPanel.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Collapse, TableCell, TableRow } from "@mui/material";
|
||||||
|
|
||||||
|
const DataTable_TableDetailPanel = ({
|
||||||
|
parentRowRef,
|
||||||
|
row,
|
||||||
|
rowVirtualizer,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
virtualRow,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
getVisibleLeafColumns,
|
||||||
|
options: {
|
||||||
|
layoutMode,
|
||||||
|
mrtTheme: { baseBackgroundColor },
|
||||||
|
muiDetailPanelProps,
|
||||||
|
muiTableBodyRowProps,
|
||||||
|
renderDetailPanel,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
const { isLoading } = getState();
|
||||||
|
|
||||||
|
|
||||||
|
const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||||
|
isDetailPanel: true,
|
||||||
|
row,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableCellProps = {
|
||||||
|
...parseFromValuesOrFunc(muiDetailPanelProps, {
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
className="Mui-TableBodyCell-DetailPanel"
|
||||||
|
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
|
||||||
|
ref={(node) => {
|
||||||
|
if (node) {
|
||||||
|
rowVirtualizer?.measureElement?.(node);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...tableRowProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
|
||||||
|
position: virtualRow ? 'absolute' : undefined,
|
||||||
|
top: virtualRow
|
||||||
|
? `${parentRowRef.current?.getBoundingClientRect()?.height}px`
|
||||||
|
: undefined,
|
||||||
|
transform: virtualRow
|
||||||
|
? `translateY(${virtualRow?.start}px)`
|
||||||
|
: undefined,
|
||||||
|
width: '100%',
|
||||||
|
...(parseFromValuesOrFunc(tableRowProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<TableCell
|
||||||
|
className="Mui-TableBodyCell-DetailPanel"
|
||||||
|
colSpan={getVisibleLeafColumns().length}
|
||||||
|
{...tableCellProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
|
||||||
|
borderBottom: !row.getIsExpanded() ? 'none' : undefined,
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
|
||||||
|
py: !!DetailPanel && row.getIsExpanded() ? '1rem' : 0,
|
||||||
|
transition: !virtualRow ? 'all 150ms ease-in-out' : undefined,
|
||||||
|
width: `100%`,
|
||||||
|
...(parseFromValuesOrFunc(tableCellProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{virtualRow ? (
|
||||||
|
row.getIsExpanded() && DetailPanel
|
||||||
|
) : (
|
||||||
|
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
|
||||||
|
{DetailPanel}
|
||||||
|
</Collapse>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_TableDetailPanel
|
||||||
74
src/core/components/DataTable/buttons/CopyButton.js
Normal file
74
src/core/components/DataTable/buttons/CopyButton.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Button, Tooltip } from "@mui/material";
|
||||||
|
|
||||||
|
const DataTable_CopyButton = ({
|
||||||
|
cell,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
options: { localization, muiCopyButtonProps },
|
||||||
|
} = table;
|
||||||
|
const { column, row } = cell;
|
||||||
|
const { columnDef } = column;
|
||||||
|
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleCopy = (event, text) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 4000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonProps = {
|
||||||
|
...parseFromValuesOrFunc(muiCopyButtonProps, {
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
{...getCommonTooltipProps('top')}
|
||||||
|
title={
|
||||||
|
buttonProps?.title ??
|
||||||
|
(copied ? localization.copiedToClipboard : localization.clickToCopy)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={(e) => handleCopy(e, cell.getValue())}
|
||||||
|
size="small"
|
||||||
|
type="button"
|
||||||
|
variant="text"
|
||||||
|
{...buttonProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
border: 'none',
|
||||||
|
color: 'inherit',
|
||||||
|
cursor: 'copy',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
fontSize: 'inherit',
|
||||||
|
letterSpacing: 'inherit',
|
||||||
|
m: '-0.25rem',
|
||||||
|
minWidth: 'unset',
|
||||||
|
py: 0,
|
||||||
|
textAlign: 'inherit',
|
||||||
|
textTransform: 'inherit',
|
||||||
|
...(parseFromValuesOrFunc(buttonProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
title={undefined}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_CopyButton
|
||||||
54
src/core/components/DataTable/buttons/GrabHandleButton.js
Normal file
54
src/core/components/DataTable/buttons/GrabHandleButton.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { IconButton, Tooltip } from "@mui/material";
|
||||||
|
|
||||||
|
const DataTable_GrabHandleButton = ({
|
||||||
|
location,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
options: {
|
||||||
|
icons: { DragHandleIcon },
|
||||||
|
localization,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
{...getCommonTooltipProps('top')}
|
||||||
|
title={rest?.title ?? localization.move}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label={rest.title ?? localization.move}
|
||||||
|
disableRipple
|
||||||
|
draggable="true"
|
||||||
|
size="small"
|
||||||
|
{...rest}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
rest?.onClick?.(e);
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
'&:active': {
|
||||||
|
cursor: 'grabbing',
|
||||||
|
},
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
cursor: 'grab',
|
||||||
|
m: '0 -0.1rem',
|
||||||
|
opacity: location === 'row' ? 1 : 0.5,
|
||||||
|
p: '2px',
|
||||||
|
transition: 'all 150ms ease-in-out',
|
||||||
|
...(parseFromValuesOrFunc(rest?.sx, theme)),
|
||||||
|
})}
|
||||||
|
title={undefined}
|
||||||
|
>
|
||||||
|
<DragHandleIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_GrabHandleButton
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { IconButton, Tooltip } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import DataTable_ShowHideColumnsMenu from "../menus/ShowHideColumnsMenu";
|
||||||
|
|
||||||
|
const DataTable_ShowHideColumnsButton = ({ table, ...rest }) => {
|
||||||
|
const {
|
||||||
|
options: {
|
||||||
|
icons: { ViewColumnIcon },
|
||||||
|
localization,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
|
||||||
|
const handleClick = (event) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title={rest?.title ?? localization.showHideColumns}>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.showHideColumns}
|
||||||
|
onClick={handleClick}
|
||||||
|
{...rest}
|
||||||
|
title={undefined}
|
||||||
|
>
|
||||||
|
<ViewColumnIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
{anchorEl && (
|
||||||
|
<DataTable_ShowHideColumnsMenu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
setAnchorEl={setAnchorEl}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_ShowHideColumnsButton
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
export default DataTable_ToggleFiltersButton
|
||||||
63
src/core/components/DataTable/head/TableHead.js
Normal file
63
src/core/components/DataTable/head/TableHead.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { TableHead } from "@mui/material";
|
||||||
|
import DataTable_TableHeadRow from "./TableHeadRow";
|
||||||
|
|
||||||
|
const DataTable_TableHead = ({
|
||||||
|
columnVirtualizer,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableStickyHeader,
|
||||||
|
layoutMode,
|
||||||
|
muiTableHeadProps,
|
||||||
|
positionToolbarAlertBanner,
|
||||||
|
},
|
||||||
|
refs: { tableHeadRef },
|
||||||
|
} = table;
|
||||||
|
const { isFullScreen, showAlertBanner } = getState();
|
||||||
|
|
||||||
|
const tableHeadProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableHeadProps, { table }),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const stickyHeader = enableStickyHeader || isFullScreen;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead
|
||||||
|
{...tableHeadProps}
|
||||||
|
ref={(ref) => {
|
||||||
|
tableHeadRef.current = ref;
|
||||||
|
if (tableHeadProps?.ref) {
|
||||||
|
// @ts-ignore
|
||||||
|
tableHeadProps.ref.current = ref;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
|
||||||
|
opacity: 0.97,
|
||||||
|
position: stickyHeader ? 'sticky' : 'relative',
|
||||||
|
top: stickyHeader && layoutMode?.startsWith('grid') ? 0 : undefined,
|
||||||
|
zIndex: stickyHeader ? 2 : undefined,
|
||||||
|
...(parseFromValuesOrFunc(tableHeadProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{table
|
||||||
|
.getHeaderGroups()
|
||||||
|
.map((headerGroup) => (
|
||||||
|
<DataTable_TableHeadRow
|
||||||
|
columnVirtualizer={columnVirtualizer}
|
||||||
|
headerGroup={headerGroup}
|
||||||
|
key={headerGroup.id}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_TableHead
|
||||||
284
src/core/components/DataTable/head/TableHeadCell.js
Normal file
284
src/core/components/DataTable/head/TableHeadCell.js
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { Box, TableCell } from "@mui/material";
|
||||||
|
import { MRT_TableHeadCellSortLabel } from "material-react-table";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const DataTable_TableHeadCell = ({
|
||||||
|
columnVirtualizer,
|
||||||
|
header,
|
||||||
|
staticColumnIndex,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
columnResizeDirection,
|
||||||
|
columnResizeMode,
|
||||||
|
enableColumnActions,
|
||||||
|
enableColumnDragging,
|
||||||
|
enableColumnOrdering,
|
||||||
|
enableColumnPinning,
|
||||||
|
enableGrouping,
|
||||||
|
enableMultiSort,
|
||||||
|
layoutMode,
|
||||||
|
mrtTheme: { draggingBorderColor },
|
||||||
|
muiTableHeadCellProps,
|
||||||
|
},
|
||||||
|
refs: { tableHeadCellRefs },
|
||||||
|
setHoveredColumn,
|
||||||
|
} = table;
|
||||||
|
const {
|
||||||
|
columnSizingInfo,
|
||||||
|
density,
|
||||||
|
draggingColumn,
|
||||||
|
grouping,
|
||||||
|
hoveredColumn,
|
||||||
|
showColumnFilters,
|
||||||
|
} = getState();
|
||||||
|
const { column } = header;
|
||||||
|
const { columnDef } = column;
|
||||||
|
const { columnDefType } = columnDef;
|
||||||
|
|
||||||
|
const tableCellProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableHeadCellProps, { column, table }),
|
||||||
|
...parseFromValuesOrFunc(columnDef.muiTableHeadCellProps, {
|
||||||
|
column,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isColumnPinned =
|
||||||
|
enableColumnPinning &&
|
||||||
|
columnDef.columnDefType !== 'group' &&
|
||||||
|
column.getIsPinned();
|
||||||
|
|
||||||
|
const showColumnActions =
|
||||||
|
(enableColumnActions || columnDef.enableColumnActions) &&
|
||||||
|
columnDef.enableColumnActions !== false;
|
||||||
|
|
||||||
|
const showDragHandle =
|
||||||
|
enableColumnDragging !== false &&
|
||||||
|
columnDef.enableColumnDragging !== false &&
|
||||||
|
(enableColumnDragging ||
|
||||||
|
(enableColumnOrdering && columnDef.enableColumnOrdering !== false) ||
|
||||||
|
(enableGrouping &&
|
||||||
|
columnDef.enableGrouping !== false &&
|
||||||
|
!grouping.includes(column.id)));
|
||||||
|
|
||||||
|
const headerPL = useMemo(() => {
|
||||||
|
let pl = 0;
|
||||||
|
if (column.getCanSort()) pl += 1;
|
||||||
|
if (showColumnActions) pl += 1.75;
|
||||||
|
if (showDragHandle) pl += 1.5;
|
||||||
|
return pl;
|
||||||
|
}, [showColumnActions, showDragHandle]);
|
||||||
|
|
||||||
|
const draggingBorders = useMemo(() => {
|
||||||
|
const showResizeBorder =
|
||||||
|
columnSizingInfo.isResizingColumn === column.id &&
|
||||||
|
columnResizeMode === 'onChange' &&
|
||||||
|
!header.subHeaders.length;
|
||||||
|
|
||||||
|
const borderStyle = showResizeBorder
|
||||||
|
? `2px solid ${draggingBorderColor} !important`
|
||||||
|
: draggingColumn?.id === column.id
|
||||||
|
? `1px dashed ${theme.palette.grey[500]}`
|
||||||
|
: hoveredColumn?.id === column.id
|
||||||
|
? `2px dashed ${draggingBorderColor}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (showResizeBorder) {
|
||||||
|
return columnResizeDirection === 'ltr'
|
||||||
|
? { borderRight: borderStyle }
|
||||||
|
: { borderLeft: borderStyle };
|
||||||
|
}
|
||||||
|
const draggingBorders = borderStyle
|
||||||
|
? {
|
||||||
|
borderLeft: borderStyle,
|
||||||
|
borderRight: borderStyle,
|
||||||
|
borderTop: borderStyle,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return draggingBorders;
|
||||||
|
}, [draggingColumn, hoveredColumn, columnSizingInfo.isResizingColumn]);
|
||||||
|
|
||||||
|
const handleDragEnter = (_e) => {
|
||||||
|
if (enableGrouping && hoveredColumn?.id === 'drop-zone') {
|
||||||
|
setHoveredColumn(null);
|
||||||
|
}
|
||||||
|
if (enableColumnOrdering && draggingColumn && columnDefType !== 'group') {
|
||||||
|
setHoveredColumn(
|
||||||
|
columnDef.enableColumnOrdering !== false ? column : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
if (columnDef.enableColumnOrdering !== false) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const HeaderElement =
|
||||||
|
parseFromValuesOrFunc(columnDef.Header, {
|
||||||
|
column,
|
||||||
|
header,
|
||||||
|
table,
|
||||||
|
}) ?? columnDef.header;
|
||||||
|
|
||||||
|
|
||||||
|
const columnRelativeDepth = header.depth - column.depth;
|
||||||
|
|
||||||
|
if (
|
||||||
|
columnRelativeDepth > 1
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rowSpan = 1;
|
||||||
|
if (header.isPlaceholder) {
|
||||||
|
const leafs = header.getLeafHeaders();
|
||||||
|
rowSpan = leafs[leafs.length - 1].depth - header.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
align={
|
||||||
|
columnDefType === 'group'
|
||||||
|
? 'center'
|
||||||
|
: theme.direction === 'rtl'
|
||||||
|
? 'right'
|
||||||
|
: 'left'
|
||||||
|
}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
rowSpan={rowSpan}
|
||||||
|
data-index={staticColumnIndex}
|
||||||
|
data-pinned={!!isColumnPinned || undefined}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
ref={(node) => {
|
||||||
|
if (node) {
|
||||||
|
tableHeadCellRefs.current[column.id] = node;
|
||||||
|
if (columnDefType !== 'group') {
|
||||||
|
columnVirtualizer?.measureElement?.(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...tableCellProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
'& :hover': {
|
||||||
|
'.MuiButtonBase-root': {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
border: 1,
|
||||||
|
flexDirection: layoutMode?.startsWith('grid') ? 'column' : undefined,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
overflow: 'visible',
|
||||||
|
p:
|
||||||
|
density === 'compact'
|
||||||
|
? '0.5rem'
|
||||||
|
: density === 'comfortable'
|
||||||
|
? columnDefType === 'display'
|
||||||
|
? '0.75rem'
|
||||||
|
: '1rem'
|
||||||
|
: columnDefType === 'display'
|
||||||
|
? '1rem 1.25rem'
|
||||||
|
: '1.5rem',
|
||||||
|
pb:
|
||||||
|
columnDefType === 'display'
|
||||||
|
? 0
|
||||||
|
: showColumnFilters || density === 'compact'
|
||||||
|
? '0.4rem'
|
||||||
|
: '0.6rem',
|
||||||
|
pt:
|
||||||
|
columnDefType === 'group' || density === 'compact'
|
||||||
|
? '0.25rem'
|
||||||
|
: density === 'comfortable'
|
||||||
|
? '.75rem'
|
||||||
|
: '1.25rem',
|
||||||
|
userSelect: enableMultiSort && column.getCanSort() ? 'none' : undefined,
|
||||||
|
verticalAlign: 'top',
|
||||||
|
...getCommonMRTCellStyles({
|
||||||
|
column,
|
||||||
|
header,
|
||||||
|
table,
|
||||||
|
tableCellProps,
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
...draggingBorders,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{tableCellProps.children ?? (
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content"
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection:
|
||||||
|
tableCellProps?.align === 'right' ? 'row-reverse' : 'row',
|
||||||
|
justifyContent:
|
||||||
|
columnDefType === 'group' ||
|
||||||
|
tableCellProps?.align === 'center'
|
||||||
|
? 'center'
|
||||||
|
: column.getCanResize()
|
||||||
|
? 'space-between'
|
||||||
|
: 'flex-start',
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content-Labels"
|
||||||
|
onClick={column.getToggleSortingHandler()}
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
cursor:
|
||||||
|
column.getCanSort() && columnDefType !== 'group'
|
||||||
|
? 'pointer'
|
||||||
|
: undefined,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection:
|
||||||
|
tableCellProps?.align === 'right' ? 'row-reverse' : 'row',
|
||||||
|
overflow: columnDefType === 'data' ? 'hidden' : undefined,
|
||||||
|
pl:
|
||||||
|
tableCellProps?.align === 'center'
|
||||||
|
? `${headerPL}rem`
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content-Wrapper"
|
||||||
|
sx={{
|
||||||
|
'&:hover': {
|
||||||
|
textOverflow: 'clip',
|
||||||
|
},
|
||||||
|
minWidth: `${Math.min(columnDef.header?.length ?? 0, 4)}ch`,
|
||||||
|
overflow: columnDefType === 'data' ? 'hidden' : undefined,
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace:
|
||||||
|
(columnDef.header?.length ?? 0) < 20
|
||||||
|
? 'nowrap'
|
||||||
|
: 'normal',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{HeaderElement}
|
||||||
|
</Box>
|
||||||
|
{(column.getCanSort() && columnDefType !== 'group') && (
|
||||||
|
<MRT_TableHeadCellSortLabel sx={{ width: 20 }} header={header} table={table} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_TableHeadCell;
|
||||||
76
src/core/components/DataTable/head/TableHeadRow.js
Normal file
76
src/core/components/DataTable/head/TableHeadRow.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { TableRow } from "@mui/material";
|
||||||
|
import DataTable_TableHeadCell from "./TableHeadCell";
|
||||||
|
|
||||||
|
const DataTable_TableHeadRow = ({
|
||||||
|
columnVirtualizer,
|
||||||
|
headerGroup,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
options: {
|
||||||
|
enableStickyHeader,
|
||||||
|
layoutMode,
|
||||||
|
mrtTheme: { baseBackgroundColor },
|
||||||
|
muiTableHeadRowProps,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
|
||||||
|
columnVirtualizer ?? {};
|
||||||
|
|
||||||
|
const tableRowProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableHeadRowProps, {
|
||||||
|
headerGroup,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
{...tableRowProps}
|
||||||
|
sx={(theme) => ({
|
||||||
|
backgroundColor: baseBackgroundColor,
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
|
||||||
|
position:
|
||||||
|
enableStickyHeader && layoutMode === 'semantic'
|
||||||
|
? 'sticky'
|
||||||
|
: 'relative',
|
||||||
|
top: 0,
|
||||||
|
...(parseFromValuesOrFunc(tableRowProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{virtualPaddingLeft ? (
|
||||||
|
<th style={{ display: 'flex', width: virtualPaddingLeft }} />
|
||||||
|
) : null}
|
||||||
|
{(virtualColumns ?? headerGroup.headers).map(
|
||||||
|
(headerOrVirtualHeader, staticColumnIndex) => {
|
||||||
|
let header = headerOrVirtualHeader;
|
||||||
|
|
||||||
|
if (columnVirtualizer) {
|
||||||
|
staticColumnIndex = (headerOrVirtualHeader)
|
||||||
|
.index;
|
||||||
|
header = headerGroup.headers[staticColumnIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return header ? (
|
||||||
|
<DataTable_TableHeadCell
|
||||||
|
columnVirtualizer={columnVirtualizer}
|
||||||
|
header={header}
|
||||||
|
key={header.id}
|
||||||
|
staticColumnIndex={staticColumnIndex}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
{virtualPaddingRight ? (
|
||||||
|
<th style={{ display: 'flex', width: virtualPaddingRight }} />
|
||||||
|
) : null}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_TableHeadRow
|
||||||
19
src/core/components/DataTable/index.js
Normal file
19
src/core/components/DataTable/index.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useMaterialReactTable } from "material-react-table";
|
||||||
|
import DataTable_Paper from "./table/Paper";
|
||||||
|
|
||||||
|
const isTableInstanceProp = (props) => props.table !== undefined;
|
||||||
|
|
||||||
|
const DataTable = (props) => {
|
||||||
|
let table;
|
||||||
|
|
||||||
|
if (isTableInstanceProp(props)) {
|
||||||
|
table = props.table;
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
table = useMaterialReactTable(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <DataTable_Paper table={table} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataTable;
|
||||||
50
src/core/components/DataTable/menus/ActionMenuItem.js
Normal file
50
src/core/components/DataTable/menus/ActionMenuItem.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Box, IconButton, ListItemIcon, MenuItem } from "@mui/material";
|
||||||
|
|
||||||
|
const DataTable_ActionMenuItem = ({
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
onOpenSubMenu,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
options: {
|
||||||
|
icons: { ArrowRightIcon },
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
minWidth: '120px',
|
||||||
|
my: 0,
|
||||||
|
py: '6px',
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>{icon}</ListItemIcon>
|
||||||
|
{label}
|
||||||
|
</Box>
|
||||||
|
{onOpenSubMenu && (
|
||||||
|
<IconButton
|
||||||
|
onClick={onOpenSubMenu}
|
||||||
|
onMouseEnter={onOpenSubMenu}
|
||||||
|
size="small"
|
||||||
|
sx={{ p: 0 }}
|
||||||
|
>
|
||||||
|
<ArrowRightIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_ActionMenuItem
|
||||||
100
src/core/components/DataTable/menus/CellActionMenu.js
Normal file
100
src/core/components/DataTable/menus/CellActionMenu.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Menu } from "@mui/material";
|
||||||
|
import DataTable_ActionMenuItem from "./ActionMenuItem";
|
||||||
|
|
||||||
|
const DataTable_CellActionMenu = ({
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
editDisplayMode,
|
||||||
|
enableClickToCopy,
|
||||||
|
enableEditing,
|
||||||
|
icons: { ContentCopy, EditIcon },
|
||||||
|
localization,
|
||||||
|
mrtTheme: { menuBackgroundColor },
|
||||||
|
renderCellActionMenuItems,
|
||||||
|
},
|
||||||
|
refs: { actionCellRef },
|
||||||
|
} = table;
|
||||||
|
const { actionCell, density } = getState();
|
||||||
|
const cell = actionCell || null;
|
||||||
|
const { row } = cell;
|
||||||
|
const { column } = cell;
|
||||||
|
const { columnDef } = column;
|
||||||
|
|
||||||
|
const handleClose = (event) => {
|
||||||
|
event?.stopPropagation();
|
||||||
|
table.setActionCell(null);
|
||||||
|
actionCellRef.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const internalMenuItems = [
|
||||||
|
(parseFromValuesOrFunc(enableClickToCopy, cell) === 'context-menu' ||
|
||||||
|
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) ===
|
||||||
|
'context-menu') && (
|
||||||
|
<DataTable_ActionMenuItem
|
||||||
|
icon={<ContentCopy />}
|
||||||
|
key={'mrt-copy'}
|
||||||
|
label={localization.copy}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
navigator.clipboard.writeText(cell.getValue());
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
parseFromValuesOrFunc(enableEditing, row) && editDisplayMode === 'cell' && (
|
||||||
|
<DataTable_ActionMenuItem
|
||||||
|
icon={<EditIcon />}
|
||||||
|
key={'mrt-edit'}
|
||||||
|
label={localization.edit}
|
||||||
|
onClick={() => {
|
||||||
|
openEditingCell({ cell, table });
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const renderActionProps = {
|
||||||
|
cell,
|
||||||
|
closeMenu: handleClose,
|
||||||
|
column,
|
||||||
|
internalMenuItems,
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
};
|
||||||
|
|
||||||
|
const menuItems =
|
||||||
|
columnDef.renderCellActionMenuItems?.(renderActionProps) ??
|
||||||
|
renderCellActionMenuItems?.(renderActionProps);
|
||||||
|
|
||||||
|
return (
|
||||||
|
(!!menuItems?.length || !!internalMenuItems?.length) && (
|
||||||
|
<Menu
|
||||||
|
MenuListProps={{
|
||||||
|
dense: density === 'compact',
|
||||||
|
sx: {
|
||||||
|
backgroundColor: menuBackgroundColor,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
anchorEl={actionCellRef.current}
|
||||||
|
disableScrollLock
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
onClose={handleClose}
|
||||||
|
open={!!cell}
|
||||||
|
transformOrigin={{ horizontal: -100, vertical: 8 }}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{menuItems ?? internalMenuItems}
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_CellActionMenu
|
||||||
140
src/core/components/DataTable/menus/ShowHideColumnsMenu.js
Normal file
140
src/core/components/DataTable/menus/ShowHideColumnsMenu.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { Box, Button, Divider, Menu } from "@mui/material";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import DataTable_ShowHideColumnsMenuItems from "./ShowHideColumnsMenuItems";
|
||||||
|
|
||||||
|
const DataTable_ShowHideColumnsMenu = ({ anchorEl,
|
||||||
|
setAnchorEl,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getAllColumns,
|
||||||
|
getAllLeafColumns,
|
||||||
|
getCenterLeafColumns,
|
||||||
|
getIsAllColumnsVisible,
|
||||||
|
getIsSomeColumnsPinned,
|
||||||
|
getIsSomeColumnsVisible,
|
||||||
|
getLeftLeafColumns,
|
||||||
|
getRightLeafColumns,
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableColumnOrdering,
|
||||||
|
enableColumnPinning,
|
||||||
|
enableHiding,
|
||||||
|
localization,
|
||||||
|
mrtTheme: { menuBackgroundColor },
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const { columnOrder, columnPinning, density } = getState();
|
||||||
|
|
||||||
|
const handleToggleAllColumns = (value) => {
|
||||||
|
getAllLeafColumns()
|
||||||
|
.filter((col) => col.columnDef.enableHiding !== false)
|
||||||
|
.forEach((col) => col.toggleVisibility(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
const allColumns = useMemo(() => {
|
||||||
|
const columns = getAllColumns();
|
||||||
|
if (
|
||||||
|
columnOrder.length > 0 &&
|
||||||
|
!columns.some((col) => col.columnDef.columnDefType === 'group')
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
...getLeftLeafColumns(),
|
||||||
|
...Array.from(new Set(columnOrder)).map((colId) =>
|
||||||
|
getCenterLeafColumns().find((col) => col?.id === colId),
|
||||||
|
),
|
||||||
|
...getRightLeafColumns(),
|
||||||
|
].filter(Boolean);
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}, [
|
||||||
|
columnOrder,
|
||||||
|
columnPinning,
|
||||||
|
getAllColumns(),
|
||||||
|
getCenterLeafColumns(),
|
||||||
|
getLeftLeafColumns(),
|
||||||
|
getRightLeafColumns(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isNestedColumns = allColumns.some(
|
||||||
|
(col) => col.columnDef.columnDefType === 'group',
|
||||||
|
);
|
||||||
|
|
||||||
|
const [hoveredColumn, setHoveredColumn] = useState(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
MenuListProps={{
|
||||||
|
dense: density === 'compact',
|
||||||
|
sx: {
|
||||||
|
backgroundColor: menuBackgroundColor,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
disableScrollLock
|
||||||
|
onClose={() => setAnchorEl(null)}
|
||||||
|
open={!!anchorEl}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
p: '0.5rem',
|
||||||
|
pt: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{enableHiding && (
|
||||||
|
<Button
|
||||||
|
disabled={!getIsSomeColumnsVisible()}
|
||||||
|
onClick={() => handleToggleAllColumns(false)}
|
||||||
|
>
|
||||||
|
{localization.hideAll}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{enableColumnOrdering && (
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
table.setColumnOrder(
|
||||||
|
getDefaultColumnOrderIds(table.options, true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{localization.resetOrder}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{enableColumnPinning && (
|
||||||
|
<Button
|
||||||
|
disabled={!getIsSomeColumnsPinned()}
|
||||||
|
onClick={() => table.resetColumnPinning(true)}
|
||||||
|
>
|
||||||
|
{localization.unpinAll}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{enableHiding && (
|
||||||
|
<Button
|
||||||
|
disabled={getIsAllColumnsVisible()}
|
||||||
|
onClick={() => handleToggleAllColumns(true)}
|
||||||
|
>
|
||||||
|
{localization.showAll}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Divider />
|
||||||
|
{allColumns.map((column, index) => (
|
||||||
|
<DataTable_ShowHideColumnsMenuItems
|
||||||
|
allColumns={allColumns}
|
||||||
|
column={column}
|
||||||
|
hoveredColumn={hoveredColumn}
|
||||||
|
isNestedColumns={isNestedColumns}
|
||||||
|
key={`${index}-${column.id}`}
|
||||||
|
setHoveredColumn={setHoveredColumn}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_ShowHideColumnsMenu
|
||||||
160
src/core/components/DataTable/menus/ShowHideColumnsMenuItems.js
Normal file
160
src/core/components/DataTable/menus/ShowHideColumnsMenuItems.js
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Box, FormControlLabel, MenuItem, Switch, Tooltip, Typography } from "@mui/material";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import DataTable_GrabHandleButton from "../buttons/GrabHandleButton";
|
||||||
|
|
||||||
|
const DataTable_ShowHideColumnsMenuItems = ({
|
||||||
|
allColumns,
|
||||||
|
column,
|
||||||
|
hoveredColumn,
|
||||||
|
isNestedColumns,
|
||||||
|
setHoveredColumn,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableColumnOrdering,
|
||||||
|
enableHiding,
|
||||||
|
localization,
|
||||||
|
mrtTheme: { draggingBorderColor },
|
||||||
|
},
|
||||||
|
setColumnOrder,
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const { columnOrder } = getState();
|
||||||
|
const { columnDef } = column;
|
||||||
|
const { columnDefType } = columnDef;
|
||||||
|
|
||||||
|
const switchChecked = column.getIsVisible();
|
||||||
|
|
||||||
|
const handleToggleColumnHidden = (column) => {
|
||||||
|
if (columnDefType === 'group') {
|
||||||
|
column?.columns?.forEach?.((childColumn) => {
|
||||||
|
childColumn.toggleVisibility(!switchChecked);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
column.toggleVisibility();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const menuItemRef = useRef(null);
|
||||||
|
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
|
const handleDragStart = (e) => {
|
||||||
|
setIsDragging(true);
|
||||||
|
try {
|
||||||
|
e.dataTransfer.setDragImage(menuItemRef.current, 0, 0);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnd = (_e) => {
|
||||||
|
setIsDragging(false);
|
||||||
|
setHoveredColumn(null);
|
||||||
|
if (hoveredColumn) {
|
||||||
|
setColumnOrder(reorderColumn(column, hoveredColumn, columnOrder));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnter = (_e) => {
|
||||||
|
if (!isDragging && columnDef.enableColumnOrdering !== false) {
|
||||||
|
setHoveredColumn(column);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!columnDef.header || columnDef.visibleInShowHideMenu === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
disableRipple
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
ref={menuItemRef}
|
||||||
|
{...rest}
|
||||||
|
sx={(theme) => ({
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
my: 0,
|
||||||
|
opacity: isDragging ? 0.5 : 1,
|
||||||
|
outline: isDragging
|
||||||
|
? `2px dashed ${theme.palette.grey[500]}`
|
||||||
|
: hoveredColumn?.id === column.id
|
||||||
|
? `2px dashed ${draggingBorderColor}`
|
||||||
|
: 'none',
|
||||||
|
outlineOffset: '-2px',
|
||||||
|
pl: `${(column.depth + 0.5) * 2}rem`,
|
||||||
|
py: '6px',
|
||||||
|
...(parseFromValuesOrFunc(rest?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'nowrap',
|
||||||
|
gap: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{columnDefType !== 'group' &&
|
||||||
|
enableColumnOrdering &&
|
||||||
|
!isNestedColumns &&
|
||||||
|
(columnDef.enableColumnOrdering !== false ? (
|
||||||
|
<DataTable_GrabHandleButton
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ width: '28px' }} />
|
||||||
|
))}
|
||||||
|
{enableHiding ? (
|
||||||
|
<FormControlLabel
|
||||||
|
checked={switchChecked}
|
||||||
|
componentsProps={{
|
||||||
|
typography: {
|
||||||
|
sx: {
|
||||||
|
mb: 0,
|
||||||
|
opacity: columnDefType !== 'display' ? 1 : 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
control={
|
||||||
|
<Tooltip
|
||||||
|
{...getCommonTooltipProps()}
|
||||||
|
title={localization.toggleVisibility}
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
disabled={!column.getCanHide()}
|
||||||
|
label={columnDef.header}
|
||||||
|
onChange={() => handleToggleColumnHidden(column)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Typography sx={{ alignSelf: 'center' }}>
|
||||||
|
{columnDef.header}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</MenuItem>
|
||||||
|
{column.columns?.map((c, i) => (
|
||||||
|
<DataTable_ShowHideColumnsMenuItems
|
||||||
|
allColumns={allColumns}
|
||||||
|
column={c}
|
||||||
|
hoveredColumn={hoveredColumn}
|
||||||
|
isNestedColumns={isNestedColumns}
|
||||||
|
key={`${i}-${c.id}`}
|
||||||
|
setHoveredColumn={setHoveredColumn}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_ShowHideColumnsMenuItems;
|
||||||
78
src/core/components/DataTable/table/Paper.js
Normal file
78
src/core/components/DataTable/table/Paper.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Paper } from "@mui/material";
|
||||||
|
import DataTable_BottomToolbar from "../toolbar/BottomToolbar";
|
||||||
|
import DataTable_TopToolbar from "../toolbar/TopToolbar";
|
||||||
|
import DataTable_TableContainer from "./TableContainer";
|
||||||
|
|
||||||
|
const DataTable_Paper = ({ table, ...rest }) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableBottomToolbar,
|
||||||
|
enableTopToolbar,
|
||||||
|
mrtTheme: { baseBackgroundColor },
|
||||||
|
muiTablePaperProps,
|
||||||
|
renderBottomToolbar,
|
||||||
|
renderTopToolbar,
|
||||||
|
},
|
||||||
|
refs: { tablePaperRef },
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const { isFullScreen } = getState();
|
||||||
|
|
||||||
|
const paperProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTablePaperProps, { table }),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
{...paperProps}
|
||||||
|
ref={(ref) => {
|
||||||
|
tablePaperRef.current = ref;
|
||||||
|
if (paperProps?.ref) {
|
||||||
|
//@ts-ignore
|
||||||
|
paperProps.ref.current = ref;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
...(isFullScreen
|
||||||
|
? {
|
||||||
|
bottom: 0,
|
||||||
|
height: '100dvh',
|
||||||
|
left: 0,
|
||||||
|
margin: 0,
|
||||||
|
maxHeight: '100dvh',
|
||||||
|
maxWidth: '100dvw',
|
||||||
|
padding: 0,
|
||||||
|
position: 'fixed',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100dvw',
|
||||||
|
zIndex: 999,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...paperProps?.style,
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
backgroundColor: baseBackgroundColor,
|
||||||
|
backgroundImage: 'unset',
|
||||||
|
overflow: 'hidden',
|
||||||
|
transition: 'all 100ms ease-in-out',
|
||||||
|
...(parseFromValuesOrFunc(paperProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{enableTopToolbar &&
|
||||||
|
(parseFromValuesOrFunc(renderTopToolbar, { table }) ?? (
|
||||||
|
<DataTable_TopToolbar table={table} />
|
||||||
|
))}
|
||||||
|
<DataTable_TableContainer table={table} />
|
||||||
|
{enableBottomToolbar &&
|
||||||
|
(parseFromValuesOrFunc(renderBottomToolbar, { table }) ?? (
|
||||||
|
<DataTable_BottomToolbar table={table} />
|
||||||
|
))}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable_Paper;
|
||||||
78
src/core/components/DataTable/table/Table.js
Normal file
78
src/core/components/DataTable/table/Table.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { parseCSSVarId, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Table } from "@mui/material";
|
||||||
|
import { useMRT_ColumnVirtualizer } from "material-react-table";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import DataTable_TableBody, { Memo_DataTable_TableBody } from "../body/TableBody";
|
||||||
|
import DataTable_TableHead from "../head/TableHead";
|
||||||
|
|
||||||
|
const DataTable_Table = ({
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getFlatHeaders,
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
columns,
|
||||||
|
enableStickyHeader,
|
||||||
|
enableTableFooter,
|
||||||
|
enableTableHead,
|
||||||
|
layoutMode,
|
||||||
|
memoMode,
|
||||||
|
muiTableProps,
|
||||||
|
renderCaption,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
const { columnSizing, columnSizingInfo, columnVisibility, isFullScreen } =
|
||||||
|
getState();
|
||||||
|
|
||||||
|
const tableProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableProps, { table }),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Caption = parseFromValuesOrFunc(renderCaption, { table });
|
||||||
|
|
||||||
|
const columnSizeVars = useMemo(() => {
|
||||||
|
const headers = getFlatHeaders();
|
||||||
|
const colSizes = {};
|
||||||
|
for (let i = 0; i < headers.length; i++) {
|
||||||
|
const header = headers[i];
|
||||||
|
const colSize = header.getSize();
|
||||||
|
colSizes[`--header-${parseCSSVarId(header.id)}-size`] = colSize;
|
||||||
|
colSizes[`--col-${parseCSSVarId(header.column.id)}-size`] = colSize;
|
||||||
|
}
|
||||||
|
return colSizes;
|
||||||
|
}, [columns, columnSizing, columnSizingInfo, columnVisibility]);
|
||||||
|
|
||||||
|
const columnVirtualizer = useMRT_ColumnVirtualizer(table);
|
||||||
|
|
||||||
|
const commonTableGroupProps = {
|
||||||
|
columnVirtualizer,
|
||||||
|
table,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
stickyHeader={enableStickyHeader || isFullScreen}
|
||||||
|
{...tableProps}
|
||||||
|
style={{ ...columnSizeVars, ...tableProps?.style }}
|
||||||
|
sx={(theme) => ({
|
||||||
|
borderCollapse: 'separate',
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
|
||||||
|
position: 'relative',
|
||||||
|
...(parseFromValuesOrFunc(tableProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{!!Caption && <caption>{Caption}</caption>}
|
||||||
|
{enableTableHead && <DataTable_TableHead {...commonTableGroupProps} />}
|
||||||
|
{memoMode === 'table-body' || columnSizingInfo.isResizingColumn ? (
|
||||||
|
<Memo_DataTable_TableBody {...commonTableGroupProps} />
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBody {...commonTableGroupProps} />
|
||||||
|
)}
|
||||||
|
{/* {enableTableFooter && <MRT_TableFooter {...commonTableGroupProps} />} */}
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_Table
|
||||||
94
src/core/components/DataTable/table/TableContainer.js
Normal file
94
src/core/components/DataTable/table/TableContainer.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { TableContainer } from "@mui/material";
|
||||||
|
import { useEffect, useLayoutEffect, useState } from "react";
|
||||||
|
import DataTable_CellActionMenu from "../menus/CellActionMenu";
|
||||||
|
import DataTable_Table from "./Table";
|
||||||
|
import DataTable_TableLoadingOverlay from "./TableLoadingOverlay";
|
||||||
|
|
||||||
|
const useIsomorphicLayoutEffect =
|
||||||
|
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||||
|
|
||||||
|
const DataTable_TableContainer = ({
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableCellActions,
|
||||||
|
enableStickyHeader,
|
||||||
|
muiTableContainerProps,
|
||||||
|
},
|
||||||
|
refs: { bottomToolbarRef, tableContainerRef, topToolbarRef },
|
||||||
|
} = table;
|
||||||
|
const {
|
||||||
|
actionCell,
|
||||||
|
isFullScreen,
|
||||||
|
isLoading,
|
||||||
|
showLoadingOverlay,
|
||||||
|
} = getState();
|
||||||
|
|
||||||
|
const loading =
|
||||||
|
showLoadingOverlay !== false && (isLoading || showLoadingOverlay);
|
||||||
|
|
||||||
|
const [totalToolbarHeight, setTotalToolbarHeight] = useState(0);
|
||||||
|
|
||||||
|
const tableContainerProps = {
|
||||||
|
...parseFromValuesOrFunc(muiTableContainerProps, {
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
useIsomorphicLayoutEffect(() => {
|
||||||
|
const topToolbarHeight =
|
||||||
|
typeof document !== 'undefined'
|
||||||
|
? topToolbarRef.current?.offsetHeight ?? 0
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const bottomToolbarHeight =
|
||||||
|
typeof document !== 'undefined'
|
||||||
|
? bottomToolbarRef?.current?.offsetHeight ?? 0
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
setTotalToolbarHeight(topToolbarHeight + bottomToolbarHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer
|
||||||
|
aria-busy={loading}
|
||||||
|
aria-describedby={loading ? 'mrt-progress' : undefined}
|
||||||
|
{...tableContainerProps}
|
||||||
|
ref={(node) => {
|
||||||
|
if (node) {
|
||||||
|
tableContainerRef.current = node;
|
||||||
|
if (tableContainerProps?.ref) {
|
||||||
|
//@ts-ignore
|
||||||
|
tableContainerProps.ref.current = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
maxHeight: isFullScreen
|
||||||
|
? `calc(100vh - ${totalToolbarHeight}px)`
|
||||||
|
: undefined,
|
||||||
|
...tableContainerProps?.style,
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
maxHeight: enableStickyHeader
|
||||||
|
? `clamp(350px, calc(100vh - ${totalToolbarHeight}px), 9999px)`
|
||||||
|
: undefined,
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflow: 'auto',
|
||||||
|
position: 'relative',
|
||||||
|
...(parseFromValuesOrFunc(tableContainerProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{loading ? <DataTable_TableLoadingOverlay table={table} /> : null}
|
||||||
|
<DataTable_Table table={table} />
|
||||||
|
{enableCellActions && actionCell && <DataTable_CellActionMenu table={table} />}
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_TableContainer
|
||||||
48
src/core/components/DataTable/table/TableLoadingOverlay.js
Normal file
48
src/core/components/DataTable/table/TableLoadingOverlay.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Box, CircularProgress } from "@mui/material";
|
||||||
|
|
||||||
|
const DataTable_TableLoadingOverlay = ({
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
options: {
|
||||||
|
localization,
|
||||||
|
mrtTheme: { baseBackgroundColor },
|
||||||
|
muiCircularProgressProps,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const circularProgressProps = {
|
||||||
|
...parseFromValuesOrFunc(muiCircularProgressProps, { table }),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: alpha(baseBackgroundColor, 0.5),
|
||||||
|
bottom: 0,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
left: 0,
|
||||||
|
maxHeight: '100vh',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100%',
|
||||||
|
zIndex: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{circularProgressProps?.Component ?? (
|
||||||
|
<CircularProgress
|
||||||
|
aria-label={localization.noRecordsToDisplay}
|
||||||
|
id="mrt-progress"
|
||||||
|
{...circularProgressProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
export default DataTable_TableLoadingOverlay
|
||||||
88
src/core/components/DataTable/toolbar/BottomToolbar.js
Normal file
88
src/core/components/DataTable/toolbar/BottomToolbar.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Box, alpha, useMediaQuery } from "@mui/material";
|
||||||
|
import DataTable_LinearProgressBar from "./LinearProgressBar";
|
||||||
|
import DataTable_TablePagination from "./TablePagination";
|
||||||
|
|
||||||
|
const DataTable_BottomToolbar = ({
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enablePagination,
|
||||||
|
muiBottomToolbarProps,
|
||||||
|
positionPagination,
|
||||||
|
renderBottomToolbarCustomActions,
|
||||||
|
},
|
||||||
|
refs: { bottomToolbarRef },
|
||||||
|
} = table;
|
||||||
|
const { isFullScreen } = getState();
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery('(max-width:720px)');
|
||||||
|
const toolbarProps = {
|
||||||
|
...parseFromValuesOrFunc(muiBottomToolbarProps, { table }),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const stackAlertBanner = isMobile || !!renderBottomToolbarCustomActions;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
{...toolbarProps}
|
||||||
|
ref={(node) => {
|
||||||
|
if (node) {
|
||||||
|
bottomToolbarRef.current = node;
|
||||||
|
if (toolbarProps?.ref) {
|
||||||
|
// @ts-ignore
|
||||||
|
toolbarProps.ref.current = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
...getCommonToolbarStyles({ table, theme }),
|
||||||
|
bottom: isFullScreen ? '0' : undefined,
|
||||||
|
boxShadow: `0 1px 2px -1px ${alpha(
|
||||||
|
theme.palette.grey[700],
|
||||||
|
0.5,
|
||||||
|
)} inset`,
|
||||||
|
left: 0,
|
||||||
|
position: isFullScreen ? 'fixed' : 'relative',
|
||||||
|
right: 0,
|
||||||
|
...(parseFromValuesOrFunc(toolbarProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DataTable_LinearProgressBar isTopToolbar={false} table={table} />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
p: '0.5rem',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderBottomToolbarCustomActions ? (
|
||||||
|
renderBottomToolbarCustomActions({ table })
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
position: stackAlertBanner ? 'relative' : 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{enablePagination &&
|
||||||
|
['both', 'bottom'].includes(positionPagination ?? '') && (
|
||||||
|
<DataTable_TablePagination position="bottom" table={table} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_BottomToolbar
|
||||||
44
src/core/components/DataTable/toolbar/LinearProgressBar.js
Normal file
44
src/core/components/DataTable/toolbar/LinearProgressBar.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Collapse, LinearProgress } from "@mui/material";
|
||||||
|
|
||||||
|
const DataTable_LinearProgressBar = ({
|
||||||
|
isTopToolbar,
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: { muiLinearProgressProps },
|
||||||
|
} = table;
|
||||||
|
const { isSaving, showProgressBars } = getState();
|
||||||
|
|
||||||
|
const linearProgressProps = {
|
||||||
|
...parseFromValuesOrFunc(muiLinearProgressProps, {
|
||||||
|
isTopToolbar,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapse
|
||||||
|
in={showProgressBars !== false && (showProgressBars || isSaving)}
|
||||||
|
mountOnEnter
|
||||||
|
sx={{
|
||||||
|
bottom: isTopToolbar ? 0 : undefined,
|
||||||
|
position: 'absolute',
|
||||||
|
top: !isTopToolbar ? 0 : undefined,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
unmountOnExit
|
||||||
|
>
|
||||||
|
<LinearProgress
|
||||||
|
aria-busy="true"
|
||||||
|
aria-label="Loading"
|
||||||
|
sx={{ position: 'relative' }}
|
||||||
|
{...linearProgressProps}
|
||||||
|
/>
|
||||||
|
</Collapse>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_LinearProgressBar
|
||||||
217
src/core/components/DataTable/toolbar/TablePagination.js
Normal file
217
src/core/components/DataTable/toolbar/TablePagination.js
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import { flipIconStyles, getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { useTheme } from "@emotion/react";
|
||||||
|
import { Box, IconButton, InputLabel, MenuItem, Pagination, PaginationItem, Select, Tooltip, Typography, useMediaQuery } from "@mui/material";
|
||||||
|
|
||||||
|
const defaultRowsPerPage = [5, 10, 15, 20, 25, 30, 50, 100];
|
||||||
|
|
||||||
|
const DataTable_TablePagination = ({
|
||||||
|
position = 'bottom',
|
||||||
|
table,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery('(max-width: 720px)');
|
||||||
|
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enableToolbarInternalActions,
|
||||||
|
icons: { ChevronLeftIcon, ChevronRightIcon, FirstPageIcon, LastPageIcon },
|
||||||
|
localization,
|
||||||
|
muiPaginationProps,
|
||||||
|
paginationDisplayMode,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
const {
|
||||||
|
pagination: { pageIndex = 0, pageSize = 10 },
|
||||||
|
showGlobalFilter,
|
||||||
|
} = getState();
|
||||||
|
|
||||||
|
const paginationProps = {
|
||||||
|
...parseFromValuesOrFunc(muiPaginationProps, {
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
|
const totalRowCount = table.getRowCount();
|
||||||
|
const numberOfPages = table.getPageCount();
|
||||||
|
const showFirstLastPageButtons = numberOfPages > 2;
|
||||||
|
const firstRowIndex = pageIndex * pageSize;
|
||||||
|
const lastRowIndex = Math.min(pageIndex * pageSize + pageSize, totalRowCount);
|
||||||
|
|
||||||
|
const {
|
||||||
|
SelectProps = {},
|
||||||
|
disabled = false,
|
||||||
|
rowsPerPageOptions = defaultRowsPerPage,
|
||||||
|
showFirstButton = showFirstLastPageButtons,
|
||||||
|
showLastButton = showFirstLastPageButtons,
|
||||||
|
showRowsPerPage = true,
|
||||||
|
...restPaginationProps
|
||||||
|
} = paginationProps ?? {};
|
||||||
|
|
||||||
|
const disableBack = pageIndex <= 0 || disabled;
|
||||||
|
const disableNext = lastRowIndex >= totalRowCount || disabled;
|
||||||
|
|
||||||
|
if (isMobile && SelectProps?.native !== false) {
|
||||||
|
SelectProps.native = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooltipProps = getCommonTooltipProps();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className="MuiTablePagination-root"
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px',
|
||||||
|
justifyContent: { md: 'space-between', sm: 'center' },
|
||||||
|
justifySelf: 'flex-end',
|
||||||
|
mt:
|
||||||
|
position === 'top' &&
|
||||||
|
enableToolbarInternalActions &&
|
||||||
|
!showGlobalFilter
|
||||||
|
? '3rem'
|
||||||
|
: undefined,
|
||||||
|
position: 'relative',
|
||||||
|
px: '8px',
|
||||||
|
py: '12px',
|
||||||
|
zIndex: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showRowsPerPage && (
|
||||||
|
<Box sx={{ alignItems: 'center', display: 'flex', gap: '8px' }}>
|
||||||
|
<InputLabel htmlFor="mrt-rows-per-page" sx={{ mb: 0 }}>
|
||||||
|
{localization.rowsPerPage}
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
MenuProps={{ disableScrollLock: true }}
|
||||||
|
disableUnderline
|
||||||
|
disabled={disabled}
|
||||||
|
inputProps={{
|
||||||
|
'aria-label': localization.rowsPerPage,
|
||||||
|
id: 'mrt-rows-per-page',
|
||||||
|
}}
|
||||||
|
label={localization.rowsPerPage}
|
||||||
|
onChange={(event) =>
|
||||||
|
table.setPageSize(+(event.target.value))
|
||||||
|
}
|
||||||
|
sx={{ mb: 0 }}
|
||||||
|
value={pageSize}
|
||||||
|
variant="standard"
|
||||||
|
{...SelectProps}
|
||||||
|
>
|
||||||
|
{rowsPerPageOptions.map((option) => {
|
||||||
|
const value = typeof option !== 'number' ? option.value : option;
|
||||||
|
const label =
|
||||||
|
typeof option !== 'number' ? option.label : `${option}`;
|
||||||
|
return (
|
||||||
|
SelectProps?.children ??
|
||||||
|
(SelectProps?.native ? (
|
||||||
|
<option key={value} value={value}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
) : (
|
||||||
|
<MenuItem key={value} sx={{ m: 0 }} value={value}>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
))
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{paginationDisplayMode === 'pages' ? (
|
||||||
|
<Pagination
|
||||||
|
count={numberOfPages}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(_e, newPageIndex) => table.setPageIndex(newPageIndex - 1)}
|
||||||
|
page={pageIndex + 1}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<PaginationItem
|
||||||
|
slots={{
|
||||||
|
first: FirstPageIcon,
|
||||||
|
last: LastPageIcon,
|
||||||
|
next: ChevronRightIcon,
|
||||||
|
previous: ChevronLeftIcon,
|
||||||
|
}}
|
||||||
|
{...item}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
showFirstButton={showFirstButton}
|
||||||
|
showLastButton={showLastButton}
|
||||||
|
{...restPaginationProps}
|
||||||
|
/>
|
||||||
|
) : paginationDisplayMode === 'default' ? (
|
||||||
|
<>
|
||||||
|
<Typography
|
||||||
|
align="center"
|
||||||
|
component="span"
|
||||||
|
sx={{ m: '0 4px', minWidth: '8ch' }}
|
||||||
|
variant="body2"
|
||||||
|
>{`${lastRowIndex === 0 ? 0 : (firstRowIndex + 1).toLocaleString()
|
||||||
|
}-${lastRowIndex.toLocaleString()} ${localization.of
|
||||||
|
} ${totalRowCount.toLocaleString()}`}</Typography>
|
||||||
|
<Box gap="xs">
|
||||||
|
{showFirstButton && (
|
||||||
|
<Tooltip {...tooltipProps} title={localization.goToFirstPage}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.goToFirstPage}
|
||||||
|
disabled={disableBack}
|
||||||
|
onClick={() => table.firstPage()}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<FirstPageIcon {...flipIconStyles(theme)} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Tooltip {...tooltipProps} title={localization.goToPreviousPage}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.goToPreviousPage}
|
||||||
|
disabled={disableBack}
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon {...flipIconStyles(theme)} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip {...tooltipProps} title={localization.goToNextPage}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.goToNextPage}
|
||||||
|
disabled={disableNext}
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ChevronRightIcon {...flipIconStyles(theme)} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{showLastButton && (
|
||||||
|
<Tooltip {...tooltipProps} title={localization.goToLastPage}>
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
aria-label={localization.goToLastPage}
|
||||||
|
disabled={disableNext}
|
||||||
|
onClick={() => table.lastPage()}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<LastPageIcon {...flipIconStyles(theme)} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_TablePagination
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import DataTable_ShowHideColumnsButton from "../buttons/ShowHideColumnsButton";
|
||||||
|
import DataTable_ToggleFiltersButton from "../buttons/ToggleFiltersButton";
|
||||||
|
|
||||||
|
const DataTable_ToolbarInternalButtons = ({ table, ...rest }) => {
|
||||||
|
const {
|
||||||
|
options: {
|
||||||
|
columnFilterDisplayMode,
|
||||||
|
enableColumnFilters,
|
||||||
|
enableColumnOrdering,
|
||||||
|
enableColumnPinning,
|
||||||
|
enableFilters,
|
||||||
|
enableHiding,
|
||||||
|
renderToolbarInternalActions,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
{...rest}
|
||||||
|
sx={(theme) => ({
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
zIndex: 3,
|
||||||
|
...(parseFromValuesOrFunc(rest?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{renderToolbarInternalActions?.({
|
||||||
|
table,
|
||||||
|
}) ?? (
|
||||||
|
<>
|
||||||
|
{enableFilters &&
|
||||||
|
enableColumnFilters &&
|
||||||
|
columnFilterDisplayMode !== 'popover' && (
|
||||||
|
<DataTable_ToggleFiltersButton table={table} />
|
||||||
|
)}
|
||||||
|
{(enableHiding || enableColumnOrdering || enableColumnPinning) && (
|
||||||
|
<DataTable_ShowHideColumnsButton table={table} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default DataTable_ToolbarInternalButtons
|
||||||
86
src/core/components/DataTable/toolbar/TopToolbar.js
Normal file
86
src/core/components/DataTable/toolbar/TopToolbar.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
|
import { Box, useMediaQuery } from "@mui/material";
|
||||||
|
import DataTable_LinearProgressBar from "./LinearProgressBar";
|
||||||
|
import DataTable_TablePagination from "./TablePagination";
|
||||||
|
import DataTable_ToolbarInternalButtons from "./ToolbarInternalButtons";
|
||||||
|
|
||||||
|
const DataTable_TopToolbar = ({ table }) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: {
|
||||||
|
enablePagination,
|
||||||
|
enableToolbarInternalActions,
|
||||||
|
muiTopToolbarProps,
|
||||||
|
positionPagination,
|
||||||
|
renderTopToolbarCustomActions,
|
||||||
|
},
|
||||||
|
refs: { topToolbarRef },
|
||||||
|
} = table;
|
||||||
|
const { isFullScreen, showGlobalFilter } = getState();
|
||||||
|
|
||||||
|
const isMobile = useMediaQuery('(max-width:720px)');
|
||||||
|
const isTablet = useMediaQuery('(max-width:1024px)');
|
||||||
|
|
||||||
|
const toolbarProps = parseFromValuesOrFunc(muiTopToolbarProps, { table });
|
||||||
|
|
||||||
|
const stackAlertBanner =
|
||||||
|
isMobile ||
|
||||||
|
!!renderTopToolbarCustomActions ||
|
||||||
|
(showGlobalFilter && isTablet);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
{...toolbarProps}
|
||||||
|
ref={(ref) => {
|
||||||
|
topToolbarRef.current = ref;
|
||||||
|
if (toolbarProps?.ref) {
|
||||||
|
// @ts-ignore
|
||||||
|
toolbarProps.ref.current = ref;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={(theme) => ({
|
||||||
|
...getCommonToolbarStyles({ table, theme }),
|
||||||
|
position: isFullScreen ? 'sticky' : 'relative',
|
||||||
|
top: isFullScreen ? '0' : undefined,
|
||||||
|
...(parseFromValuesOrFunc(toolbarProps?.sx, theme)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '0.5rem',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
p: '0.5rem',
|
||||||
|
position: stackAlertBanner ? 'relative' : 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderTopToolbarCustomActions?.({ table }) ?? <span />}
|
||||||
|
{enableToolbarInternalActions && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap-reverse',
|
||||||
|
gap: '0.5rem',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DataTable_ToolbarInternalButtons table={table} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{enablePagination &&
|
||||||
|
['both', 'top'].includes(positionPagination ?? '') && (
|
||||||
|
<DataTable_TablePagination position="top" table={table} />
|
||||||
|
)}
|
||||||
|
<DataTable_LinearProgressBar isTopToolbar table={table} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DataTable_TopToolbar
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import {createTheme} from "@mui/material";
|
import { createTheme } from "@mui/material";
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createTheme({
|
||||||
direction: 'rtl',
|
direction: 'rtl',
|
||||||
typography: {
|
typography: {
|
||||||
fontFamily: `IRANSansFaNum, sans-serif`,
|
fontFamily: `IRANSansFaNum, sans-serif`,
|
||||||
|
fontSize: 12
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
162
src/core/utils/utils.js
Normal file
162
src/core/utils/utils.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import { alpha, darken } from "@mui/material";
|
||||||
|
|
||||||
|
export const parseCSSVarId = (id) => id.replace(/[^a-zA-Z0-9]/g, '_');
|
||||||
|
|
||||||
|
export const parseFromValuesOrFunc = (
|
||||||
|
fn,
|
||||||
|
arg
|
||||||
|
) => (fn instanceof Function ? fn(arg) : fn);
|
||||||
|
|
||||||
|
export const getCommonToolbarStyles = ({
|
||||||
|
table,
|
||||||
|
}) => ({
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
backgroundColor: table.options.mrtTheme.baseBackgroundColor,
|
||||||
|
display: 'grid',
|
||||||
|
flexWrap: 'wrap-reverse',
|
||||||
|
minHeight: '3.5rem',
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
|
transition: 'all 150ms ease-in-out',
|
||||||
|
zIndex: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getCommonTooltipProps = (
|
||||||
|
placement,
|
||||||
|
) => ({
|
||||||
|
disableInteractive: true,
|
||||||
|
enterDelay: 1000,
|
||||||
|
enterNextDelay: 1000,
|
||||||
|
placement,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const flipIconStyles = (theme) =>
|
||||||
|
theme.direction === 'rtl'
|
||||||
|
? { style: { transform: 'scaleX(-1)' } }
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
export const getCommonPinnedCellStyles = ({
|
||||||
|
column,
|
||||||
|
table,
|
||||||
|
theme,
|
||||||
|
}) => {
|
||||||
|
const { baseBackgroundColor } = table.options.mrtTheme;
|
||||||
|
const isPinned = column?.getIsPinned();
|
||||||
|
|
||||||
|
return {
|
||||||
|
'&[data-pinned="true"]': {
|
||||||
|
'&:before': {
|
||||||
|
backgroundColor: alpha(
|
||||||
|
darken(
|
||||||
|
baseBackgroundColor,
|
||||||
|
theme.palette.mode === 'dark' ? 0.05 : 0.01,
|
||||||
|
),
|
||||||
|
0.97,
|
||||||
|
),
|
||||||
|
boxShadow: column
|
||||||
|
? isPinned === 'left' && column.getIsLastColumn(isPinned)
|
||||||
|
? `-4px 0 4px -4px ${alpha(theme.palette.grey[700], 0.5)} inset`
|
||||||
|
: isPinned === 'right' && column.getIsFirstColumn(isPinned)
|
||||||
|
? `4px 0 4px -4px ${alpha(theme.palette.grey[700], 0.5)} inset`
|
||||||
|
: undefined
|
||||||
|
: undefined,
|
||||||
|
...commonCellBeforeAfterStyles,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCommonMRTCellStyles = ({
|
||||||
|
column,
|
||||||
|
header,
|
||||||
|
table,
|
||||||
|
tableCellProps,
|
||||||
|
theme,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
options: { enableColumnVirtualization, layoutMode },
|
||||||
|
} = table;
|
||||||
|
const { draggingColumn } = getState();
|
||||||
|
const { columnDef } = column;
|
||||||
|
const { columnDefType } = columnDef;
|
||||||
|
|
||||||
|
const isColumnPinned =
|
||||||
|
columnDef.columnDefType !== 'group' && column.getIsPinned();
|
||||||
|
|
||||||
|
const widthStyles = {
|
||||||
|
minWidth: `max(calc(var(--${header ? 'header' : 'col'}-${parseCSSVarId(
|
||||||
|
header?.id ?? column.id,
|
||||||
|
)}-size) * 1px), ${columnDef.minSize ?? 30}px)`,
|
||||||
|
width: `calc(var(--${header ? 'header' : 'col'}-${parseCSSVarId(
|
||||||
|
header?.id ?? column.id,
|
||||||
|
)}-size) * 1px)`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (layoutMode === 'grid') {
|
||||||
|
widthStyles.flex = `${[0, false].includes(columnDef.grow)
|
||||||
|
? 0
|
||||||
|
: `var(--${header ? 'header' : 'col'}-${parseCSSVarId(
|
||||||
|
header?.id ?? column.id,
|
||||||
|
)}-size)`
|
||||||
|
} 0 auto`;
|
||||||
|
} else if (layoutMode === 'grid-no-grow') {
|
||||||
|
widthStyles.flex = `${+(columnDef.grow || 0)} 0 auto`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinnedStyles = isColumnPinned
|
||||||
|
? {
|
||||||
|
...getCommonPinnedCellStyles({ column, table, theme }),
|
||||||
|
left:
|
||||||
|
isColumnPinned === 'left'
|
||||||
|
? `${column.getStart('left')}px`
|
||||||
|
: undefined,
|
||||||
|
opacity: 0.97,
|
||||||
|
position: 'sticky',
|
||||||
|
right:
|
||||||
|
isColumnPinned === 'right'
|
||||||
|
? `${column.getAfter('right')}px`
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
backgroundColor: 'inherit',
|
||||||
|
backgroundImage: 'inherit',
|
||||||
|
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
|
||||||
|
justifyContent:
|
||||||
|
columnDefType === 'group'
|
||||||
|
? 'center'
|
||||||
|
: layoutMode?.startsWith('grid')
|
||||||
|
? tableCellProps.align
|
||||||
|
: undefined,
|
||||||
|
opacity:
|
||||||
|
table.getState().draggingColumn?.id === column.id ||
|
||||||
|
table.getState().hoveredColumn?.id === column.id
|
||||||
|
? 0.5
|
||||||
|
: 1,
|
||||||
|
position: 'relative',
|
||||||
|
transition: enableColumnVirtualization
|
||||||
|
? 'none'
|
||||||
|
: `padding 150ms ease-in-out`,
|
||||||
|
zIndex:
|
||||||
|
column.getIsResizing() || draggingColumn?.id === column.id
|
||||||
|
? 2
|
||||||
|
: columnDefType !== 'group' && isColumnPinned
|
||||||
|
? 1
|
||||||
|
: 0,
|
||||||
|
...pinnedStyles,
|
||||||
|
...widthStyles,
|
||||||
|
...(parseFromValuesOrFunc(tableCellProps?.sx, theme)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const commonCellBeforeAfterStyles = {
|
||||||
|
content: '""',
|
||||||
|
height: '100%',
|
||||||
|
left: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
width: '100%',
|
||||||
|
zIndex: -1,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user