diff --git a/app/components/p8p_data_grid.js b/app/components/p8p_data_grid.js
index 7ccfd73..51c1416 100644
--- a/app/components/p8p_data_grid.js
+++ b/app/components/p8p_data_grid.js
@@ -35,6 +35,8 @@ const P8PDataGrid = ({
groups,
rows,
size,
+ fixedHeader = false,
+ fixedColumns = 0,
morePages = false,
reloading,
expandable,
@@ -111,6 +113,8 @@ const P8PDataGrid = ({
orders={orders}
filters={filters}
size={size || P8P_DATA_GRID_SIZE.MEDIUM}
+ fixedHeader={fixedHeader}
+ fixedColumns={fixedColumns}
morePages={morePages}
reloading={reloading}
expandable={expandable}
@@ -147,6 +151,8 @@ P8PDataGrid.propTypes = {
groups: PropTypes.array,
rows: PropTypes.array.isRequired,
size: PropTypes.string,
+ fixedHeader: PropTypes.bool,
+ fixedColumns: PropTypes.number,
morePages: PropTypes.bool,
reloading: PropTypes.bool.isRequired,
expandable: PropTypes.bool,
diff --git a/app/components/p8p_table.js b/app/components/p8p_table.js
index b81947d..9d77a0a 100644
--- a/app/components/p8p_table.js
+++ b/app/components/p8p_table.js
@@ -33,6 +33,7 @@ import {
Container,
Link
} from "@mui/material"; //Интерфейсные компоненты
+import { useTheme } from "@mui/material/styles"; //Взаимодействие со стилями MUI
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { P8P_TABLE_AT, HEADER_INITIAL_STATE, hasValue, p8pTableReducer } from "./p8p_table_reducer"; //Редьюсер состояния
@@ -85,9 +86,30 @@ const STYLES = {
TABLE: {
with: "100%"
},
+ TABLE_HEAD_STICKY: {
+ position: "sticky",
+ top: 0,
+ zIndex: 1000
+ },
+ TABLE_HEAD_CELL_STICKY: (theme, left) => ({
+ position: "sticky",
+ left,
+ backgroundColor: theme.palette.background.default,
+ zIndex: 1000
+ }),
TABLE_ROW: {
"&:last-child td, &:last-child th": { border: 0 }
},
+ TABLE_CELL_STICKY: (theme, left) => ({
+ position: "sticky",
+ left,
+ backgroundColor: theme.palette.background.default,
+ zIndex: 500
+ }),
+ TABLE_CELL_EXPAND_CONTROL: {
+ minWidth: "60px",
+ maxWidth: "60px"
+ },
TABLE_CELL_EXPAND_CONTAINER: {
paddingBottom: 0,
paddingTop: 0
@@ -95,6 +117,10 @@ const STYLES = {
TABLE_CELL_GROUP_HEADER: {
backgroundColor: "lightgray"
},
+ TABLE_CELL_GROUP_HEADER_STICKY: {
+ position: "sticky",
+ left: 0
+ },
TABLE_COLUMN_STACK: {
alignItems: "center"
},
@@ -460,6 +486,8 @@ const P8PTable = ({
orders,
filters,
size,
+ fixedHeader = false,
+ fixedColumns = 0,
morePages = false,
reloading,
expandable,
@@ -501,6 +529,9 @@ const P8PTable = ({
//Собственное состояние - колонка с отображаемой подсказкой
const [displayHintColumn, setDisplayHintColumn] = useState(null);
+ //Стили
+ const theme = useTheme();
+
//Описание фильтруемой колонки
const filterColumnDef = filterColumn ? columnsDef.find(columnDef => columnDef.name == filterColumn) || null : null;
@@ -516,12 +547,12 @@ const P8PTable = ({
: ["", ""];
//Формирование заголовка таблицы
- const setHeader = ({ columnsDef, expandable, objectsCopier }) =>
- dispatchHeaderAction({ type: P8P_TABLE_AT.SET_HEADER, payload: { columnsDef, expandable, objectsCopier } });
+ const setHeader = ({ columnsDef, expandable, fixedColumns, objectsCopier }) =>
+ dispatchHeaderAction({ type: P8P_TABLE_AT.SET_HEADER, payload: { columnsDef, expandable, fixedColumns, objectsCopier } });
//Сворачивание/разворачивание уровня заголовка таблицы
const toggleHeaderExpand = ({ columnName, objectsCopier }) =>
- dispatchHeaderAction({ type: P8P_TABLE_AT.TOGGLE_HEADER_EXPAND, payload: { columnName, expandable, objectsCopier } });
+ dispatchHeaderAction({ type: P8P_TABLE_AT.TOGGLE_HEADER_EXPAND, payload: { columnName, expandable, fixedColumns, objectsCopier } });
//Выравнивание в зависимости от типа данных
const getAlignByDataType = ({ dataType, hasChild }) =>
@@ -621,38 +652,46 @@ const P8PTable = ({
//При изменении описания колонок
useEffect(() => {
- setHeader({ columnsDef, expandable, objectsCopier });
- }, [columnsDef, expandable, objectsCopier]);
+ setHeader({ columnsDef, expandable, fixedColumns, objectsCopier });
+ }, [columnsDef, expandable, fixedColumns, objectsCopier]);
//Генерация заголовка группы
const renderGroupCell = group => {
let customRender = {};
if (groupCellRender) customRender = groupCellRender({ columnsDef: header.columnsDef, group }) || {};
- return (
+ return header.displayDataColumns.map((columnDef, i) => (
-
- {group.expandable ? (
- {
- setExpandedGroups(pv => ({ ...pv, ...{ [group.name]: !pv[group.name] } }));
- }}
- >
- {expandedGroups[group.name] ? "indeterminate_check_box" : "add_box"}
-
- ) : null}
- {customRender.data ? customRender.data : group.caption}
-
+ {i == 0 ? (
+
+ {group.expandable ? (
+ {
+ setExpandedGroups(pv => ({ ...pv, ...{ [group.name]: !pv[group.name] } }));
+ }}
+ >
+ {expandedGroups[group.name] ? "indeterminate_check_box" : "add_box"}
+
+ ) : null}
+ {customRender.data ? customRender.data : group.caption}
+
+ ) : null}
- );
+ ));
};
//Генерация содержимого
return (
- <>
+
{displayHintColumn ? (
) : null}
@@ -684,13 +723,22 @@ const P8PTable = ({
valueFormatter={valueFormatter}
/>
) : null}
-
-
-
- {header.displayLevels.map(level => (
+
+
+ {header.displayLevels.map((level, i) => (
- {expandable && rowExpandRender ? : null}
+ {expandable && rowExpandRender && i == 0 ? (
+
+ ) : null}
{header.displayLevelsColumns[level].map((columnDef, j) => {
let customRender = {};
if (headCellRender) customRender = headCellRender({ columnDef }) || {};
@@ -698,7 +746,11 @@ const P8PTable = ({
{expandable && rowExpandRender ? (
-
+
handleExpandClick(i)}>
{expanded[i] === true ? "keyboard_arrow_down" : "keyboard_arrow_right"}
@@ -766,7 +825,13 @@ const P8PTable = ({
{customRender.data
@@ -780,7 +845,15 @@ const P8PTable = ({
{expandable && rowExpandRender && expanded[i] === true ? (
-
+
{rowExpandRender({ columnsDef, row })}
@@ -800,19 +873,19 @@ const P8PTable = ({
: null}
- {rows.length == 0 ? (
- noDataFoundText && !reloading ? (
-
- ) : null
- ) : morePages ? (
-
-
-
- ) : null}
- >
+ {rows.length == 0 ? (
+ noDataFoundText && !reloading ? (
+
+ ) : null
+ ) : morePages ? (
+
+
+
+ ) : null}
+
);
};
@@ -829,7 +902,8 @@ P8PTable.propTypes = {
values: PropTypes.array,
parent: PropTypes.string,
expandable: PropTypes.bool.isRequired,
- expanded: PropTypes.bool.isRequired
+ expanded: PropTypes.bool.isRequired,
+ width: PropTypes.number
})
).isRequired,
groups: PropTypes.arrayOf(
@@ -849,6 +923,8 @@ P8PTable.propTypes = {
).isRequired,
filters: PropTypes.arrayOf(P8P_TABLE_FILTER_SHAPE).isRequired,
size: PropTypes.string,
+ fixedHeader: PropTypes.bool,
+ fixedColumns: PropTypes.number,
morePages: PropTypes.bool,
reloading: PropTypes.bool.isRequired,
expandable: PropTypes.bool,
diff --git a/app/components/p8p_table_reducer.js b/app/components/p8p_table_reducer.js
index 9dc4721..9d2a10a 100644
--- a/app/components/p8p_table_reducer.js
+++ b/app/components/p8p_table_reducer.js
@@ -19,7 +19,8 @@ const HEADER_INITIAL_STATE = () => ({
displayLevels: [],
displayLevelsColumns: {},
displayDataColumnsCount: 0,
- displayDataColumns: []
+ displayDataColumns: [],
+ displayFixedColumnsCount: 0
});
//Состояние описания ячейки заголовка таблицы по умолчанию
@@ -28,6 +29,8 @@ const HEADER_COLUMN_INITIAL_STATE = ({ columnDef, objectsCopier }) => {
if (!hasValue(tmp.parent)) tmp.parent = "";
if (!hasValue(tmp.expandable)) tmp.expandable = false;
if (!hasValue(tmp.expanded)) tmp.expanded = true;
+ if (!hasValue(tmp.fixed)) tmp.fixed = false;
+ if (!hasValue(tmp.fixedLeft)) tmp.fixedLeft = 0;
return tmp;
};
@@ -55,8 +58,23 @@ const getDisplayColumnColSpan = (displayTree, columnDef) => {
} else return 1;
};
+//Определения признака зафиксированности колонки
+const getFixedColumns = (displayTree, parentFixed, parentLeft, fixedColumns) => {
+ if (fixedColumns) {
+ let left = parentLeft;
+ displayTree.forEach((columnDef, i) => {
+ left += columnDef.width;
+ if ((columnDef.level == 1 && i + 1 <= fixedColumns) || (columnDef.level > 1 && parentFixed)) {
+ columnDef.fixed = true;
+ columnDef.fixedLeft = left - columnDef.width;
+ } else columnDef.fixed = false;
+ if (columnDef.hasChild) getFixedColumns(columnDef.child, columnDef.fixed, columnDef.fixedLeft, fixedColumns);
+ });
+ }
+};
+
//Формирование дерева отображаемых элементов заголовка
-const buildDisplayTree = (columnsDef, parent, level) => {
+const buildDisplayTree = (columnsDef, parent, level, expandable, fixedColumns) => {
const baseBuild = (columnsDef, parent, level) => {
let maxLevel = level - 1;
const res = columnsDef
@@ -77,6 +95,7 @@ const buildDisplayTree = (columnsDef, parent, level) => {
};
const [displayTree, maxLevel] = baseBuild(columnsDef, parent, level);
getDisplayColumnRowSpan(displayTree, maxLevel);
+ getFixedColumns(displayTree, false, expandable ? 60 : 0, fixedColumns);
return [displayTree, maxLevel];
};
@@ -106,28 +125,41 @@ const buildDisplayDataColumns = (displayTree, expandable) => {
return [displayDataColumns, displayDataColumns.length + (expandable === true ? 1 : 0)];
};
+//Подсчёт количества отображаемых фиксированных колонок
+const getDisplayFixedColumnsCount = displayTree => {
+ let res = 0;
+ const traverseTree = displayTree => {
+ displayTree.forEach(columnDef => (columnDef.hasChild ? traverseTree(columnDef.child) : columnDef.fixed ? res++ : null));
+ };
+ traverseTree(displayTree);
+ return res;
+};
+
//Формирование описания отображаемых колонок
-const buildDisplay = ({ columnsDef, expandable }) => {
+const buildDisplay = ({ columnsDef, expandable, fixedColumns }) => {
//Сформируем дерево отображаемых колонок заголовка
- const [displayTree, maxLevel] = buildDisplayTree(columnsDef, "", 1);
+ const [displayTree, maxLevel] = buildDisplayTree(columnsDef, "", 1, expandable, fixedColumns);
//Вытянем дерево в удобные для рендеринга структуры
const [displayLevels, displayLevelsColumns] = buildDisplayLevelsColumns(displayTree, maxLevel);
//Сформируем отображаемые колонки данных
const [displayDataColumns, displayDataColumnsCount] = buildDisplayDataColumns(displayTree, expandable);
+ //Подсчитаем количество отображаемых фиксированных колонок
+ const displayFixedColumnsCount = getDisplayFixedColumnsCount(displayTree);
//Вернём результат
- return [displayLevels, displayLevelsColumns, displayDataColumns, displayDataColumnsCount];
+ return [displayLevels, displayLevelsColumns, displayDataColumns, displayDataColumnsCount, displayFixedColumnsCount];
};
//Формирование описания заголовка
-const buildHeaderDef = ({ columnsDef, expandable, objectsCopier }) => {
+const buildHeaderDef = ({ columnsDef, expandable, fixedColumns, objectsCopier }) => {
//Инициализируем результат
const res = HEADER_INITIAL_STATE();
//Инициализируем внутренне описание колонок и поместим его в результат
columnsDef.forEach(columnDef => res.columnsDef.push(HEADER_COLUMN_INITIAL_STATE({ columnDef, objectsCopier })));
//Добавим в результат сведения об отображаемых данных
- [res.displayLevels, res.displayLevelsColumns, res.displayDataColumns, res.displayDataColumnsCount] = buildDisplay({
+ [res.displayLevels, res.displayLevelsColumns, res.displayDataColumns, res.displayDataColumnsCount, res.displayFixedColumnsCount] = buildDisplay({
columnsDef: res.columnsDef,
- expandable
+ expandable,
+ fixedColumns
});
//Сформируем дерево отображаемых колонок заголовка
//const [displayTree, maxLevel] = buildDisplayTree(res.columnsDef, "", 1);
@@ -147,19 +179,20 @@ const buildHeaderDef = ({ columnsDef, expandable, objectsCopier }) => {
const handlers = {
//Формирование заголовка
[P8P_TABLE_AT.SET_HEADER]: (state, { payload }) => {
- const { columnsDef, expandable, objectsCopier } = payload;
+ const { columnsDef, expandable, fixedColumns, objectsCopier } = payload;
return {
...state,
- ...buildHeaderDef({ columnsDef, expandable, objectsCopier })
+ ...buildHeaderDef({ columnsDef, expandable, fixedColumns, objectsCopier })
};
},
[P8P_TABLE_AT.TOGGLE_HEADER_EXPAND]: (state, { payload }) => {
- const { columnName, expandable, objectsCopier } = payload;
+ const { columnName, expandable, fixedColumns, objectsCopier } = payload;
const columnsDef = objectsCopier(state.columnsDef);
columnsDef.forEach(columnDef => (columnDef.name == columnName ? (columnDef.expanded = !columnDef.expanded) : null));
- const [displayLevels, displayLevelsColumns, displayDataColumns, displayDataColumnsCount] = buildDisplay({
+ const [displayLevels, displayLevelsColumns, displayDataColumns, displayDataColumnsCount, displayFixedColumnsCount] = buildDisplay({
columnsDef,
- expandable
+ expandable,
+ fixedColumns
});
//const [displayTree, maxLevel] = buildDisplayTree(columnsDef, "", 1);
//const [displayLevels, displayLevelsColumns] = buildDisplayLevelsColumns(displayTree, maxLevel);
@@ -170,7 +203,8 @@ const handlers = {
displayLevels,
displayLevelsColumns,
displayDataColumns,
- displayDataColumnsCount
+ displayDataColumnsCount,
+ displayFixedColumnsCount
};
},
//Обработчик по умолчанию