forked from CITKParus/P8-Panels
WEB APP: P8PDataGrid - поддержка фиксированных заголовка и колонок
This commit is contained in:
parent
44069b0bc9
commit
4b5938e3b1
@ -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,
|
||||
|
@ -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) => (
|
||||
<TableCell
|
||||
colSpan={header.displayDataColumnsCount}
|
||||
sx={{ ...STYLES.TABLE_CELL_GROUP_HEADER, ...customRender.cellStyle }}
|
||||
key={`group-header-cell-${i}`}
|
||||
{...customRender.cellProps}
|
||||
sx={{
|
||||
...STYLES.TABLE_CELL_GROUP_HEADER,
|
||||
...customRender.cellStyle,
|
||||
...(columnDef.width ? { minWidth: columnDef.width, maxWidth: columnDef.width } : {}),
|
||||
...(i == 0 && fixedColumns ? STYLES.TABLE_CELL_GROUP_HEADER_STICKY : {})
|
||||
}}
|
||||
colSpan={expandable && rowExpandRender ? 2 : 1}
|
||||
>
|
||||
<Stack direction="row" sx={STYLES.TABLE_COLUMN_STACK}>
|
||||
{group.expandable ? (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setExpandedGroups(pv => ({ ...pv, ...{ [group.name]: !pv[group.name] } }));
|
||||
}}
|
||||
>
|
||||
<Icon>{expandedGroups[group.name] ? "indeterminate_check_box" : "add_box"}</Icon>
|
||||
</IconButton>
|
||||
) : null}
|
||||
{customRender.data ? customRender.data : group.caption}
|
||||
</Stack>
|
||||
{i == 0 ? (
|
||||
<Stack direction="row" sx={STYLES.TABLE_COLUMN_STACK}>
|
||||
{group.expandable ? (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setExpandedGroups(pv => ({ ...pv, ...{ [group.name]: !pv[group.name] } }));
|
||||
}}
|
||||
>
|
||||
<Icon>{expandedGroups[group.name] ? "indeterminate_check_box" : "add_box"}</Icon>
|
||||
</IconButton>
|
||||
) : null}
|
||||
{customRender.data ? customRender.data : group.caption}
|
||||
</Stack>
|
||||
) : null}
|
||||
</TableCell>
|
||||
);
|
||||
));
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{displayHintColumn ? (
|
||||
<P8PTableColumnHintDialog columnDef={displayHintColumnDef} okBtnCaption={okFilterBtnCaption} onOk={handleHintOk} />
|
||||
) : null}
|
||||
@ -684,13 +723,22 @@ const P8PTable = ({
|
||||
valueFormatter={valueFormatter}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<TableContainer component={containerComponent ? containerComponent : Paper} {...(containerComponentProps ? containerComponentProps : {})}>
|
||||
<Table sx={STYLES.TABLE} size={size || P8P_TABLE_SIZE.MEDIUM}>
|
||||
<TableHead>
|
||||
{header.displayLevels.map(level => (
|
||||
<Table stickyHeader={fixedHeader} sx={STYLES.TABLE} size={size || P8P_TABLE_SIZE.MEDIUM}>
|
||||
<TableHead sx={fixedHeader ? STYLES.TABLE_HEAD_STICKY : {}}>
|
||||
{header.displayLevels.map((level, i) => (
|
||||
<TableRow key={level}>
|
||||
{expandable && rowExpandRender ? <TableCell key="head-cell-expand-control" align="center"></TableCell> : null}
|
||||
{expandable && rowExpandRender && i == 0 ? (
|
||||
<TableCell
|
||||
key="head-cell-expand-control"
|
||||
align="center"
|
||||
sx={{
|
||||
...STYLES.TABLE_CELL_EXPAND_CONTROL,
|
||||
...(fixedColumns ? STYLES.TABLE_HEAD_CELL_STICKY(theme, 0) : {})
|
||||
}}
|
||||
rowSpan={header.displayLevelsColumns[level][0].rowSpan}
|
||||
></TableCell>
|
||||
) : null}
|
||||
{header.displayLevelsColumns[level].map((columnDef, j) => {
|
||||
let customRender = {};
|
||||
if (headCellRender) customRender = headCellRender({ columnDef }) || {};
|
||||
@ -698,7 +746,11 @@ const P8PTable = ({
|
||||
<TableCell
|
||||
key={`head-cell-${j}`}
|
||||
align={getAlignByDataType(columnDef)}
|
||||
sx={{ ...customRender.cellStyle }}
|
||||
sx={{
|
||||
...customRender.cellStyle,
|
||||
...(columnDef.width ? { minWidth: columnDef.width, maxWidth: columnDef.width } : {}),
|
||||
...(columnDef.fixed ? STYLES.TABLE_HEAD_CELL_STICKY(theme, columnDef.fixedLeft) : {})
|
||||
}}
|
||||
rowSpan={columnDef.rowSpan}
|
||||
colSpan={columnDef.colSpan}
|
||||
{...customRender.cellProps}
|
||||
@ -753,7 +805,14 @@ const P8PTable = ({
|
||||
<React.Fragment key={`data-${i}`}>
|
||||
<TableRow key={`data-row-${i}`} sx={STYLES.TABLE_ROW}>
|
||||
{expandable && rowExpandRender ? (
|
||||
<TableCell key={`data-cell-expand-control-${i}`} align="center">
|
||||
<TableCell
|
||||
key={`data-cell-expand-control-${i}`}
|
||||
align="center"
|
||||
sx={{
|
||||
...STYLES.TABLE_CELL_EXPAND_CONTROL,
|
||||
...(fixedColumns ? STYLES.TABLE_CELL_STICKY(theme, 0) : {})
|
||||
}}
|
||||
>
|
||||
<IconButton onClick={() => handleExpandClick(i)}>
|
||||
<Icon>{expanded[i] === true ? "keyboard_arrow_down" : "keyboard_arrow_right"}</Icon>
|
||||
</IconButton>
|
||||
@ -766,7 +825,13 @@ const P8PTable = ({
|
||||
<TableCell
|
||||
key={`data-cell-${j}`}
|
||||
align={getAlignByDataType(columnDef)}
|
||||
sx={{ ...customRender.cellStyle }}
|
||||
sx={{
|
||||
...customRender.cellStyle,
|
||||
...(columnDef.width
|
||||
? { minWidth: columnDef.width, maxWidth: columnDef.width }
|
||||
: {}),
|
||||
...(columnDef.fixed ? STYLES.TABLE_CELL_STICKY(theme, columnDef.fixedLeft) : {})
|
||||
}}
|
||||
{...customRender.cellProps}
|
||||
>
|
||||
{customRender.data
|
||||
@ -780,7 +845,15 @@ const P8PTable = ({
|
||||
</TableRow>
|
||||
{expandable && rowExpandRender && expanded[i] === true ? (
|
||||
<TableRow key={`data-row-expand-${i}`}>
|
||||
<TableCell sx={STYLES.TABLE_CELL_EXPAND_CONTAINER} colSpan={header.displayDataColumnsCount}>
|
||||
<TableCell
|
||||
sx={{
|
||||
...STYLES.TABLE_CELL_EXPAND_CONTAINER,
|
||||
...(fixedColumns ? STYLES.TABLE_CELL_STICKY(theme, 0) : {})
|
||||
}}
|
||||
colSpan={
|
||||
fixedColumns ? header.displayFixedColumnsCount + 1 : header.displayDataColumnsCount
|
||||
}
|
||||
>
|
||||
{rowExpandRender({ columnsDef, row })}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -800,19 +873,19 @@ const P8PTable = ({
|
||||
: null}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{rows.length == 0 ? (
|
||||
noDataFoundText && !reloading ? (
|
||||
<P8PAppInlineError text={noDataFoundText} />
|
||||
) : null
|
||||
) : morePages ? (
|
||||
<Container style={STYLES.MORE_BUTTON_CONTAINER}>
|
||||
<Button fullWidth onClick={handleMorePagesBtnClick}>
|
||||
{morePagesBtnCaption}
|
||||
</Button>
|
||||
</Container>
|
||||
) : null}
|
||||
</TableContainer>
|
||||
</>
|
||||
{rows.length == 0 ? (
|
||||
noDataFoundText && !reloading ? (
|
||||
<P8PAppInlineError text={noDataFoundText} />
|
||||
) : null
|
||||
) : morePages ? (
|
||||
<Container style={STYLES.MORE_BUTTON_CONTAINER}>
|
||||
<Button fullWidth onClick={handleMorePagesBtnClick}>
|
||||
{morePagesBtnCaption}
|
||||
</Button>
|
||||
</Container>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
@ -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
|
||||
};
|
||||
},
|
||||
//Обработчик по умолчанию
|
||||
|
Loading…
x
Reference in New Issue
Block a user