ЦИТК-1076 - Добавлен единый стиль P8P* компонентов

This commit is contained in:
Dollerino 2026-07-30 19:54:37 +03:00
parent 55ec11dc55
commit e243a85a4a
80 changed files with 4210 additions and 1783 deletions

View File

@ -19,7 +19,13 @@ import Button from "@mui/material/Button"; //Кнопки
import Container from "@mui/material/Container"; //Контейнер
import Box from "@mui/material/Box"; //Обёртка
import { BUTTONS, STATE } from "../../app.text"; //Типовые текстовые ресурсы и константы
import { APP_COLORS } from "../../app.styles"; //Типовые стили
import { useTheme } from "@mui/material"; //Тема
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
import { P8P_DIALOG_TITLE_VARIANT } from "../theme/variants/p8p_dialog_title_variants"; //Варианты заголовков диалога
import { P8P_BUTTON_VARIANT } from "../theme/variants/p8p_button_variants"; //Варианты кнопок
import { P8P_CONTAINER_VARIANT } from "../theme/variants/p8p_container_variants"; //Варианты контейнеров
import { P8P_DIALOG_CONTENT_VARIANT } from "../theme/variants/p8p_dialog_content_variants"; //Варианты содержимого диалогов
import { P8P_DIALOG_CONTENT_TEXT_VARIANT } from "../theme/variants/p8p_dialog_content_text_variants"; //Варианты текста содержимого диалога
//---------
//Константы
@ -32,44 +38,6 @@ const P8P_APP_MESSAGE_VARIANT = {
ERR: STATE.ERR
};
//Стили
const STYLES = {
DEFAULT: {
wordBreak: "break-word"
},
INFO: {
titleText: {
color: APP_COLORS[STATE.INFO].contrColor
},
bodyText: {
color: APP_COLORS[STATE.INFO].contrColor
}
},
WARN: {
titleText: {
color: APP_COLORS[STATE.WARN].contrColor
},
bodyText: {
color: APP_COLORS[STATE.WARN].contrColor
}
},
ERR: {
titleText: {
color: APP_COLORS[STATE.ERR].contrColor
},
bodyText: {
color: APP_COLORS[STATE.ERR].contrColor
}
},
INLINE_MESSAGE: {
with: "100%",
textAlign: "center"
},
FULL_ERROR_TEXT_BUTTON: {
color: APP_COLORS[STATE.WARN].contrColor
}
};
//-----------
//Тело модуля
//-----------
@ -94,37 +62,50 @@ const P8PAppMessage = ({
//Состояние подробной информации об ошибке
const [showFullErrorText, setShowFullErrorText] = useState(false);
//Подбор стиля и ресурсов
let style = STYLES.INFO;
//Определяем вариант стилизации
let titleVariant = P8P_DIALOG_TITLE_VARIANT.INFO;
let contentVariant = P8P_DIALOG_CONTENT_TEXT_VARIANT.INFO;
switch (variant) {
case P8P_APP_MESSAGE_VARIANT.INFO: {
style = STYLES.INFO;
titleVariant = P8P_DIALOG_TITLE_VARIANT.INFO;
contentVariant = P8P_DIALOG_CONTENT_TEXT_VARIANT.INFO;
break;
}
case P8P_APP_MESSAGE_VARIANT.WARN: {
style = STYLES.WARN;
titleVariant = P8P_DIALOG_TITLE_VARIANT.WARN;
contentVariant = P8P_DIALOG_CONTENT_TEXT_VARIANT.WARN;
break;
}
case P8P_APP_MESSAGE_VARIANT.ERR: {
style = STYLES.ERR;
titleVariant = P8P_DIALOG_TITLE_VARIANT.ERROR;
contentVariant = P8P_DIALOG_CONTENT_TEXT_VARIANT.ERROR;
break;
}
}
//Заголовок
let titlePart;
if (title && titleText) titlePart = <DialogTitle style={{ ...style.DEFAULT, ...style.titleText }}>{titleText}</DialogTitle>;
if (title && titleText)
titlePart = (
<DialogTitle variant={titleVariant}>
<Typography variant={P8P_TYPOGRAPHY_VARIANT.H7}>{titleText}</Typography>
</DialogTitle>
);
//Кнопка Отмена
let cancelBtnPart;
if (cancelBtn && cancelBtnCaption && variant === P8P_APP_MESSAGE_VARIANT.WARN)
cancelBtnPart = <Button onClick={() => (onCancel ? onCancel() : null)}>{cancelBtnCaption}</Button>;
cancelBtnPart = (
<Button variant={P8P_BUTTON_VARIANT.TEXT} onClick={() => (onCancel ? onCancel() : null)}>
{cancelBtnCaption}
</Button>
);
//Кнопка OK
let okBtnPart;
if (okBtn && okBtnCaption)
okBtnPart = (
<Button onClick={() => (onOk ? onOk() : null)} autoFocus>
<Button variant={P8P_BUTTON_VARIANT.TEXT} onClick={() => (onOk ? onOk() : null)} autoFocus>
{okBtnCaption}
</Button>
);
@ -133,7 +114,7 @@ const P8PAppMessage = ({
let fullErrorTextBtn;
if (fullErrorText && showErrMoreCaption && hideErrMoreCaption && variant === P8P_APP_MESSAGE_VARIANT.ERR)
fullErrorTextBtn = (
<Button onClick={() => setShowFullErrorText(!showFullErrorText)} sx={STYLES.FULL_ERROR_TEXT_BUTTON} autoFocus>
<Button variant={P8P_BUTTON_VARIANT.TEXT} color="P8PWarning" onClick={() => setShowFullErrorText(!showFullErrorText)} autoFocus>
{!showFullErrorText ? showErrMoreCaption : hideErrMoreCaption}
</Button>
);
@ -154,7 +135,7 @@ const P8PAppMessage = ({
<Dialog open={open || false} onClose={() => (onCancel ? onCancel() : null)}>
{titlePart}
<DialogContent>
<DialogContentText style={style.bodyText}>{!showFullErrorText ? text : fullErrorText}</DialogContentText>
<DialogContentText variant={contentVariant}>{!showFullErrorText ? text : fullErrorText}</DialogContentText>
</DialogContent>
{actionsPart}
</Dialog>
@ -181,24 +162,28 @@ P8PAppMessage.propTypes = {
//Встроенное сообщение
const P8PAppInlineMessage = ({ variant, text, okBtn, onOk, okBtnCaption }) => {
//Определяем тему
const theme = useTheme();
//Генерация содержимого
return (
<Container style={STYLES.INLINE_MESSAGE}>
<Container variant={P8P_CONTAINER_VARIANT.INLINE_MSG}>
<Box p={1}>
<Typography
variant={P8P_TYPOGRAPHY_VARIANT.BODY1}
color={
variant === P8P_APP_MESSAGE_VARIANT.ERR
? APP_COLORS[STATE.ERR].contrColor
? theme.palette.P8PError.main
: variant === P8P_APP_MESSAGE_VARIANT.WARN
? APP_COLORS[STATE.WARN].contrColor
: APP_COLORS[STATE.INFO].contrColor
? theme.palette.P8PWarning.main
: theme.palette.P8PText.primary
}
>
{text}
</Typography>
{okBtn && okBtnCaption ? (
<Box pt={1}>
<Button onClick={() => (onOk ? onOk() : null)} autoFocus>
<Button variant={P8P_BUTTON_VARIANT.TEXT} onClick={() => (onOk ? onOk() : null)} autoFocus>
{okBtnCaption}
</Button>
</Box>
@ -254,12 +239,14 @@ const P8PAppInlineInfo = props => buildVariantInlineMessage(props, P8P_APP_MESSA
const P8PHintDialog = ({ title, hint, onOk }) => {
return (
<Dialog open={true} onClose={e => (onOk ? onOk(e) : null)}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogTitle variant={P8P_DIALOG_TITLE_VARIANT.PRIMARY}>{title}</DialogTitle>
<DialogContent variant={P8P_DIALOG_CONTENT_VARIANT.HINT}>
<div dangerouslySetInnerHTML={{ __html: hint }}></div>
</DialogContent>
<DialogActions>
<Button onClick={e => (onOk ? onOk(e) : null)}>{BUTTONS.OK}</Button>
<Button variant={P8P_BUTTON_VARIANT.SECONDARY} onClick={e => (onOk ? onOk(e) : null)}>
{BUTTONS.OK}
</Button>
</DialogActions>
</Dialog>
);

View File

@ -14,6 +14,8 @@ import DialogTitle from "@mui/material/DialogTitle"; //Заголовок диа
import DialogContent from "@mui/material/DialogContent"; //Содержимое диалога
import DialogContentText from "@mui/material/DialogContentText"; //Текст содержимого диалога
import LinearProgress from "@mui/material/LinearProgress"; //Индикатор
import { P8P_DIALOG_TITLE_VARIANT } from "../theme/variants/p8p_dialog_title_variants"; //Варианты заголовков диалога
import { P8P_DIALOG_CONTENT_TEXT_VARIANT } from "../theme/variants/p8p_dialog_content_text_variants"; //Варианты текста содержимого диалога
//-----------
//Тело модуля
@ -28,9 +30,15 @@ const P8PAppProgress = props => {
return (
<div>
<Dialog open={open || false} aria-labelledby="progress-dialog-title" aria-describedby="progress-dialog-description">
{title ? <DialogTitle id="progress-dialog-title">{title}</DialogTitle> : null}
{title ? (
<DialogTitle id="progress-dialog-title" variant={P8P_DIALOG_TITLE_VARIANT.PRIMARY}>
{title}
</DialogTitle>
) : null}
<DialogContent>
<DialogContentText id="progress-dialog-description">{text}</DialogContentText>
<DialogContentText id="progress-dialog-description" variant={P8P_DIALOG_CONTENT_TEXT_VARIANT.PRIMARY}>
{text}
</DialogContentText>
<LinearProgress />
</DialogContent>
</Dialog>

View File

@ -25,7 +25,13 @@ import {
Divider
} from "@mui/material"; //Интерфейсные компоненты
import { P8PPanelsMenuDrawer, P8P_PANELS_MENU_PANEL_SHAPE } from "./p8p_panels_menu"; //Меню
import { APP_STYLES } from "../../app.styles"; //Типовые стили
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
import { P8P_ICON_BUTTON_VARIANT } from "../theme/variants/p8p_icon_button_variants"; //Варианты кнопок-иконок
import { P8P_APP_BAR_VARIANT } from "../theme/variants/p8p_app_bar_variants"; //Варианты областей заголовка
import { P8P_DRAWER_VARIANT } from "../theme/variants/p8p_drawer_variants"; //Варианты выезжающих областей
import { P8P_BOX_CENTER_START, P8P_BOX_CENTER_END, P8P_BOX_FLEX, P8P_BOX_APP_WORKSPACE } from "../theme/styles/box"; //Стили контейнеров
import { P8P_MAIN_APP_WORKSPACE } from "../theme/styles/main"; //Стили содержимого страницы
import { P8P_LIST_ITEM_TEXT_VARIANT } from "../theme/variants/p8p_list_item_text_variants"; //Варианты значений списков
//---------
//Константы
@ -34,18 +40,6 @@ import { APP_STYLES } from "../../app.styles"; //Типовые стили
//Высота главного меню
const APP_BAR_HEIGHT = "64px";
//Стили
const STYLES = {
DRAWER: { [`& .MuiDrawer-paper`]: { ...APP_STYLES.SCROLL } },
ROOT_BOX: { display: "flex" },
APP_BAR: { position: "fixed" },
APP_BAR_MAIN_BOX: { display: "flex", width: "100vw", alignItems: "center", justifyContent: "space-between" },
APP_BAR_LEFT_SIDE: { display: "flex", alignItems: "center", justifyContent: "flex-start" },
APP_BAR_RIGHT_SIDE: { display: "flex", alignItems: "center", justifyContent: "flex-end" },
APP_BAR_BUTTON: { mr: 2 },
MAIN: { flexGrow: 1 }
};
//-----------
//Тело модуля
//-----------
@ -92,35 +86,35 @@ const P8PAppWorkspace = ({
//Генерация содержимого
return (
<Box sx={STYLES.ROOT_BOX}>
<Box sx={P8P_BOX_FLEX}>
{showAppBar && (
<>
<CssBaseline />
<AppBar sx={STYLES.APP_BAR}>
<AppBar variant={P8P_APP_BAR_VARIANT.FIXED}>
<Toolbar>
<Box sx={STYLES.APP_BAR_MAIN_BOX}>
<Box sx={STYLES.APP_BAR_LEFT_SIDE}>
<Box sx={P8P_BOX_APP_WORKSPACE}>
<Box sx={P8P_BOX_CENTER_START}>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={open ? handleDrawerClose : handleDrawerOpen}
edge="start"
sx={STYLES.APP_BAR_BUTTON}
variant={P8P_ICON_BUTTON_VARIANT.APP_BAR_BUTTON}
>
<Icon>{open ? "chevron_left" : "menu"}</Icon>
</IconButton>
<Typography variant="h6" noWrap component="div">
<Typography variant={P8P_TYPOGRAPHY_VARIANT.H6} noWrap component="div">
{caption || selectedPanel?.caption}
</Typography>
</Box>
<Box sx={STYLES.APP_BAR_RIGHT_SIDE}>
<Box sx={P8P_BOX_CENTER_END}>
{showAppBarSettings && selectedPanel.showUserSettings ? (
<IconButton
color="inherit"
aria-label="open drawer"
onClick={() => handleSettingsDialog(selectedPanel.name)}
edge="end"
sx={STYLES.APP_BAR_BUTTON}
variant={P8P_ICON_BUTTON_VARIANT.APP_BAR_BUTTON}
>
<Icon>settings</Icon>
</IconButton>
@ -129,33 +123,33 @@ const P8PAppWorkspace = ({
</Box>
</Toolbar>
</AppBar>
<Drawer anchor="left" open={open} onClose={handleDrawerClose} sx={STYLES.DRAWER}>
<Drawer anchor="left" open={open} onClose={handleDrawerClose} data-variant={P8P_DRAWER_VARIANT.PRIMARY}>
<List>
<ListItemButton onClick={handleDrawerClose}>
<ListItemIcon>
<Icon>close</Icon>
</ListItemIcon>
<ListItemText primary={closeCaption} />
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={closeCaption} />
</ListItemButton>
<ListItemButton onClick={handleHomeClick}>
<ListItemIcon>
<Icon>home</Icon>
</ListItemIcon>
<ListItemText primary={homeCaption} />
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={homeCaption} />
</ListItemButton>
<Divider component="li" />
<ListItemButton onClick={() => handleSettingsDialog()}>
<ListItemIcon>
<Icon>settings</Icon>
</ListItemIcon>
<ListItemText primary={settingsCaption} />
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={settingsCaption} />
</ListItemButton>
</List>
<P8PPanelsMenuDrawer panels={panels} selectedPanel={selectedPanel} onItemNavigate={handleItemNavigate} />
</Drawer>
</>
)}
<main style={STYLES.MAIN}>
<main style={P8P_MAIN_APP_WORKSPACE}>
{showAppBar && <Toolbar />}
{children}
</main>

View File

@ -9,604 +9,32 @@
import React, { useEffect, useState, useRef } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Box,
Typography,
Dialog,
DialogActions,
DialogContent,
Button,
List,
ListItem,
ListItemText,
Link,
Divider,
IconButton,
Icon
} from "@mui/material"; //Интерфейсные компоненты
import { Box, Typography, Link, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { hasValue } from "../core/utils"; //Вспомогательный функции
import { useP8PCyclogram } from "./p8p_cyclogram_hooks"; //Хук для циклограммы
import { P8PCyclogramTaskEditor } from "./p8p_cyclogram/p8p_cyclogram_task_editor"; //Редактор задачи
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
import { P8P_COMPONENT_HEIGHT } from "../theme/styles/common"; //Стили - общие
import { P8P_BOX_CYCLOGRAM } from "../theme/styles/box"; //Стили контейнеров
import { getShift } from "./p8p_cyclogram/p8p_cyclogram_utils"; //Вспомогательные функции циклограммы
import {
P8P_CYCLOGRAM_ZOOM,
NDEFAULT_LINE_HEIGHT,
NDEFAULT_HEADER_HEIGHT,
TITLE_HEIGHT,
ZOOM_HEIGHT,
P8P_CYCLOGRAM_COLUMN_SHAPE,
P8P_CYCLOGRAM_GROUP_SHAPE,
P8P_CYCLOGRAM_TASK_SHAPE,
P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE
} from "./p8p_cyclogram/p8p_cyclogram_constants"; //Константы циклограммы
import { P8PCyclogramGrid } from "./p8p_cyclogram/p8p_cyclogram_grid"; //Фон таблицы циклограммы
import { P8PCyclogramView } from "./p8p_cyclogram/p8p_cyclogram_view"; //Представление циклограммы
import { P8P_TYPOGRAPHY_TITLE } from "../theme/styles/typography"; //Стили текста
//---------
//Константы
//---------
//Уровни масштаба
const P8P_CYCLOGRAM_ZOOM = [0.2, 0.4, 0.7, 1, 1.5, 2, 2.5];
//Параметры элементов циклограммы
const NDEFAULT_LINE_HEIGHT = 20;
const NDEFAULT_HEADER_HEIGHT = 35;
//Высота заголовка
const TITLE_HEIGHT = "44px";
//Высота панели масштабирования
const ZOOM_HEIGHT = "56px";
//Стили
const STYLES = {
CYCLOGRAM_TITLE: { height: TITLE_HEIGHT },
CYCLOGRAM_ZOOM: { height: ZOOM_HEIGHT },
HEADER_COLUMN: {
fontSize: "12px",
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "pre",
textAlign: "center",
lineHeight: "3",
padding: "0px 5px"
},
CYCLOGRAM_BOX: (noData, title, zoomBar) => ({
position: "relative",
overflow: "auto",
padding: "0px 8px",
height: `calc(100% - ${zoomBar ? ZOOM_HEIGHT : "0px"} - ${title ? TITLE_HEIGHT : "0px"})`,
display: noData ? "none" : ""
}),
GRID_ROW: index => (index % 2 === 0 ? { backgroundColor: "#ffffff" } : { backgroundColor: "#f5f5f5" }),
GROUP_HEADER_BOX: {
border: "1px solid",
backgroundColor: "#ebebeb",
display: "flex",
alignItems: "center",
justifyContent: "center"
},
GROUP_HEADER: {
fontSize: "14px",
textAlign: "center",
wordWrap: "break-word"
},
TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" },
TASK_BOX: (lineHeight, bgColor, textColor, highlightColor) => ({
display: "flex",
alignItems: "center",
backgroundColor: bgColor ? bgColor : "#b4b9bf",
...(textColor ? { color: textColor } : {}),
height: lineHeight,
"&:hover": {
...(highlightColor
? { backgroundColor: `${highlightColor} !important`, filter: "brightness(1) !important" }
: { filter: "brightness(1.25) !important" }),
cursor: "pointer !important"
}
}),
TASK: lineHeight => {
const availableLines = Math.floor(lineHeight / 18);
return {
width: "100%",
fontSize: "12px",
overflowWrap: "break-word",
wordBreak: "break-all",
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
lineHeight: "18px",
maxHeight: lineHeight,
WebkitLineClamp: availableLines < 1 ? 1 : availableLines,
WebkitBoxOrient: "vertical"
};
}
};
//Структура колонки
const P8P_CYCLOGRAM_COLUMN_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
start: PropTypes.number.isRequired,
end: PropTypes.number.isRequired
});
//Структура группы
const P8P_CYCLOGRAM_GROUP_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
visible: PropTypes.bool.isRequired
});
//Структура задачи
const P8P_CYCLOGRAM_TASK_SHAPE = PropTypes.shape({
id: PropTypes.string.isRequired,
rn: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
fullName: PropTypes.string.isRequired,
lineNumb: PropTypes.number.isRequired,
start: PropTypes.number.isRequired,
end: PropTypes.number.isRequired,
group: PropTypes.string,
bgColor: PropTypes.string,
textColor: PropTypes.string,
highlightColor: PropTypes.string
});
//Структура динамического атрибута задачи
const P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
caption: PropTypes.string.isRequired,
visible: PropTypes.bool.isRequired
});
//--------------------------------
//Вспомогательные классы и функции
//--------------------------------
//Определение сдвига для максимальной ширины колонок
const getShift = (columns, currentColumnsMaxWidth, maxCyclogramWidth) => {
//Определяем доступное пространство для расширения
let maxWidthDiff = maxCyclogramWidth - currentColumnsMaxWidth;
//Инициализируем значение сдвига
let shift = 1;
//Если доступно больше ширины и есть пространство для расширения
if (maxCyclogramWidth > currentColumnsMaxWidth && maxCyclogramWidth - maxWidthDiff > columns.length) {
//Определяем доступный сдвиг колонок
shift = maxCyclogramWidth / currentColumnsMaxWidth;
}
//Возвращаем сдвиг
return shift;
};
//Формирование стилей для группы
const getGroupStyles = (indexGrp, highlightColor) => {
return `.main .TaskGrp${indexGrp}:hover .TaskGrp${indexGrp} {
${highlightColor ? `background: ${highlightColor};` : `filter: brightness(1.15);`}
}
.main:has(.TaskGrp${indexGrp}:hover) .TaskGrpHeader${indexGrp} {
display: block;
}
`;
//cursor: pointer;
};
//Фон строк таблицы
const P8PCyclogramRowsGrid = ({ rows, maxWidth, lineHeight }) => {
return (
<g>
{rows.map((el, index) => (
<foreignObject x="0" y={NDEFAULT_HEADER_HEIGHT + index * lineHeight} width={maxWidth} height={lineHeight} key={index}>
<Box sx={STYLES.GRID_ROW(index)} height={lineHeight} />
</foreignObject>
))}
</g>
);
};
//Контроль свойств - Фон строк таблицы
P8PCyclogramRowsGrid.propTypes = {
rows: PropTypes.array.isRequired,
maxWidth: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired
};
//Линии строк таблицы
const P8PCyclogramRowsLines = ({ rows, maxWidth, lineHeight }) => {
return (
<g>
{rows.map((el, index) => (
<line
x1="0"
y1={NDEFAULT_HEADER_HEIGHT + lineHeight + index * lineHeight}
x2={maxWidth}
y2={NDEFAULT_HEADER_HEIGHT + lineHeight + index * lineHeight}
key={index}
></line>
))}
</g>
);
};
//Контроль свойств - Линии строк таблицы
P8PCyclogramRowsLines.propTypes = {
rows: PropTypes.array.isRequired,
maxWidth: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired
};
//Линии колонок таблицы
const P8PCyclogramColumnsLines = ({ columns, shift, y1, y2 }) => {
//Инициализируем старт текущей колонки
let prevColumnEnd = 0;
return (
<g>
{columns.map((column, index) => {
//Аккумулируем окончание последней колонки с учетом сдвига
prevColumnEnd = index !== 0 ? prevColumnEnd + (columns[index - 1].end - columns[index - 1].start) * shift : 0;
return <line x1={prevColumnEnd} y1={y1} x2={prevColumnEnd} y2={y2} stroke="#e0e0e0" key={index} />;
})}
<line
x1={prevColumnEnd + (columns[columns.length - 1].end - columns[columns.length - 1].start) * shift}
y1={y1}
x2={prevColumnEnd + (columns[columns.length - 1].end - columns[columns.length - 1].start) * shift}
y2={y2}
stroke="#e0e0e0"
/>
</g>
);
};
//Контроль свойств - Линии колонок таблицы
P8PCyclogramColumnsLines.propTypes = {
columns: PropTypes.array.isRequired,
shift: PropTypes.number.isRequired,
y1: PropTypes.number.isRequired,
y2: PropTypes.number.isRequired
};
//Фон таблицы циклограммы
const P8PCyclogramGrid = ({ tasks, columns, shift, maxWidth, maxHeight, lineHeight }) => {
//Формируем массив строк исходя из максимального значения строки задачи
const rows = Array.from(Array(Math.max(...tasks.map(o => o.lineNumb)) + 1).keys());
return (
<g className="grid">
<rect x="0" y="0" width={maxWidth} height={maxHeight}></rect>
<P8PCyclogramRowsGrid rows={rows} maxWidth={maxWidth} lineHeight={lineHeight} />
<P8PCyclogramRowsLines rows={rows} maxWidth={maxWidth} lineHeight={lineHeight} />
<P8PCyclogramColumnsLines columns={columns} shift={shift} y1={NDEFAULT_HEADER_HEIGHT} y2={maxHeight} />
</g>
);
};
//Контроль свойств - Фон таблицы циклограммы
P8PCyclogramGrid.propTypes = {
tasks: PropTypes.array.isRequired,
columns: PropTypes.array.isRequired,
shift: PropTypes.number.isRequired,
maxWidth: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired
};
//Колонка заголовка циклограммы
const P8PCyclogramHeaderColumn = ({ column, start, shift, columnRenderer }) => {
//Рассчитываем ширину колонки
const columnWidth = column.end - column.start;
//Формируем собственное отображение, если требуется
const customView = columnRenderer ? columnRenderer({ column }) : null;
return (
<>
<foreignObject x={start} y="0" width={columnWidth * shift} height={NDEFAULT_HEADER_HEIGHT}>
{customView ? (
customView
) : (
<Typography sx={{ ...STYLES.HEADER_COLUMN, height: NDEFAULT_HEADER_HEIGHT }} title={column.name}>
{column.name}
</Typography>
)}
</foreignObject>
</>
);
};
//Контроль свойств - Колонка заголовка циклограммы
P8PCyclogramHeaderColumn.propTypes = {
column: PropTypes.object.isRequired,
start: PropTypes.number.isRequired,
shift: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
lastElement: PropTypes.bool,
columnRenderer: PropTypes.func
};
//Заголовок циклограммы
const P8PCyclogramHeader = ({ columns, shift, maxWidth, maxHeight, columnRenderer, headerBlock }) => {
//Инициализируем старт текущей колонки
let prevColumnEnd = 0;
return (
<g className="header" ref={headerBlock}>
<rect x="0" y="0" width={maxWidth} height={NDEFAULT_HEADER_HEIGHT} fill="#ffffff" stroke="#e0e0e0" strokeWidth="1.4"></rect>
{columns.map((column, index) => {
//Аккумулируем окончание последней колонки с учетом сдвига
prevColumnEnd = index !== 0 ? prevColumnEnd + (columns[index - 1].end - columns[index - 1].start) * shift : 0;
return (
<P8PCyclogramHeaderColumn
column={column}
shift={shift}
start={prevColumnEnd}
maxHeight={maxHeight}
lastElement={columns.length - 1 === index}
columnRenderer={columnRenderer}
key={index}
/>
);
})}
<g className="columnsDividers">
<P8PCyclogramColumnsLines columns={columns} shift={shift} y1={0} y2={NDEFAULT_HEADER_HEIGHT} />
</g>
</g>
);
};
//Контроль свойств - Заголовок циклограммы
P8PCyclogramHeader.propTypes = {
columns: PropTypes.array.isRequired,
shift: PropTypes.number.isRequired,
maxWidth: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
columnRenderer: PropTypes.func,
headerBlock: PropTypes.object
};
//Задача циклограммы
const P8PCyclogramTask = ({ task, indexGrp, shift, lineHeight, openTaskEditor, taskRenderer }) => {
//Рассчитываем ширину задачи
const width = task.end !== 0 ? (task.end - task.start) * shift : 0;
//Формируем собственное отображение, если требуется
const customView = taskRenderer ? taskRenderer({ task, taskHeight: lineHeight, taskWidth: width }) || {} : {};
return (
<foreignObject
x={task.start !== 0 ? task.start * shift : 0}
y={NDEFAULT_HEADER_HEIGHT + task.lineNumb * lineHeight}
width={width}
height={lineHeight}
>
<Box
className={hasValue(indexGrp) ? `TaskGrp${indexGrp}` : null}
sx={{ ...STYLES.TASK_BOX(lineHeight, task.bgColor, task.textColor, task.highlightColor), ...customView.taskStyle }}
{...customView.taskProps}
onClick={() => openTaskEditor(task)}
>
{customView.data ? (
customView.data
) : (
<Typography sx={STYLES.TASK(lineHeight)} title={task.name}>
{task.name}
</Typography>
)}
</Box>
</foreignObject>
);
};
//Контроль свойств - Группы циклограммы
P8PCyclogramTask.propTypes = {
task: PropTypes.object.isRequired,
indexGrp: PropTypes.number,
shift: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired,
openTaskEditor: PropTypes.func.isRequired,
taskRenderer: PropTypes.func
};
//Основная информация циклограммы
const P8PCyclogramMain = ({
columns,
groups,
tasks,
shift,
lineHeight,
maxWidth,
maxHeight,
openTaskEditor,
groupHeaderRenderer,
taskRenderer,
columnRenderer,
headerBlock
}) => {
//Инициализируем коллекцию тасков с группами
const tasksWithGroup = tasks.filter(task => hasValue(task.groupName));
//Инициализируем коллекцию тасков без групп
const tasksWithoutGroup = tasks.filter(task => !hasValue(task.groupName));
//Инициализируем коллекцию отображаемых групп
const visibleGroups = groups ? groups.filter(group => group.visible) : [];
return (
<g className="main">
<g className="tasks">
{visibleGroups.length !== 0
? visibleGroups.map((grp, indexGrp) => {
//Считываем задачи группы
let groupTasks = tasksWithGroup.filter(task => task.groupName === grp.name);
//Если по данной группе нет тасков - ничего не выводим
if (groupTasks.length === 0) {
return null;
}
return (
<g className={`TaskGrp${indexGrp}`} key={indexGrp}>
{groupTasks.map((task, index) => (
<P8PCyclogramTask
task={task}
indexGrp={indexGrp}
shift={shift}
lineHeight={lineHeight}
openTaskEditor={openTaskEditor}
taskRenderer={taskRenderer}
key={index}
/>
))}
<style>{getGroupStyles(indexGrp, grp.highlightColor)}</style>
</g>
);
})
: null}
<g className={`TasksWithoutGroups`}>
{tasksWithoutGroup.map((task, index) => {
return (
<P8PCyclogramTask
task={task}
shift={shift}
lineHeight={lineHeight}
openTaskEditor={openTaskEditor}
taskRenderer={taskRenderer}
key={index}
/>
);
})}
</g>
</g>
<P8PCyclogramHeader
columns={columns}
shift={shift}
maxWidth={maxWidth}
maxHeight={maxHeight}
columnRenderer={columnRenderer}
headerBlock={headerBlock}
/>
{visibleGroups.length !== 0 ? (
<g className="groups">
{visibleGroups.map((grp, indexGrp) => {
//Инициализируем параметры группы
let defaultView = null;
let customView = null;
let groupHeaderX = 0;
let groupHeaderY = 0;
let groupTasks = tasksWithGroup.filter(task => task.groupName === grp.name);
//Если по данной группе нет тасков - ничего не выводим
if (groupTasks.length === 0) {
return null;
}
//Если требуется отображать заголовок группы
if (grp.visible) {
//Формируем отображение по умолчанию
defaultView = (
<Box sx={{ ...STYLES.GROUP_HEADER_BOX, height: grp.height }}>
<Typography sx={{ ...STYLES.GROUP_HEADER, maxWidth: grp.width, maxHeight: grp.height }}>{grp.name}</Typography>
</Box>
);
//Формируем собственное отображение, если требуется
customView = groupHeaderRenderer ? groupHeaderRenderer({ group: grp }) : null;
//Рассчитываем координаты заголовка группы
groupHeaderX = Math.min(...groupTasks.map(o => o.start)) * shift;
groupHeaderY = NDEFAULT_HEADER_HEIGHT + Math.min(...groupTasks.map(o => o.lineNumb)) * lineHeight - grp.height - 5;
}
return (
<foreignObject
x={groupHeaderX}
y={groupHeaderY}
width={grp.width}
height={grp.height}
className={`TaskGrpHeader${indexGrp}`}
display="none"
key={indexGrp}
>
{customView ? customView : defaultView}
</foreignObject>
);
})}
</g>
) : null}
</g>
);
};
//Контроль свойств - Основная информация циклограммы
P8PCyclogramMain.propTypes = {
columns: PropTypes.array.isRequired,
groups: PropTypes.array,
tasks: PropTypes.array.isRequired,
shift: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired,
maxWidth: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
openTaskEditor: PropTypes.func.isRequired,
groupHeaderRenderer: PropTypes.func,
taskRenderer: PropTypes.func,
columnRenderer: PropTypes.func,
headerBlock: PropTypes.object
};
//Редактор задачи
const P8PCyclogramTaskEditor = ({
task,
taskAttributes,
onOk,
onCancel,
taskAttributeRenderer,
taskDialogRenderer,
nameCaption,
okBtnCaption,
cancelBtnCaption
}) => {
//Собственное состояние
const [state] = useState({
start: task.start,
end: task.end
});
//Отображаемые атрибуты
const dispTaskAttributes =
Array.isArray(taskAttributes) && taskAttributes.length > 0 ? taskAttributes.filter(attr => attr.visible && hasValue(task[attr.name])) : [];
//При сохранении
const handleOk = () => (onOk && state.start && state.end ? onOk() : null);
//При отмене
const handleCancel = () => (onCancel ? onCancel() : null);
//Генерация содержимого
return (
<Dialog open onClose={handleCancel}>
{taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, close: handleCancel })
) : (
<>
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
<List sx={STYLES.TASK_EDITOR_LIST}>
<ListItem alignItems="flex-start">
<ListItemText primary={nameCaption} secondary={task.fullName} />
</ListItem>
{dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
{dispTaskAttributes.length > 0
? dispTaskAttributes.map((attr, i) => {
const defaultView = task[attr.name];
const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null;
return (
<React.Fragment key={i}>
<ListItem alignItems="flex-start">
<ListItemText
primary={attr.caption}
secondaryTypographyProps={{ component: "span" }}
secondary={customView ? customView : defaultView}
/>
</ListItem>
{i < dispTaskAttributes.length - 1 ? <Divider component="li" /> : null}
</React.Fragment>
);
})
: null}
</List>
</DialogContent>
<DialogActions>
<Button onClick={handleOk}>{okBtnCaption}</Button>
<Button onClick={handleCancel}>{cancelBtnCaption}</Button>
</DialogActions>
</>
)}
</Dialog>
);
};
//Контроль свойств - Редактор задачи
P8PCyclogramTaskEditor.propTypes = {
task: P8P_CYCLOGRAM_TASK_SHAPE,
taskAttributes: PropTypes.arrayOf(P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE),
onOk: PropTypes.func,
onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
nameCaption: PropTypes.string.isRequired,
okBtnCaption: PropTypes.string.isRequired,
cancelBtnCaption: PropTypes.string.isRequired
};
//-----------
//Тело модуля
//-----------
//Циклограмма
const P8PCyclogram = ({
@ -716,13 +144,14 @@ const P8PCyclogram = ({
{title ? (
<Typography
p={1}
sx={{ ...STYLES.CYCLOGRAM_TITLE, ...(titleStyle ? titleStyle : {}) }}
sx={{ ...P8P_TYPOGRAPHY_TITLE, ...(titleStyle ? titleStyle : {}) }}
align="center"
color="textSecondary"
variant="subtitle1"
variant={P8P_TYPOGRAPHY_VARIANT.TITLE}
component="h6"
>
{onTitleClick ? (
<Link component="button" variant="body2" underline="hover" onClick={() => onTitleClick()}>
<Link component="button" variant={P8P_TYPOGRAPHY_VARIANT.BODY3} underline="hover" onClick={() => onTitleClick()}>
{title}
</Link>
) : (
@ -731,7 +160,7 @@ const P8PCyclogram = ({
</Typography>
) : null}
{zoomBar ? (
<Box p={1} sx={STYLES.CYCLOGRAM_ZOOM}>
<Box p={1} sx={P8P_COMPONENT_HEIGHT({ height: ZOOM_HEIGHT })}>
<IconButton
onClick={() => handleZoomChange(1)}
disabled={state.zoom == P8P_CYCLOGRAM_ZOOM[P8P_CYCLOGRAM_ZOOM.length - 1]}
@ -743,7 +172,16 @@ const P8PCyclogram = ({
</IconButton>
</Box>
) : null}
<Box className="scroll" sx={STYLES.CYCLOGRAM_BOX(state.noData, title, zoomBar)} onScroll={handleScroll}>
<Box
className="scroll"
//sx={STYLES.CYCLOGRAM_BOX(state.noData, title, zoomBar)}
sx={P8P_BOX_CYCLOGRAM({
noData: state.noData,
zoomBarHeight: zoomBar ? ZOOM_HEIGHT : null,
titleHeight: title ? TITLE_HEIGHT : null
})}
onScroll={handleScroll}
>
<svg id="cyclogram" width={state.maxWidth} height={state.maxHeight}>
<P8PCyclogramGrid
tasks={state.tasks}
@ -753,7 +191,7 @@ const P8PCyclogram = ({
maxHeight={state.maxHeight}
lineHeight={state.lineHeight}
/>
<P8PCyclogramMain
<P8PCyclogramView
columns={columns}
groups={groups}
tasks={state.tasks}

View File

@ -0,0 +1,56 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Колонка
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Typography } from "@mui/material"; //Интерфейсные компоненты
import { P8P_TYPOGRAPHY_VARIANT } from "../../theme/p8p_typography"; //Варианты шрифтов
import { NDEFAULT_HEADER_HEIGHT } from "./p8p_cyclogram_constants"; //Константы циклограммы
import { P8P_TYPOGRAPHY_CG_HEADER } from "../../theme/styles/typography"; //Стили текста
//-----------
//Тело модуля
//-----------
//Колонка
const P8PCyclogramHeaderColumn = ({ column, start, shift, columnRenderer }) => {
//Рассчитываем ширину колонки
const columnWidth = column.end - column.start;
//Формируем собственное отображение, если требуется
const customView = columnRenderer ? columnRenderer({ column }) : null;
return (
<>
<foreignObject x={start} y="0" width={columnWidth * shift} height={NDEFAULT_HEADER_HEIGHT}>
{customView ? (
customView
) : (
<Typography variant={P8P_TYPOGRAPHY_VARIANT.COLUMN} sx={P8P_TYPOGRAPHY_CG_HEADER} title={column.name} component={"p"}>
{column.name}
</Typography>
)}
</foreignObject>
</>
);
};
//Контроль свойств - Колонка
P8PCyclogramHeaderColumn.propTypes = {
column: PropTypes.object.isRequired,
start: PropTypes.number.isRequired,
shift: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
lastElement: PropTypes.bool,
columnRenderer: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramHeaderColumn };

View File

@ -0,0 +1,51 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Линии колонки таблицы
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
//-----------
//Тело модуля
//-----------
//Линии колонок таблицы
const P8PCyclogramColumnsLines = ({ columns, shift, y1, y2 }) => {
//Инициализируем старт текущей колонки
let prevColumnEnd = 0;
return (
<g>
{columns.map((column, index) => {
//Аккумулируем окончание последней колонки с учетом сдвига
prevColumnEnd = index !== 0 ? prevColumnEnd + (columns[index - 1].end - columns[index - 1].start) * shift : 0;
return <line x1={prevColumnEnd} y1={y1} x2={prevColumnEnd} y2={y2} stroke="#e0e0e0" key={index} />;
})}
<line
x1={prevColumnEnd + (columns[columns.length - 1].end - columns[columns.length - 1].start) * shift}
y1={y1}
x2={prevColumnEnd + (columns[columns.length - 1].end - columns[columns.length - 1].start) * shift}
y2={y2}
stroke="#e0e0e0"
/>
</g>
);
};
//Контроль свойств - Линии колонок таблицы
P8PCyclogramColumnsLines.propTypes = {
columns: PropTypes.array.isRequired,
shift: PropTypes.number.isRequired,
y1: PropTypes.number.isRequired,
y2: PropTypes.number.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramColumnsLines };

View File

@ -0,0 +1,80 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Константы
*/
//---------------------
//Подключение библиотек
//---------------------
import PropTypes from "prop-types"; //Контроль свойств компонента
//-----------
//Тело модуля
//-----------
//Уровни масштаба
const P8P_CYCLOGRAM_ZOOM = [0.2, 0.4, 0.7, 1, 1.5, 2, 2.5];
//Параметры элементов циклограммы
const NDEFAULT_LINE_HEIGHT = 20;
const NDEFAULT_HEADER_HEIGHT = 35;
//Высота заголовка
const TITLE_HEIGHT = "44px";
//Высота панели масштабирования
const ZOOM_HEIGHT = "56px";
//Структура колонки
const P8P_CYCLOGRAM_COLUMN_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
start: PropTypes.number.isRequired,
end: PropTypes.number.isRequired
});
//Структура группы
const P8P_CYCLOGRAM_GROUP_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
visible: PropTypes.bool.isRequired
});
//Структура задачи
const P8P_CYCLOGRAM_TASK_SHAPE = PropTypes.shape({
id: PropTypes.string.isRequired,
rn: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
fullName: PropTypes.string.isRequired,
lineNumb: PropTypes.number.isRequired,
start: PropTypes.number.isRequired,
end: PropTypes.number.isRequired,
group: PropTypes.string,
bgColor: PropTypes.string,
textColor: PropTypes.string,
highlightColor: PropTypes.string
});
//Структура динамического атрибута задачи
const P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
caption: PropTypes.string.isRequired,
visible: PropTypes.bool.isRequired
});
//----------------
//Интерфейс модуля
//----------------
export {
P8P_CYCLOGRAM_ZOOM,
NDEFAULT_LINE_HEIGHT,
NDEFAULT_HEADER_HEIGHT,
TITLE_HEIGHT,
ZOOM_HEIGHT,
P8P_CYCLOGRAM_COLUMN_SHAPE,
P8P_CYCLOGRAM_GROUP_SHAPE,
P8P_CYCLOGRAM_TASK_SHAPE,
P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE
};

View File

@ -0,0 +1,49 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Фон таблицы
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { P8PCyclogramRowsGrid } from "./p8p_cyclogram_rows_grid"; //Фон строк таблицы
import { P8PCyclogramRowsLines } from "./p8p_cyclogram_row_lines"; //Линии строк таблицы
import { P8PCyclogramColumnsLines } from "./p8p_cyclogram_column_lines"; //Линии колонок таблицы
import { NDEFAULT_HEADER_HEIGHT } from "./p8p_cyclogram_constants"; //Константы циклограммы
//-----------
//Тело модуля
//-----------
//Фон таблицы
const P8PCyclogramGrid = ({ tasks, columns, shift, maxWidth, maxHeight, lineHeight }) => {
//Формируем массив строк исходя из максимального значения строки задачи
const rows = Array.from(Array(Math.max(...tasks.map(o => o.lineNumb)) + 1).keys());
return (
<g className="grid">
<rect x="0" y="0" width={maxWidth} height={maxHeight}></rect>
<P8PCyclogramRowsGrid rows={rows} maxWidth={maxWidth} lineHeight={lineHeight} />
<P8PCyclogramRowsLines rows={rows} maxWidth={maxWidth} lineHeight={lineHeight} />
<P8PCyclogramColumnsLines columns={columns} shift={shift} y1={NDEFAULT_HEADER_HEIGHT} y2={maxHeight} />
</g>
);
};
//Контроль свойств - Фон таблицы
P8PCyclogramGrid.propTypes = {
tasks: PropTypes.array.isRequired,
columns: PropTypes.array.isRequired,
shift: PropTypes.number.isRequired,
maxWidth: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramGrid };

View File

@ -0,0 +1,63 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Заголовок
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { NDEFAULT_HEADER_HEIGHT } from "./p8p_cyclogram_constants"; //Константы циклограммы
import { P8PCyclogramHeaderColumn } from "./p8p_cyclogram_column"; //Колонка циклограммы
import { P8PCyclogramColumnsLines } from "./p8p_cyclogram_column_lines"; //Линии колонок таблицы
//-----------
//Тело модуля
//-----------
//Заголовок
const P8PCyclogramHeader = ({ columns, shift, maxWidth, maxHeight, columnRenderer, headerBlock }) => {
//Инициализируем старт текущей колонки
let prevColumnEnd = 0;
return (
<g className="header" ref={headerBlock}>
<rect x="0" y="0" width={maxWidth} height={NDEFAULT_HEADER_HEIGHT} fill="#ffffff" stroke="#e0e0e0" strokeWidth="1.4"></rect>
{columns.map((column, index) => {
//Аккумулируем окончание последней колонки с учетом сдвига
prevColumnEnd = index !== 0 ? prevColumnEnd + (columns[index - 1].end - columns[index - 1].start) * shift : 0;
return (
<P8PCyclogramHeaderColumn
column={column}
shift={shift}
start={prevColumnEnd}
maxHeight={maxHeight}
lastElement={columns.length - 1 === index}
columnRenderer={columnRenderer}
key={index}
/>
);
})}
<g className="columnsDividers">
<P8PCyclogramColumnsLines columns={columns} shift={shift} y1={0} y2={NDEFAULT_HEADER_HEIGHT} />
</g>
</g>
);
};
//Контроль свойств - Заголовок
P8PCyclogramHeader.propTypes = {
columns: PropTypes.array.isRequired,
shift: PropTypes.number.isRequired,
maxWidth: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
columnRenderer: PropTypes.func,
headerBlock: PropTypes.object
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramHeader };

View File

@ -0,0 +1,42 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Линии строк таблицы
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { NDEFAULT_HEADER_HEIGHT } from "./p8p_cyclogram_constants"; //Константы циклограммы
//Линии строк таблицы
const P8PCyclogramRowsLines = ({ rows, maxWidth, lineHeight }) => {
return (
<g>
{rows.map((el, index) => (
<line
x1="0"
y1={NDEFAULT_HEADER_HEIGHT + lineHeight + index * lineHeight}
x2={maxWidth}
y2={NDEFAULT_HEADER_HEIGHT + lineHeight + index * lineHeight}
key={index}
></line>
))}
</g>
);
};
//Контроль свойств - Линии строк таблицы
P8PCyclogramRowsLines.propTypes = {
rows: PropTypes.array.isRequired,
maxWidth: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramRowsLines };

View File

@ -0,0 +1,44 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Фон строк таблицы
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box } from "@mui/material"; //Интерфейсные компоненты
import { P8P_BOX_CYCLOGRAM_ROW } from "../../theme/styles/box"; //Стили контейнеров
import { NDEFAULT_HEADER_HEIGHT } from "./p8p_cyclogram_constants"; //Константы циклограммы
//-----------
//Тело модуля
//-----------
//Фон строк таблицы
const P8PCyclogramRowsGrid = ({ rows, maxWidth, lineHeight }) => {
return (
<g>
{rows.map((el, index) => (
<foreignObject x="0" y={NDEFAULT_HEADER_HEIGHT + index * lineHeight} width={maxWidth} height={lineHeight} key={index}>
<Box sx={P8P_BOX_CYCLOGRAM_ROW({ index })} height={lineHeight} />
</foreignObject>
))}
</g>
);
};
//Контроль свойств - Фон строк таблицы
P8PCyclogramRowsGrid.propTypes = {
rows: PropTypes.array.isRequired,
maxWidth: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramRowsGrid };

View File

@ -0,0 +1,77 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Задача
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Typography } from "@mui/material"; //Интерфейсные компоненты
import { hasValue } from "../../core/utils"; //Вспомогательный функции
import { P8P_TYPOGRAPHY_VARIANT } from "../../theme/p8p_typography"; //Варианты шрифтов
import { P8P_BOX_CYCLOGRAM_TASK } from "../../theme/styles/box"; //Стили контейнеров
import { NDEFAULT_HEADER_HEIGHT } from "./p8p_cyclogram_constants"; //Константы циклограммы
import { P8P_TYPOGRAPHY_CG_TASK } from "../../theme/styles/typography"; //Стили текста
//-----------
//Тело модуля
//-----------
//Задача
const P8PCyclogramTask = ({ task, indexGrp, shift, lineHeight, openTaskEditor, taskRenderer }) => {
//Рассчитываем ширину задачи
const width = task.end !== 0 ? (task.end - task.start) * shift : 0;
//Формируем собственное отображение, если требуется
const customView = taskRenderer ? taskRenderer({ task, taskHeight: lineHeight, taskWidth: width }) || {} : {};
//Определение количества доступных строк
const availableLines = Math.floor(lineHeight / 18);
return (
<foreignObject
x={task.start !== 0 ? task.start * shift : 0}
y={NDEFAULT_HEADER_HEIGHT + task.lineNumb * lineHeight}
width={width}
height={lineHeight}
>
<Box
className={hasValue(indexGrp) ? `TaskGrp${indexGrp}` : null}
sx={{
...P8P_BOX_CYCLOGRAM_TASK({ lineHeight, bgColor: task.bgColor, textColor: task.textColor, highlightColor: task.highlightColor }),
...customView.taskStyle
}}
{...customView.taskProps}
onClick={() => openTaskEditor(task)}
>
{customView.data ? (
customView.data
) : (
<Typography
sx={P8P_TYPOGRAPHY_CG_TASK({ maxHeight: lineHeight, availableLines })}
variant={P8P_TYPOGRAPHY_VARIANT.BODY2_LIGHT}
title={task.name}
>
{task.name}
</Typography>
)}
</Box>
</foreignObject>
);
};
//Контроль свойств - Задача
P8PCyclogramTask.propTypes = {
task: PropTypes.object.isRequired,
indexGrp: PropTypes.number,
shift: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired,
openTaskEditor: PropTypes.func.isRequired,
taskRenderer: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramTask };

View File

@ -0,0 +1,117 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Редактор задачи
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogActions, DialogContent, Button, List, ListItem, ListItemText, Divider } from "@mui/material"; //Интерфейсные компоненты
import { hasValue } from "../../core/utils"; //Вспомогательный функции
import { P8P_DIALOG_CONTENT_VARIANT } from "../../theme/variants/p8p_dialog_content_variants"; //Варианты диалогов (содержимое)
import { P8P_LIST_VARIANT } from "../../theme/variants/p8p_list_variants"; //Варианты списков
import { P8P_LIST_ITEM_TEXT_VARIANT } from "../../theme/variants/p8p_list_item_text_variants"; //Варианты значений списков
import { P8P_BUTTON_VARIANT } from "../../theme/variants/p8p_button_variants"; //Варианты кнопок
import { P8P_CYCLOGRAM_TASK_SHAPE, P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE } from "./p8p_cyclogram_constants"; //Константы циклограммы
//-----------
//Тело модуля
//-----------
//Редактор задачи
const P8PCyclogramTaskEditor = ({
task,
taskAttributes,
onOk,
onCancel,
taskAttributeRenderer,
taskDialogRenderer,
nameCaption,
okBtnCaption,
cancelBtnCaption
}) => {
//Собственное состояние
const [state] = useState({
start: task.start,
end: task.end
});
//Отображаемые атрибуты
const dispTaskAttributes =
Array.isArray(taskAttributes) && taskAttributes.length > 0 ? taskAttributes.filter(attr => attr.visible && hasValue(task[attr.name])) : [];
//При сохранении
const handleOk = () => (onOk && state.start && state.end ? onOk() : null);
//При отмене
const handleCancel = () => (onCancel ? onCancel() : null);
//Генерация содержимого
return (
<Dialog open onClose={handleCancel}>
{taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, close: handleCancel })
) : (
<>
<DialogContent variant={P8P_DIALOG_CONTENT_VARIANT.TASK}>
<List variant={P8P_LIST_VARIANT.GANTT_TASK}>
<ListItem alignItems="flex-start">
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={nameCaption} secondary={task.fullName} />
</ListItem>
{dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
{dispTaskAttributes.length > 0
? dispTaskAttributes.map((attr, i) => {
const defaultView = task[attr.name];
const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null;
return (
<React.Fragment key={i}>
<ListItem alignItems="flex-start">
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
primary={attr.caption}
secondaryTypographyProps={{ component: "span" }}
secondary={customView ? customView : defaultView}
/>
</ListItem>
{i < dispTaskAttributes.length - 1 ? <Divider component="li" /> : null}
</React.Fragment>
);
})
: null}
</List>
</DialogContent>
<DialogActions>
<Button variant={P8P_BUTTON_VARIANT.SECONDARY} onClick={handleCancel}>
{cancelBtnCaption}
</Button>
<Button variant={P8P_BUTTON_VARIANT.PRIMARY} onClick={handleOk}>
{okBtnCaption}
</Button>
</DialogActions>
</>
)}
</Dialog>
);
};
//Контроль свойств - Редактор задачи
P8PCyclogramTaskEditor.propTypes = {
task: P8P_CYCLOGRAM_TASK_SHAPE,
taskAttributes: PropTypes.arrayOf(P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE),
onOk: PropTypes.func,
onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
nameCaption: PropTypes.string.isRequired,
okBtnCaption: PropTypes.string.isRequired,
cancelBtnCaption: PropTypes.string.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramTaskEditor };

View File

@ -0,0 +1,34 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Ядро: Вспомогательные функции
*/
//-----------
//Тело модуля
//-----------
//Определение сдвига для максимальной ширины колонок
export const getShift = (columns, currentColumnsMaxWidth, maxCyclogramWidth) => {
//Определяем доступное пространство для расширения
let maxWidthDiff = maxCyclogramWidth - currentColumnsMaxWidth;
//Инициализируем значение сдвига
let shift = 1;
//Если доступно больше ширины и есть пространство для расширения
if (maxCyclogramWidth > currentColumnsMaxWidth && maxCyclogramWidth - maxWidthDiff > columns.length) {
//Определяем доступный сдвиг колонок
shift = maxCyclogramWidth / currentColumnsMaxWidth;
}
//Возвращаем сдвиг
return shift;
};
//Формирование стилей для группы
export const getGroupStyles = (indexGrp, highlightColor) => {
return `.main .TaskGrp${indexGrp}:hover .TaskGrp${indexGrp} {
${highlightColor ? `background: ${highlightColor};` : `filter: brightness(1.15);`}
}
.main:has(.TaskGrp${indexGrp}:hover) .TaskGrpHeader${indexGrp} {
display: block;
}
`;
};

View File

@ -0,0 +1,171 @@
/*
Парус 8 - Панели мониторинга - Циклограмма
Компонент: Представление циклограммы
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Typography } from "@mui/material"; //Интерфейсные компоненты
import { hasValue } from "../../core/utils"; //Вспомогательный функции
import { P8P_TYPOGRAPHY_VARIANT } from "../../theme/p8p_typography"; //Варианты шрифтов
import { P8P_BOX_CYCLOGRAM_GROUP } from "../../theme/styles/box"; //Стили контейнеров
import { NDEFAULT_HEADER_HEIGHT } from "./p8p_cyclogram_constants"; //Константы циклограммы
import { P8PCyclogramTask } from "./p8p_cyclogram_task"; //Задача циклограммы
import { P8PCyclogramHeader } from "./p8p_cyclogram_header"; //Заголовок циклограммы
import { getGroupStyles } from "./p8p_cyclogram_utils"; //Вспомогательные функции циклограммы
import { P8P_TYPOGRAPHY_CG_GROUP } from "../../theme/styles/typography"; //Стили текста
//-----------
//Тело модуля
//-----------
//Представление циклограммы
const P8PCyclogramView = ({
columns,
groups,
tasks,
shift,
lineHeight,
maxWidth,
maxHeight,
openTaskEditor,
groupHeaderRenderer,
taskRenderer,
columnRenderer,
headerBlock
}) => {
//Инициализируем коллекцию тасков с группами
const tasksWithGroup = tasks.filter(task => hasValue(task.groupName));
//Инициализируем коллекцию тасков без групп
const tasksWithoutGroup = tasks.filter(task => !hasValue(task.groupName));
//Инициализируем коллекцию отображаемых групп
const visibleGroups = groups ? groups.filter(group => group.visible) : [];
return (
<g className="main">
<g className="tasks">
{visibleGroups.length !== 0
? visibleGroups.map((grp, indexGrp) => {
//Считываем задачи группы
let groupTasks = tasksWithGroup.filter(task => task.groupName === grp.name);
//Если по данной группе нет тасков - ничего не выводим
if (groupTasks.length === 0) {
return null;
}
return (
<g className={`TaskGrp${indexGrp}`} key={indexGrp}>
{groupTasks.map((task, index) => (
<P8PCyclogramTask
task={task}
indexGrp={indexGrp}
shift={shift}
lineHeight={lineHeight}
openTaskEditor={openTaskEditor}
taskRenderer={taskRenderer}
key={index}
/>
))}
<style>{getGroupStyles(indexGrp, grp.highlightColor)}</style>
</g>
);
})
: null}
<g className={`TasksWithoutGroups`}>
{tasksWithoutGroup.map((task, index) => {
return (
<P8PCyclogramTask
task={task}
shift={shift}
lineHeight={lineHeight}
openTaskEditor={openTaskEditor}
taskRenderer={taskRenderer}
key={index}
/>
);
})}
</g>
</g>
<P8PCyclogramHeader
columns={columns}
shift={shift}
maxWidth={maxWidth}
maxHeight={maxHeight}
columnRenderer={columnRenderer}
headerBlock={headerBlock}
/>
{visibleGroups.length !== 0 ? (
<g className="groups">
{visibleGroups.map((grp, indexGrp) => {
//Инициализируем параметры группы
let defaultView = null;
let customView = null;
let groupHeaderX = 0;
let groupHeaderY = 0;
let groupTasks = tasksWithGroup.filter(task => task.groupName === grp.name);
//Если по данной группе нет тасков - ничего не выводим
if (groupTasks.length === 0) {
return null;
}
//Если требуется отображать заголовок группы
if (grp.visible) {
//Формируем отображение по умолчанию
defaultView = (
<Box sx={P8P_BOX_CYCLOGRAM_GROUP({ height: grp.height })}>
<Typography
sx={P8P_TYPOGRAPHY_CG_GROUP({ maxWidth: grp.width, maxHeight: grp.height })}
variant={P8P_TYPOGRAPHY_VARIANT.BODY3_LIGHT}
>
{grp.name}
</Typography>
</Box>
);
//Формируем собственное отображение, если требуется
customView = groupHeaderRenderer ? groupHeaderRenderer({ group: grp }) : null;
//Рассчитываем координаты заголовка группы
groupHeaderX = Math.min(...groupTasks.map(o => o.start)) * shift;
groupHeaderY = NDEFAULT_HEADER_HEIGHT + Math.min(...groupTasks.map(o => o.lineNumb)) * lineHeight - grp.height - 5;
}
return (
<foreignObject
x={groupHeaderX}
y={groupHeaderY}
width={grp.width}
height={grp.height}
className={`TaskGrpHeader${indexGrp}`}
display="none"
key={indexGrp}
>
{customView ? customView : defaultView}
</foreignObject>
);
})}
</g>
) : null}
</g>
);
};
//Контроль свойств - Представление циклограммы
P8PCyclogramView.propTypes = {
columns: PropTypes.array.isRequired,
groups: PropTypes.array,
tasks: PropTypes.array.isRequired,
shift: PropTypes.number.isRequired,
lineHeight: PropTypes.number.isRequired,
maxWidth: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
openTaskEditor: PropTypes.func.isRequired,
groupHeaderRenderer: PropTypes.func,
taskRenderer: PropTypes.func,
columnRenderer: PropTypes.func,
headerBlock: PropTypes.object
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogramView };

View File

@ -19,7 +19,7 @@ import {
P8P_TABLE_FILTERS_HEIGHT,
P8P_TABLE_PAGINATOR_ALIGN,
P8P_TABLE_PAGINATOR_POSITION
} from "./p8p_table"; //Таблица
} from "./p8p_table/p8p_table"; //Таблица
import { useP8PDataGrid } from "./p8p_data_grid_hooks"; //Хук для таблицы данных
//---------
@ -92,6 +92,7 @@ const P8PDataGrid = ({
valueFormatter,
containerComponent,
containerComponentProps,
headExpandCellStyle,
onOrderChanged,
onFilterChanged,
onPagesCountChanged,
@ -187,6 +188,7 @@ const P8PDataGrid = ({
containerComponent={containerComponent}
containerComponentProps={containerComponentProps}
morePagesBtnProps={morePagesBtnProps}
headExpandCellStyle={headExpandCellStyle}
onOrderChanged={handleOrderChanged}
onFilterChanged={handleFilterChanged}
onPagesCountChanged={handlePagesCountChanged}
@ -233,6 +235,7 @@ P8PDataGrid.propTypes = {
valueFormatter: PropTypes.func,
containerComponent: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]),
containerComponentProps: PropTypes.object,
headExpandCellStyle: PropTypes.object,
onOrderChanged: PropTypes.func,
onFilterChanged: PropTypes.func,
onPagesCountChanged: PropTypes.func,

View File

@ -12,7 +12,9 @@ import PropTypes from "prop-types"; //Контроль свойств компо
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from "@mui/material"; //Интерфейсные компоненты
import { BUTTONS } from "../../app.text"; //Общие текстовые ресурсы
import { P8P_INPUT, P8PInput } from "./p8p_input"; //Поле ввода
import { APP_STYLES } from "../../app.styles"; //Типовые стили
import { P8P_DIALOG_CONTENT_VARIANT } from "../theme/variants/p8p_dialog_content_variants"; //Варианты содержимого диалога
import { P8P_BUTTON_VARIANT } from "../theme/variants/p8p_button_variants"; //Варианты кнопок
import { P8P_DIALOG_TITLE_VARIANT } from "../theme/variants/p8p_dialog_title_variants"; //Варианты заголовков диалога
//---------
//Константы
@ -27,12 +29,6 @@ const P8P_DIALOG_WIDTH = {
XL: "xl"
};
//Стили
const STYLES = {
SCROLL: display =>
display === true ? { overflow: "auto", ...APP_STYLES.SCROLL } : { overflow: "hidden", display: "flex", flexDirection: "column" }
};
//-----------------------
//Вспомогательные функции
//-----------------------
@ -93,22 +89,29 @@ const P8PDialog = ({
//Формирование представления
return (
<Dialog onClose={handleClose} open {...{ ...(width ? { maxWidth: width } : {}), ...(fullWidth === true ? { fullWidth: true } : {}) }}>
<DialogTitle>{title}</DialogTitle>
<DialogContent sx={STYLES.SCROLL(scrollContent)}>
<DialogTitle variant={P8P_DIALOG_TITLE_VARIANT.PRIMARY}>{title}</DialogTitle>
<DialogContent variant={scrollContent ? P8P_DIALOG_CONTENT_VARIANT.PRIMARY : P8P_DIALOG_CONTENT_VARIANT.HIDDEN}>
{inputsState.map((input, i) => (
<P8PInput key={i} {...input} formValues={formValues} onChange={handleInputChange} />
))}
{children}
</DialogContent>
<DialogActions>
{onClose && (
<Button variant={P8P_BUTTON_VARIANT.OUTLINED} onClick={handleClose}>
{BUTTONS.CLOSE}
</Button>
)}
{onCancel && (
<Button variant={P8P_BUTTON_VARIANT.SECONDARY} onClick={handleCancel}>
{BUTTONS.CANCEL}
</Button>
)}
{onOk && (
<Button disabled={okDisabled} onClick={handleOk}>
<Button variant={P8P_BUTTON_VARIANT.PRIMARY} disabled={okDisabled} onClick={handleOk}>
{BUTTONS.OK}
</Button>
)}
{onCancel && <Button onClick={handleCancel}>{BUTTONS.CANCEL}</Button>}
{onClose && <Button onClick={handleClose}>{BUTTONS.CLOSE}</Button>}
</DialogActions>
</Dialog>
);

View File

@ -10,17 +10,10 @@
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, AppBar, Toolbar, IconButton, Typography, Icon, DialogContent, DialogTitle } from "@mui/material"; //Интерфейсные компоненты
//---------
//Константы
//---------
//Стили
const STYLES = {
DIALOG_TITLE: { padding: 0 },
APP_BAR: { position: "relative" },
TITLE_TYPOGRAPHY: { ml: 2, flex: 1 }
};
import { P8P_APP_BAR_VARIANT } from "../theme/variants/p8p_app_bar_variants"; //Варианты областей заголовка
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
import { P8P_COMPONENT_ZERO_PADDING } from "../theme/styles/common"; //Стили - общие
import { P8P_TYPOGRAPHY_DIALOG_TITLE } from "../theme/styles/typography"; //Стили текста
//-----------
//Тело модуля
@ -33,14 +26,14 @@ const P8PFullScreenDialog = ({ title, onClose, contentProps, children }) => {
};
return (
<Dialog fullScreen open onClose={handleClose} scroll="paper">
<DialogTitle sx={STYLES.DIALOG_TITLE}>
<AppBar sx={STYLES.APP_BAR}>
<Dialog fullScreen open onClose={handleClose} scroll="paper" p={0}>
<DialogTitle sx={P8P_COMPONENT_ZERO_PADDING}>
<AppBar variant={P8P_APP_BAR_VARIANT.RELATIVE}>
<Toolbar>
<IconButton edge="start" color="inherit" onClick={handleClose} aria-label="close">
<Icon>close</Icon>
</IconButton>
<Typography sx={STYLES.TITLE_TYPOGRAPHY} variant="h6" component="div">
<Typography variant={P8P_TYPOGRAPHY_VARIANT.H6} sx={P8P_TYPOGRAPHY_DIALOG_TITLE} component="div">
{title}
</Typography>
</Toolbar>

View File

@ -9,324 +9,23 @@
import React, { useEffect, useState, useCallback, useRef } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Box,
IconButton,
Icon,
Typography,
Dialog,
DialogActions,
DialogContent,
TextField,
Button,
List,
ListItem,
ListItemText,
Divider,
Slider,
Link
} from "@mui/material"; //Интерфейсные компоненты
import { Box, IconButton, Icon, Typography, Link } from "@mui/material"; //Интерфейсные компоненты
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { useP8PGantt } from "./p8p_gantt_hooks"; //Хук для диаграммы Ганта
//---------
//Константы
//---------
//Уровни масштаба
const P8P_GANTT_ZOOM = [0, 1, 2, 3, 4, 5];
//Уровни масштаба (строковые наименования в терминах библиотеки)
const P8P_GANTT_ZOOM_VIEW_MODES = {
0: "Quarter Day",
1: "Half Day",
2: "Day",
3: "Week",
4: "Month",
5: "Year"
};
//Структура задачи
const P8P_GANTT_TASK_SHAPE = PropTypes.shape({
id: PropTypes.string.isRequired,
rn: PropTypes.number.isRequired,
numb: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
fullName: PropTypes.string.isRequired,
start: PropTypes.string.isRequired,
end: PropTypes.string.isRequired,
progress: PropTypes.number,
dependencies: PropTypes.array,
readOnly: PropTypes.bool,
readOnlyDates: PropTypes.bool,
readOnlyProgress: PropTypes.bool,
bgColor: PropTypes.string,
textColor: PropTypes.string,
bgProgressColor: PropTypes.string
});
//Структура динамического атрибута задачи
const P8P_GANTT_TASK_ATTRIBUTE_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
caption: PropTypes.string.isRequired,
visible: PropTypes.bool.isRequired
});
//Структура описания цвета задачи
const P8P_GANTT_TASK_COLOR_SHAPE = PropTypes.shape({
bgColor: PropTypes.string,
textColor: PropTypes.string,
bgProgressColor: PropTypes.string,
desc: PropTypes.string.isRequired
});
//Высота заголовка
const TITLE_HEIGHT = "44px";
//Высота панели масштабирования
const ZOOM_HEIGHT = "56px";
//Стили
const STYLES = {
TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" },
GANTT_TITLE: { height: TITLE_HEIGHT },
GANTT_ZOOM: { height: ZOOM_HEIGHT },
GANTT: (noData, title, zoomBar) => ({
height: `calc(100% - ${zoomBar ? ZOOM_HEIGHT : "0px"} - ${title ? TITLE_HEIGHT : "0px"})`,
display: noData ? "none" : ""
})
};
//--------------------------------
//Вспомогательные классы и функции
//--------------------------------
//Проверка существования значения
const hasValue = value => typeof value !== "undefined" && value !== null && value !== "";
//Формирование описания для легенды
const taskLegendDesc = ({ task, taskColors }) => {
if (Array.isArray(taskColors) && taskColors.length > 0) {
const colorDesc = taskColors.find(
color => task.bgColor === color.bgColor && task.textColor === color.textColor && task.bgProgressColor === color.bgProgressColor
);
if (colorDesc)
return {
text: colorDesc.desc,
style: {
...(colorDesc.bgProgressColor
? {
background: `linear-gradient(to right, ${colorDesc.bgProgressColor} ,${
colorDesc.bgColor ? colorDesc.bgColor : "transparent"
})`
}
: colorDesc.bgColor
? { backgroundColor: colorDesc.bgColor }
: {}),
...(colorDesc.textColor ? { color: colorDesc.textColor } : {})
}
};
else return null;
} else return null;
};
//Редактор задачи
const P8PGanttTaskEditor = ({
task,
taskAttributes,
taskColors,
onOk,
onCancel,
taskAttributeRenderer,
taskDialogRenderer,
taskDialogProps,
numbCaption,
nameCaption,
startCaption,
endCaption,
progressCaption,
legendCaption,
okBtnCaption,
cancelBtnCaption
}) => {
//Собственное состояние
const [state, setState] = useState({
start: task.start,
end: task.end,
progress: task.progress
});
//Отображаемые атрибуты
const dispTaskAttributes =
Array.isArray(taskAttributes) && taskAttributes.length > 0 ? taskAttributes.filter(attr => attr.visible && hasValue(task[attr.name])) : [];
//При сохранении
const handleOk = () => (onOk && state.start && state.end ? onOk({ task, start: state.start, end: state.end, progress: state.progress }) : null);
//При отмене
const handleCancel = () => (onCancel ? onCancel() : null);
//При изменении сроков
const handlePeriodChanged = e => setState(prev => ({ ...prev, [e.target.name]: e.target.value }));
//При изменении прогресса
const handleProgressChanged = (e, newValue) => setState(prev => ({ ...prev, progress: newValue }));
//Описание легенды для задачи
const legendDesc = taskLegendDesc({ task, taskColors });
let legend = legendDesc ? (
<ListItemText
secondaryTypographyProps={{
p: 1,
sx: legendDesc.style
}}
primary={legendCaption}
secondary={legendDesc.text}
/>
) : null;
//Генерация содержимого
return (
<Dialog open onClose={handleCancel} {...(taskDialogProps ? taskDialogProps : {})}>
{taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
) : (
<>
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
<List sx={STYLES.TASK_EDITOR_LIST}>
<ListItem alignItems="flex-start">
<ListItemText primary={numbCaption} secondary={task.numb} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText primary={nameCaption} secondary={task.fullName} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
secondaryTypographyProps={{ component: "span" }}
primary={startCaption}
secondary={
<TextField
error={!state.start}
disabled={task.readOnly === true || task.readOnlyDates === true}
name="start"
fullWidth
required
InputLabelProps={{ shrink: true }}
type={"date"}
value={state.start}
onChange={handlePeriodChanged}
variant="standard"
size="small"
margin="normal"
/>
}
/>
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
secondaryTypographyProps={{ component: "span" }}
primary={endCaption}
secondary={
<TextField
error={!state.end}
disabled={task.readOnly === true || task.readOnlyDates === true}
name="end"
fullWidth
required
InputLabelProps={{ shrink: true }}
type={"date"}
value={state.end}
onChange={handlePeriodChanged}
variant="standard"
size="small"
margin="normal"
/>
}
/>
</ListItem>
{hasValue(task.progress) || legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
{hasValue(task.progress) ? (
<>
<ListItem alignItems="flex-start">
<ListItemText
secondaryTypographyProps={{ component: "span" }}
primary={`${progressCaption}${
task.readOnly === true || task.readOnlyProgress === true ? ` (${task.progress}%)` : ""
}`}
secondary={
<Slider
disabled={task.readOnly === true || task.readOnlyProgress === true}
defaultValue={task.progress}
valueLabelDisplay="auto"
onChange={handleProgressChanged}
/>
}
/>
</ListItem>
{legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
</>
) : null}
{legend ? (
<>
<ListItem alignItems="flex-start">{legend}</ListItem>
{dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
</>
) : null}
{dispTaskAttributes.length > 0
? dispTaskAttributes.map((attr, i) => {
const defaultView = task[attr.name];
const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null;
return (
<React.Fragment key={i}>
<ListItem alignItems="flex-start">
<ListItemText
primary={attr.caption}
secondaryTypographyProps={{ component: "span" }}
secondary={customView ? customView : defaultView}
/>
</ListItem>
{i < dispTaskAttributes.length - 1 ? <Divider component="li" /> : null}
</React.Fragment>
);
})
: null}
</List>
</DialogContent>
<DialogActions>
<Button disabled={!state.start || !state.end || task.readOnly} onClick={handleOk}>
{okBtnCaption}
</Button>
<Button onClick={handleCancel}>{cancelBtnCaption}</Button>
</DialogActions>
</>
)}
</Dialog>
);
};
//Контроль свойств - Редактор задачи
P8PGanttTaskEditor.propTypes = {
task: P8P_GANTT_TASK_SHAPE,
taskAttributes: PropTypes.arrayOf(P8P_GANTT_TASK_ATTRIBUTE_SHAPE),
taskColors: PropTypes.arrayOf(P8P_GANTT_TASK_COLOR_SHAPE),
onOk: PropTypes.func,
onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
numbCaption: PropTypes.string.isRequired,
nameCaption: PropTypes.string.isRequired,
startCaption: PropTypes.string.isRequired,
endCaption: PropTypes.string.isRequired,
progressCaption: PropTypes.string.isRequired,
legendCaption: PropTypes.string.isRequired,
okBtnCaption: PropTypes.string.isRequired,
cancelBtnCaption: PropTypes.string.isRequired
};
import { useP8PGantt } from "./p8p_gantt/p8p_gantt_hooks"; //Хук для диаграммы Ганта
import {
P8P_GANTT_ZOOM,
P8P_GANTT_ZOOM_VIEW_MODES,
P8P_GANTT_TASK_SHAPE,
P8P_GANTT_TASK_ATTRIBUTE_SHAPE,
P8P_GANTT_TASK_COLOR_SHAPE,
TITLE_HEIGHT,
ZOOM_HEIGHT
} from "./p8p_gantt/p8p_gantt_constants"; //Константы диаграммы Ганта
import { P8PGanttTaskEditor, taskLegendDesc } from "./p8p_gantt/p8p_gantt_task_editor"; //Редактор задачи
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
import { P8P_COMPONENT_HEIGHT } from "../theme/styles/common"; //Стили - общие
import { P8P_BOX_GANTT } from "../theme/styles/box"; //Стили контейнеров
import { P8P_TYPOGRAPHY_TITLE } from "../theme/styles/typography"; //Стили текста
//-----------
//Тело модуля
@ -359,7 +58,9 @@ const P8PGantt = ({
progressTaskEditorCaption,
legendTaskEditorCaption,
okTaskEditorBtnCaption,
cancelTaskEditorBtnCaption
cancelTaskEditorBtnCaption,
zoomBarStyle,
zoomBarHeight
}) => {
//Собственное состояние
const [state, setState] = useState({
@ -438,13 +139,14 @@ const P8PGantt = ({
{state.gantt && !state.noData && title ? (
<Typography
p={1}
sx={{ ...STYLES.GANTT_TITLE, ...(titleStyle ? titleStyle : {}) }}
sx={{ ...P8P_TYPOGRAPHY_TITLE, ...(titleStyle ? titleStyle : {}) }}
align="center"
color="textSecondary"
variant="subtitle1"
variant={P8P_TYPOGRAPHY_VARIANT.TITLE}
component="h6"
>
{onTitleClick ? (
<Link component="button" variant="body2" underline="hover" onClick={() => onTitleClick()}>
<Link component="button" variant={P8P_TYPOGRAPHY_VARIANT.BODY3} underline="hover" onClick={() => onTitleClick()}>
{title}
</Link>
) : (
@ -453,7 +155,7 @@ const P8PGantt = ({
</Typography>
) : null}
{state.gantt && !state.noData && zoomBar ? (
<Box p={1} sx={STYLES.GANTT_ZOOM}>
<Box p={1} sx={zoomBarStyle ? zoomBarStyle : P8P_COMPONENT_HEIGHT({ height: ZOOM_HEIGHT })}>
<IconButton onClick={() => handleZoomChange(-1)} disabled={state.zoom == 0}>
<Icon>zoom_in</Icon>
</IconButton>
@ -482,7 +184,14 @@ const P8PGantt = ({
cancelBtnCaption={cancelTaskEditorBtnCaption}
/>
) : null}
<div style={STYLES.GANTT(state.noData, title, zoomBar)} ref={svgContainerRef}>
<div
style={P8P_BOX_GANTT({
noData: state.noData,
zoomBarHeight: zoomBar ? (zoomBarHeight ? zoomBarHeight : ZOOM_HEIGHT) : null,
titleHeight: title ? TITLE_HEIGHT : null
})}
ref={svgContainerRef}
>
<svg id="__gantt__" width="100%"></svg>
</div>
</div>
@ -516,7 +225,9 @@ P8PGantt.propTypes = {
progressTaskEditorCaption: PropTypes.string.isRequired,
legendTaskEditorCaption: PropTypes.string.isRequired,
okTaskEditorBtnCaption: PropTypes.string.isRequired,
cancelTaskEditorBtnCaption: PropTypes.string.isRequired
cancelTaskEditorBtnCaption: PropTypes.string.isRequired,
zoomBarStyle: PropTypes.object,
zoomBarHeight: PropTypes.string
};
//----------------

View File

@ -0,0 +1,81 @@
/*
Парус 8 - Панели мониторинга - Диаграмма Ганта
Компонент: Константы
*/
//---------------------
//Подключение библиотек
//---------------------
import PropTypes from "prop-types"; //Контроль свойств компонента
//-----------
//Тело модуля
//-----------
//Уровни масштаба
const P8P_GANTT_ZOOM = [0, 1, 2, 3, 4, 5];
//Уровни масштаба (строковые наименования в терминах библиотеки)
const P8P_GANTT_ZOOM_VIEW_MODES = {
0: "Quarter Day",
1: "Half Day",
2: "Day",
3: "Week",
4: "Month",
5: "Year"
};
//Структура задачи
const P8P_GANTT_TASK_SHAPE = PropTypes.shape({
id: PropTypes.string.isRequired,
rn: PropTypes.number.isRequired,
numb: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
fullName: PropTypes.string.isRequired,
start: PropTypes.string.isRequired,
end: PropTypes.string.isRequired,
progress: PropTypes.number,
dependencies: PropTypes.array,
readOnly: PropTypes.bool,
readOnlyDates: PropTypes.bool,
readOnlyProgress: PropTypes.bool,
bgColor: PropTypes.string,
textColor: PropTypes.string,
bgProgressColor: PropTypes.string
});
//Структура динамического атрибута задачи
const P8P_GANTT_TASK_ATTRIBUTE_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
caption: PropTypes.string.isRequired,
visible: PropTypes.bool.isRequired
});
//Структура описания цвета задачи
const P8P_GANTT_TASK_COLOR_SHAPE = PropTypes.shape({
bgColor: PropTypes.string,
textColor: PropTypes.string,
bgProgressColor: PropTypes.string,
desc: PropTypes.string.isRequired
});
//Высота заголовка
const TITLE_HEIGHT = "44px";
//Высота панели масштабирования
const ZOOM_HEIGHT = "56px";
//----------------
//Интерфейс модуля
//----------------
export {
P8P_GANTT_ZOOM,
P8P_GANTT_ZOOM_VIEW_MODES,
P8P_GANTT_TASK_SHAPE,
P8P_GANTT_TASK_ATTRIBUTE_SHAPE,
P8P_GANTT_TASK_COLOR_SHAPE,
TITLE_HEIGHT,
ZOOM_HEIGHT
};

View File

@ -8,8 +8,8 @@
//---------------------
import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React
import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером
import { formatDateJSONDateOnly } from "../core/utils"; //Вспомогательные функции
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { formatDateJSONDateOnly } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы

View File

@ -0,0 +1,267 @@
/*
Парус 8 - Панели мониторинга - Диаграмма Ганта
Компонент: Редактор задачи
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogActions, DialogContent, TextField, Button, List, ListItem, ListItemText, Divider, Slider } from "@mui/material"; //Интерфейсные компоненты
import { P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE } from "./p8p_gantt_constants"; //Константы диаграммы Ганта
import { P8P_DIALOG_CONTENT_VARIANT } from "../../theme/variants/p8p_dialog_content_variants"; //Варианты диалогов (содержимое)
import { P8P_LIST_VARIANT } from "../../theme/variants/p8p_list_variants"; //Варианты списков
import { P8P_LIST_ITEM_TEXT_VARIANT } from "../../theme/variants/p8p_list_item_text_variants"; //Варианты значений списков
import { P8P_TEXT_FIELD_VARIANT } from "../../theme/variants/p8p_text_field_variants"; //Варианты полей ввода
import { P8P_BUTTON_VARIANT } from "../../theme/variants/p8p_button_variants"; //Варианты кнопок
//--------------------------------
//Вспомогательные классы и функции
//--------------------------------
//Проверка существования значения
const hasValue = value => typeof value !== "undefined" && value !== null && value !== "";
//Формирование описания для легенды
const taskLegendDesc = ({ task, taskColors }) => {
if (Array.isArray(taskColors) && taskColors.length > 0) {
const colorDesc = taskColors.find(
color => task.bgColor === color.bgColor && task.textColor === color.textColor && task.bgProgressColor === color.bgProgressColor
);
if (colorDesc)
return {
text: colorDesc.desc,
style: {
...(colorDesc.bgProgressColor
? {
background: `linear-gradient(to right, ${colorDesc.bgProgressColor} ,${
colorDesc.bgColor ? colorDesc.bgColor : "transparent"
})`
}
: colorDesc.bgColor
? { backgroundColor: colorDesc.bgColor }
: {}),
...(colorDesc.textColor ? { color: colorDesc.textColor } : {})
}
};
else return null;
} else return null;
};
//-----------
//Тело модуля
//-----------
//Редактор задачи
const P8PGanttTaskEditor = ({
task,
taskAttributes,
taskColors,
onOk,
onCancel,
taskAttributeRenderer,
taskDialogRenderer,
taskDialogProps,
numbCaption,
nameCaption,
startCaption,
endCaption,
progressCaption,
legendCaption,
okBtnCaption,
cancelBtnCaption
}) => {
//Собственное состояние
const [state, setState] = useState({
start: task.start,
end: task.end,
progress: task.progress
});
//Отображаемые атрибуты
const dispTaskAttributes =
Array.isArray(taskAttributes) && taskAttributes.length > 0 ? taskAttributes.filter(attr => attr.visible && hasValue(task[attr.name])) : [];
//При сохранении
const handleOk = () => (onOk && state.start && state.end ? onOk({ task, start: state.start, end: state.end, progress: state.progress }) : null);
//При отмене
const handleCancel = () => (onCancel ? onCancel() : null);
//При изменении сроков
const handlePeriodChanged = e => setState(prev => ({ ...prev, [e.target.name]: e.target.value }));
//При изменении прогресса
const handleProgressChanged = (e, newValue) => setState(prev => ({ ...prev, progress: newValue }));
//Описание легенды для задачи
const legendDesc = taskLegendDesc({ task, taskColors });
let legend = legendDesc ? (
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
secondaryTypographyProps={{
p: 1,
sx: legendDesc.style
}}
primary={legendCaption}
secondary={legendDesc.text}
/>
) : null;
//Генерация содержимого
return (
<Dialog open onClose={handleCancel} {...(taskDialogProps ? taskDialogProps : {})}>
{taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
) : (
<>
<DialogContent variant={P8P_DIALOG_CONTENT_VARIANT.TASK}>
<List variant={P8P_LIST_VARIANT.GANTT_TASK}>
<ListItem alignItems="flex-start">
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={numbCaption} secondary={task.numb} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={nameCaption} secondary={task.fullName} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
secondaryTypographyProps={{ component: "span" }}
primary={startCaption}
secondary={
<TextField
error={!state.start}
disabled={task.readOnly === true || task.readOnlyDates === true}
name="start"
fullWidth
required
InputLabelProps={{ shrink: true }}
type={"date"}
value={state.start}
onChange={handlePeriodChanged}
variant="standard"
data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY}
size="small"
margin="normal"
/>
}
/>
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
secondaryTypographyProps={{ component: "span" }}
primary={endCaption}
secondary={
<TextField
error={!state.end}
disabled={task.readOnly === true || task.readOnlyDates === true}
name="end"
fullWidth
required
InputLabelProps={{ shrink: true }}
type={"date"}
value={state.end}
onChange={handlePeriodChanged}
variant="standard"
data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY}
size="small"
margin="normal"
/>
}
/>
</ListItem>
{hasValue(task.progress) || legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
{hasValue(task.progress) ? (
<>
<ListItem alignItems="flex-start">
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
secondaryTypographyProps={{ component: "span" }}
primary={`${progressCaption}${
task.readOnly === true || task.readOnlyProgress === true ? ` (${task.progress}%)` : ""
}`}
secondary={
<Slider
disabled={task.readOnly === true || task.readOnlyProgress === true}
defaultValue={task.progress}
valueLabelDisplay="auto"
onChange={handleProgressChanged}
/>
}
/>
</ListItem>
{legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
</>
) : null}
{legend ? (
<>
<ListItem alignItems="flex-start">{legend}</ListItem>
{dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
</>
) : null}
{dispTaskAttributes.length > 0
? dispTaskAttributes.map((attr, i) => {
const defaultView = task[attr.name];
const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null;
return (
<React.Fragment key={i}>
<ListItem alignItems="flex-start">
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
primary={attr.caption}
secondaryTypographyProps={{ component: "span" }}
secondary={customView ? customView : defaultView}
/>
</ListItem>
{i < dispTaskAttributes.length - 1 ? <Divider component="li" /> : null}
</React.Fragment>
);
})
: null}
</List>
</DialogContent>
<DialogActions>
<Button variant={P8P_BUTTON_VARIANT.SECONDARY} onClick={handleCancel}>
{cancelBtnCaption}
</Button>
<Button variant={P8P_BUTTON_VARIANT.PRIMARY} disabled={!state.start || !state.end || task.readOnly} onClick={handleOk}>
{okBtnCaption}
</Button>
</DialogActions>
</>
)}
</Dialog>
);
};
//Контроль свойств - Редактор задачи
P8PGanttTaskEditor.propTypes = {
task: P8P_GANTT_TASK_SHAPE,
taskAttributes: PropTypes.arrayOf(P8P_GANTT_TASK_ATTRIBUTE_SHAPE),
taskColors: PropTypes.arrayOf(P8P_GANTT_TASK_COLOR_SHAPE),
onOk: PropTypes.func,
onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
numbCaption: PropTypes.string.isRequired,
nameCaption: PropTypes.string.isRequired,
startCaption: PropTypes.string.isRequired,
endCaption: PropTypes.string.isRequired,
progressCaption: PropTypes.string.isRequired,
legendCaption: PropTypes.string.isRequired,
okBtnCaption: PropTypes.string.isRequired,
cancelBtnCaption: PropTypes.string.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { P8PGanttTaskEditor, taskLegendDesc };

View File

@ -12,15 +12,18 @@ import PropTypes from "prop-types"; //Контроль свойств компо
import { IconButton, Icon, Typography, Paper, Stack } from "@mui/material"; //Интерфейсные компоненты MUI
import { P8PHintDialog } from "./p8p_app_message"; //Диалог подсказки
import { TEXTS, STATE } from "../../app.text"; //Типовые текстовые ресурсы и константы
import { APP_COLORS } from "../../app.styles"; //Типовые стили
import { useP8PIndicator } from "./p8p_indicator_hooks"; //Хук для индикатора
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
import { P8P_TYPOGRAPHY_CLICKABLE } from "../theme/styles/typography"; //Стили текстовых полей
import { P8P_PAPER_INDICATOR } from "../theme/styles/paper"; //Стили выделяемого контейнера
import { P8P_STACK_INLINE_HIDDEN } from "../theme/styles/stack"; //Стили для групповой информации
import { P8P_ICON_INDICATOR } from "../theme/styles/icon"; //Стили иконок
//---------
//Константы
//---------
//Варианты исполнения
const P8P_INDICATOR_VARIANT = {
ELEVATION: "elevation",
OUTLINED: "outlined"
@ -33,70 +36,11 @@ const P8P_INDICATOR_STATE = {
WARN: STATE.WARN,
ERR: STATE.ERR
};
//Цвета заливки
const BG_COLOR = {
[STATE.OK]: APP_COLORS[STATE.OK].color,
[STATE.ERR]: APP_COLORS[STATE.ERR].color,
[STATE.WARN]: APP_COLORS[STATE.WARN].color
};
//Цвета текста и иконок
const COLOR = {
[STATE.OK]: APP_COLORS[STATE.OK].contrColor,
[STATE.ERR]: APP_COLORS[STATE.ERR].contrColor,
[STATE.WARN]: APP_COLORS[STATE.WARN].contrColor
};
//Стили
const STYLES = {
CONTAINER: (state, clickable, userColor, userBackgroundColor) => ({
padding: "10px",
width: "100%",
height: "100%",
overflow: "hidden",
...getBackgroundColor(state, userBackgroundColor),
...getColor(state, userColor),
display: "flex",
flexDirection: "column",
justifyContent: "center",
...(clickable
? {
cursor: "pointer",
"&:hover": { filter: "brightness(0.92) !important" },
"&:active": { backgroundColor: APP_COLORS.ACTIVE.color }
}
: {})
}),
ICON: (state, userColor) => ({ fontSize: "50px", ...getColor(state, userColor) }),
HINT_ICON: (state, userColor) => ({ fontSize: "1rem", ...getColor(state, userColor) }),
VALUE_CAPTION_STACK: { containerType: "inline-size", width: "100%", overflow: "hidden" },
CAPTION_TYPOGRAPHY: clickable => ({
width: "99cqw",
...(clickable
? {
cursor: "pointer"
}
: {})
}),
VALUE_TYPOGRAPHY: clickable => ({
...(clickable
? {
cursor: "pointer"
}
: {})
})
};
//-----------------------
//Вспомогательные функции
//-----------------------
//Подбор цвета заливки
const getBackgroundColor = (state, userColor) =>
userColor ? { backgroundColor: userColor } : BG_COLOR[state] ? { backgroundColor: BG_COLOR[state] } : {};
//Подбор цвета текста
const getColor = (state, userColor) => (userColor ? { color: userColor } : COLOR[state] ? { color: COLOR[state] } : {});
//Ширина для элементов индикатора
const WIDTH_ICON_VALUE = "1rem"; //Иконка значения индикатора
const WIDTH_ICON = "50px"; //Иконка индикатора
const WIDTH_CAPTION = "99cqw"; //Заголовок индикатора
//-----------
//Тело модуля
@ -150,7 +94,7 @@ const P8PIndicator = ({
//Представление текста значения индикатора
const valueTextView = (
<Typography variant={"h4"} sx={STYLES.VALUE_TYPOGRAPHY(onValueClick ? true : false)} onClick={handleValueClick}>
<Typography variant={P8P_TYPOGRAPHY_VARIANT.H4} sx={onValueClick ? { ...P8P_TYPOGRAPHY_CLICKABLE } : {}} onClick={handleValueClick}>
{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}
</Typography>
);
@ -158,9 +102,10 @@ const P8PIndicator = ({
//Представление текста подписи индикатора
const captionView = (
<Typography
variant={P8P_TYPOGRAPHY_VARIANT.BODY3}
align={"left"}
noWrap={true}
sx={STYLES.CAPTION_TYPOGRAPHY(onCaptionClick ? true : false)}
sx={{ width: WIDTH_CAPTION, ...(onCaptionClick ? { ...P8P_TYPOGRAPHY_CLICKABLE } : {}) }}
title={caption}
onClick={handleCaptionClick}
>
@ -175,7 +120,7 @@ const P8PIndicator = ({
<Stack direction={"row"} alignItems={"start"}>
{valueTextView}
<IconButton onClick={handleHintClick}>
<Icon sx={STYLES.HINT_ICON(state, color)}>help_outline</Icon>
<Icon sx={P8P_ICON_INDICATOR({ state, fontSize: WIDTH_ICON_VALUE, color })}>help_outline</Icon>
</IconButton>
</Stack>
</>
@ -190,17 +135,17 @@ const P8PIndicator = ({
return (
<Paper
elevation={variant === P8P_INDICATOR_VARIANT.ELEVATION ? elevation : 0}
sx={STYLES.CONTAINER(state, clickable, color, backgroundColor)}
sx={P8P_PAPER_INDICATOR({ state, color, backgroundColor, clickable })}
square={square}
variant={variant}
onClick={handleClick}
>
<Stack direction={"row"} alignItems={"center"} justifyContent={"space-between"}>
<Stack direction={"column"} alignItems={"start"} pr={2} sx={STYLES.VALUE_CAPTION_STACK}>
<Stack direction={"column"} alignItems={"start"} pr={2} sx={P8P_STACK_INLINE_HIDDEN}>
{valueView}
{captionView}
</Stack>
{icon ? <Icon sx={STYLES.ICON(state, color)}>{icon}</Icon> : null}
{icon ? <Icon sx={P8P_ICON_INDICATOR({ state, fontSize: WIDTH_ICON, color })}>{icon}</Icon> : null}
</Stack>
</Paper>
);

View File

@ -10,6 +10,12 @@
import React, { useState, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Icon, Input, InputAdornment, FormControl, Select, InputLabel, MenuItem, IconButton, Autocomplete, TextField } from "@mui/material"; //Интерфейсные компоненты
import { P8P_AUTOCOMPLETE_VARIANT } from "../theme/variants/p8p_autocomplete_variants"; //Варианты полей выбора (Autocomplete)
import { P8P_INPUT_LABEL_VARIANT } from "../theme/variants/p8p_input_label_variants"; //Варианты меток выбора
import { P8P_TEXT_FIELD_VARIANT } from "../theme/variants/p8p_text_field_variants"; //Варианты полей ввода
import { P8P_SELECT_VARIANT } from "../theme/variants/p8p_select_variants"; //Варианты полей выбора (Select)
import { P8P_MENU_ITEM_VARIANT } from "../theme/variants/p8p_menu_item_variants"; //Варианты элементов меню
import { P8P_INPUT_VARIANT } from "../theme/variants/p8p_input_variants"; //Варианты полей ввода (Input)
//---------
//Константы
@ -58,7 +64,7 @@ const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSo
//Генерация содержимого
return (
<Box p={1}>
<Box p={1} minWidth="300px">
<FormControl variant={"standard"} fullWidth {...other}>
{list ? (
freeSolo ? (
@ -71,11 +77,15 @@ const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSo
onChange={(event, newValue) => handleChangeByName(name, newValue)}
onInputChange={(event, newInputValue) => handleChangeByName(name, newInputValue)}
options={list}
renderInput={params => <TextField {...params} label={label} name={name} variant={"standard"} />}
variant={P8P_AUTOCOMPLETE_VARIANT.PRIMARY}
renderInput={params => (
<TextField {...params} label={label} name={name} variant={"standard"} data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY} />
)}
/>
) : (
<>
<InputLabel id={`${name}Lable`} shrink>
<FormControl variant="standard" fullWidth>
<InputLabel id={`${name}Lable`} shrink data-variant={P8P_INPUT_LABEL_VARIANT.PRIMARY}>
{label}
</InputLabel>
<Select
@ -87,18 +97,29 @@ const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSo
onChange={handleChange}
disabled={disabled}
displayEmpty
data-variant={P8P_SELECT_VARIANT.PRIMARY}
>
{list.map((item, i) => (
<MenuItem key={i} value={[undefined, null].includes(item.value) ? "" : item.value}>
<MenuItem
key={i}
value={[undefined, null].includes(item.value) ? "" : item.value}
variant={P8P_MENU_ITEM_VARIANT.PRIMARY}
>
{item.name}
</MenuItem>
))}
</Select>
</FormControl>
</>
)
) : (
<>
<InputLabel {...(current.type == "date" ? { shrink: true } : {})} htmlFor={name}>
<FormControl variant="standard" fullWidth>
<InputLabel
{...(current.type == "date" ? { shrink: true } : {})}
htmlFor={name}
data-variant={P8P_INPUT_LABEL_VARIANT.PRIMARY}
>
{label}
</InputLabel>
<Input
@ -117,7 +138,9 @@ const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSo
{...(current.type ? { type: current.type } : {})}
onChange={handleChange}
disabled={disabled}
variant={P8P_INPUT_VARIANT.PRIMARY}
/>
</FormControl>
</>
)}
</FormControl>

View File

@ -27,6 +27,16 @@ import {
ListItemIcon,
ListItemText
} from "@mui/material"; //Интерфейсные компоненты
import { P8P_BOX_PANELS_MENU_CONTAINER } from "../theme/styles/box"; //Стили контейнеров
import { P8P_GRID_VARIANT } from "../theme/variants/p8p_grid_variants"; //Варианты сеток
import { P8P_CARD_VARIANT } from "../theme/variants/p8p_card_variants"; //Варианты карточек
import { P8P_COMPONENT_HEIGHT } from "../theme/styles/common"; //Стили - общие
import { P8P_ICON_VARIANT } from "../theme/variants/p8p_icon_variants"; //Варианты иконок
import { P8P_CARD_ACTIONS_VARIANT } from "../theme/variants/p8p_card_actions_variants"; //Варианты действий карточки
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
import { P8P_BUTTON_VARIANT } from "../theme/variants/p8p_button_variants"; //Варианты кнопок
import { P8P_LIST_ITEM_TEXT_VARIANT } from "../theme/variants/p8p_list_item_text_variants"; //Варианты значений списков
import { P8P_TYPOGRAPHY_PANEL_DESK } from "../theme/styles/typography"; //Стили текста
//---------
//Константы
@ -52,37 +62,6 @@ const P8P_PANELS_MENU_PANEL_SHAPE = PropTypes.shape({
url: PropTypes.string.isRequired
});
//Стили
const STYLES = {
GRID_CONTAINER: { display: "flex", justifyContent: "center", alignItems: "flex-start", minHeight: "100vh" },
GRID: { maxWidth: 1200, direction: "row", justifyContent: "left", alignItems: "stretch" },
GRID_PANEL_CARD: { maxWidth: 400, height: "100%", flexDirection: "column", display: "flex" },
GRID_PANEL_CARD_MEDIA: { height: 140 },
GRID_PANEL_CARD_CONTENT_TITLE: { alignItems: "flex-start" },
GRID_PANEL_CARD_CONTENT_TITLE_ICON: { paddingTop: "4px" },
GRID_PANEL_CARD_ACTIONS: { marginTop: "auto", display: "flex", justifyContent: "flex-end", alignItems: "flex-start" },
DESKTOP_GROUP_HEADER: { fontWeight: "bold", fontFamily: "tahoma, arial, verdana, sans-serif!important", fontSize: "13px!important" },
DESKTOP_ITEM_BUTTON: {
fontSize: "12px",
textTransform: "none",
"&:hover": { backgroundColor: "#c3e1ff" },
width: "150px",
height: "90px",
flexDirection: "column",
justifyContent: "flex-start"
},
DESKTOP_ITEM_ICON: { width: "48px", height: "48px", fontSize: "48px" },
DESKTOP_ITEM_CATION: {
display: "-webkit-box",
overflow: "hidden",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 2,
fontSize: "12px",
maxWidth: "140px",
lineHeight: "1.2"
}
};
//--------------------------------
//Вспомогательные классы и функции
//--------------------------------
@ -113,7 +92,7 @@ const getPanelsLinks = ({ variant, panels, selectedPanel, group, defaultGroupTyt
panelsLinks.push(
variant === P8P_PANELS_MENU_VARIANT.GRID ? (
<Grid item xs={12} sm={12} md={12} lg={12} xl={12} key={grp}>
<Typography variant="h5" color="secondary">
<Typography variant={P8P_TYPOGRAPHY_VARIANT.H5} color="P8PPurple">
{grp ? grp : defaultGroupTytle}
</Typography>
</Grid>
@ -121,9 +100,7 @@ const getPanelsLinks = ({ variant, panels, selectedPanel, group, defaultGroupTyt
<Divider key={grp} />
) : (
<Box pb={1} key={grp}>
<Typography variant="h7" sx={STYLES.DESKTOP_GROUP_HEADER}>
{grp ? grp : defaultGroupTytle}
</Typography>
<Typography variant={P8P_TYPOGRAPHY_VARIANT.DESKTOP_GROUP}>{grp ? grp : defaultGroupTytle}</Typography>
</Box>
)
);
@ -132,28 +109,30 @@ const getPanelsLinks = ({ variant, panels, selectedPanel, group, defaultGroupTyt
panelsLinks.push(
variant === P8P_PANELS_MENU_VARIANT.GRID ? (
<Grid item xs={12} sm={6} md={4} lg={4} xl={4} key={panel.name}>
<Card sx={STYLES.GRID_PANEL_CARD}>
<Card variant={P8P_CARD_VARIANT.PANEL_INFO}>
{panel.preview ? (
<CardMedia component="img" alt={panel.name} image={panel.preview} sx={STYLES.GRID_PANEL_CARD_MEDIA} />
<CardMedia component="img" alt={panel.name} image={panel.preview} sx={P8P_COMPONENT_HEIGHT({ height: 140 })} />
) : (
<CardMedia
component="img"
alt={panel.name}
image={"./img/default_preview.png"}
sx={STYLES.GRID_PANEL_CARD_MEDIA}
sx={P8P_COMPONENT_HEIGHT({ height: 140 })}
/>
)}
<CardContent>
<Stack gap={1} direction="row" sx={STYLES.GRID_PANEL_CARD_CONTENT_TITLE}>
{panel.icon ? <Icon sx={STYLES.GRID_PANEL_CARD_CONTENT_TITLE_ICON}>{panel.icon}</Icon> : null}
<Typography variant="h5">{panel.caption}</Typography>
<Stack gap={1} direction="row" alignItems="flex-start">
{panel.icon ? <Icon variant={P8P_ICON_VARIANT.PANEL_MENU_TITLE}>{panel.icon}</Icon> : null}
<Typography variant={P8P_TYPOGRAPHY_VARIANT.H6_LIGHT}>{panel.caption}</Typography>
</Stack>
<Typography variant="body2" color="text.secondary">
{panel.desc}
</Typography>
<Typography variant={P8P_TYPOGRAPHY_VARIANT.BODY3_LIGHT}>{panel.desc}</Typography>
</CardContent>
<CardActions sx={STYLES.GRID_PANEL_CARD_ACTIONS}>
<Button size="large" onClick={() => (onItemNavigate ? onItemNavigate(panel) : null)}>
<CardActions variant={P8P_CARD_ACTIONS_VARIANT.PANEL_CARD}>
<Button
size="large"
onClick={() => (onItemNavigate ? onItemNavigate(panel) : null)}
variant={P8P_BUTTON_VARIANT.TEXT}
>
{navigateCaption}
</Button>
</CardActions>
@ -168,7 +147,7 @@ const getPanelsLinks = ({ variant, panels, selectedPanel, group, defaultGroupTyt
<ListItemIcon>
<Icon>{panel.icon}</Icon>
</ListItemIcon>
<ListItemText primary={panel.caption} />
<ListItemText variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY} primary={panel.caption} />
</ListItemButton>
</ListItem>
) : (
@ -176,11 +155,11 @@ const getPanelsLinks = ({ variant, panels, selectedPanel, group, defaultGroupTyt
p={3}
key={panel.name}
onClick={() => (onItemNavigate ? onItemNavigate(panel) : null)}
sx={STYLES.DESKTOP_ITEM_BUTTON}
variant={P8P_BUTTON_VARIANT.DESKTOP_PANEL}
title={panel.caption}
>
<Icon sx={STYLES.DESKTOP_ITEM_ICON}>{panel.icon}</Icon>
<Typography sx={STYLES.DESKTOP_ITEM_CATION} variant="body1">
<Icon variant={P8P_ICON_VARIANT.DESKTOP_PANEL}>{panel.icon}</Icon>
<Typography sx={P8P_TYPOGRAPHY_PANEL_DESK} variant={P8P_TYPOGRAPHY_VARIANT.DESKTOP_CAPTION}>
{panel.caption}
</Typography>
</Button>
@ -216,12 +195,18 @@ P8PPanelsMenuDrawer.propTypes = {
//Меню панелей - грид
const P8PPanelsMenuGrid = ({ onItemNavigate, navigateCaption, panels = [], defaultGroupTytle } = {}) => {
//Формируем ссылки на панели
const panelsLinks = getPanelsLinks({ variant: P8P_PANELS_MENU_VARIANT.GRID, panels, defaultGroupTytle, navigateCaption, onItemNavigate });
const panelsLinks = getPanelsLinks({
variant: P8P_PANELS_MENU_VARIANT.GRID,
panels,
defaultGroupTytle,
navigateCaption,
onItemNavigate
});
//Генерация содержимого
return (
<Box sx={STYLES.GRID_CONTAINER}>
<Grid container spacing={2} p={2} sx={STYLES.GRID}>
<Box sx={P8P_BOX_PANELS_MENU_CONTAINER}>
<Grid container spacing={2} p={2} variant={P8P_GRID_VARIANT.PANELS_MENU}>
{panelsLinks}
</Grid>
</Box>

View File

@ -11,21 +11,12 @@ import React, { useState, useContext, useMemo } from "react"; //Классы Rea
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Stack, List, ListItem, ListItemButton, ListItemText, Typography, Box, Divider } from "@mui/material"; //Интерфейсные элементы
import { P8PDialog, P8P_DIALOG_WIDTH } from "./p8p_dialog"; //Типовой диалог
import { APP_STYLES } from "../../app.styles"; //Типовые стили
import { P8PSettingsList } from "./p8p_settings_list"; //Список параметров
import { ApplicationCtx } from "../context/application"; //Контекст приложения
import { deepCopyObject, hasValue } from "../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { display: "flex", flexDirection: "row", alignItems: "flex-start" },
BOX_PANELS: { width: "300px", height: "500px", overflow: "auto", ...APP_STYLES.SCROLL },
BOX_SETTINGS: { width: "520px", height: "500px", overflow: "auto", ...APP_STYLES.SCROLL }
};
import { P8P_BOX_SETTINGS_CONTAINER, P8P_BOX_SETTINGS_LIST, P8P_BOX_SETTINGS_PANELS } from "../theme/styles/box"; //Стили контейнеров
import { P8P_LIST_ITEM_TEXT_VARIANT } from "../theme/variants/p8p_list_item_text_variants"; //Варианты значений списков
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
//-----------
//Тело модуля
@ -73,7 +64,7 @@ const P8PSettingsDialog = ({ settings, panel = null, onOk, onClose }) => {
//Генерация содержимого
return (
<>
<Box sx={STYLES.BOX_PANELS}>
<Box sx={P8P_BOX_SETTINGS_PANELS}>
<List>
{Object.keys(panelSettings).map((panel, i) => {
//Считываем информацию о панели
@ -83,12 +74,13 @@ const P8PSettingsDialog = ({ settings, panel = null, onOk, onClose }) => {
<ListItem key={i}>
<ListItemButton onClick={() => handlePanelSelect(panel)} selected={panel === selectedPanel}>
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
primary={panelInfo.name}
secondaryTypographyProps={{ component: "div" }}
secondary={
<Stack direction={"row"} justifyContent={"space-between"} gap={2}>
<Stack direction="row" justifyContent="space-between" gap={2}>
<Typography
variant={"caption"}
variant={P8P_TYPOGRAPHY_VARIANT.CAPTION}
noWrap={true}
title={panelInfo.desc ? panelInfo.desc : "Описание отсутствует"}
>{`${panelInfo.desc ? panelInfo.desc : "Описание отсутствует"}`}</Typography>
@ -117,20 +109,22 @@ const P8PSettingsDialog = ({ settings, panel = null, onOk, onClose }) => {
okDisabled={Object.keys(panelSettings).length === 0}
>
{isSettingsExists ? (
<Box sx={STYLES.CONTAINER}>
<Box sx={P8P_BOX_SETTINGS_CONTAINER}>
{!hasValue(panel) ? panelsListRender() : null}
<Box sx={STYLES.BOX_SETTINGS}>
<Box sx={P8P_BOX_SETTINGS_LIST}>
{hasValue(selectedPanel) ? (
<P8PSettingsList settings={panelSettings[selectedPanel]} onSettingChange={handleSettingChange} />
) : (
<Typography align="center" variant="subtitle1">
<>
<Typography align="center" component="h6" variant={P8P_TYPOGRAPHY_VARIANT.SUBTITLE1}>
Выберите панель для отображения параметров
</Typography>
</>
)}
</Box>
</Box>
) : (
<Typography align="center" variant="subtitle1">
<Typography align="center" component="h6" variant={P8P_TYPOGRAPHY_VARIANT.SUBTITLE1}>
Отсутствуют доступные параметры
</Typography>
)}

View File

@ -14,17 +14,10 @@ import { P8PDialog } from "./p8p_dialog"; //Типовой диалог
import { deepCopyObject } from "../core/utils"; //Вспомогательные функции
import { ApplicationCtx } from "../context/application"; //Контекст приложения
import { P8P_DATA_TYPES } from "../core/data_types"; //Типы данных
//---------
//Константы
//---------
//Стили
const STYLES = {
LIST: { width: "510px", bgcolor: "background.paper", overflowY: "auto" },
TYPOGRAPHY_VALUE: { maxWidth: "200px" },
TEXT_FIELD_STR_VALUE: { minWidth: "400px" }
};
import { P8P_LIST_VARIANT } from "../theme/variants/p8p_list_variants"; //Варианты списков
import { P8P_COMPONENT_WIDTH } from "../theme/styles/common"; //Стили - общие
import { P8P_LIST_ITEM_TEXT_VARIANT } from "../theme/variants/p8p_list_item_text_variants"; //Варианты значений списков
import { P8P_TYPOGRAPHY_VARIANT } from "../theme/p8p_typography"; //Варианты шрифтов
//--------------------------------
//Вспомогательные классы и функции
@ -129,24 +122,25 @@ const P8PSettingsList = ({ settings, onSettingChange }) => {
//Формирование представления
return (
<>
<List sx={STYLES.LIST}>
<List variant={P8P_LIST_VARIANT.SETTINGS}>
{Object.keys(settings).map((setting, i) => {
return (
<ListItem key={i}>
<ListItemButton onClick={() => handleSettingClick(setting)}>
<ListItemText
variant={P8P_LIST_ITEM_TEXT_VARIANT.PRIMARY}
primary={settings[setting].name}
secondaryTypographyProps={{ component: "div" }}
secondary={
<Stack direction={"row"} justifyContent={"space-between"} gap={2}>
<Typography
variant={"caption"}
variant={P8P_TYPOGRAPHY_VARIANT.CAPTION}
noWrap={true}
title={settings[setting].desc ? settings[setting].desc : "Описание отсутствует"}
>{`${settings[setting].desc ? settings[setting].desc : "Описание отсутствует"}`}</Typography>
<Typography
variant={"caption"}
sx={STYLES.TYPOGRAPHY_VALUE}
variant={P8P_TYPOGRAPHY_VARIANT.CAPTION}
sx={P8P_COMPONENT_WIDTH({ maxWidth: "200px" })}
noWrap={true}
title={settings[setting].value}
>{`${settings[setting].value}`}</Typography>

View File

@ -10,17 +10,13 @@
import React, { useEffect, useRef, useState } from "react"; //Классы React
import { IconButton, Icon, Container, Grid } from "@mui/material"; //Интерфейсные элементы
import PropTypes from "prop-types"; //Контроль свойств компонента
import { P8P_BOX_CENTER } from "../theme/styles/box"; //Стили контейнеров
import { P8P_GRID_VARIANT } from "../theme/variants/p8p_grid_variants"; //Стили сеток
//---------
//Константы
//---------
//Стили
const STYLES = {
GRID_ITEM_CANVAS: { width: "100%", height: "100%" },
CONTROLS: { justifyContent: "center", alignItems: "center", display: "flex" }
};
//Структура элемента изображения
const P8P_SVG_ITEM_SHAPE = PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
@ -156,12 +152,12 @@ const P8PSVG = ({ data, items, onClick, onItemClick, canvasStyle, fillOpacity })
return (
<Container>
<Grid container direction="column" justifyContent="center" alignItems="center" spacing={0}>
<Grid item xs={12} sx={STYLES.GRID_ITEM_CANVAS}>
<Grid item xs={12} variant={P8P_GRID_VARIANT.SVG_CONTAINER}>
<div ref={svgContainerRef} style={{ ...(canvasStyle ? canvasStyle : {}) }}></div>
</Grid>
{state.imagesCount > 1 ? (
<Grid item xs={12}>
<div style={STYLES.CONTROLS}>
<div style={P8P_BOX_CENTER}>
<IconButton onClick={handlePrevClick}>
<Icon>arrow_left</Icon>
</IconButton>

View File

@ -20,475 +20,38 @@ import {
Paper,
IconButton,
Icon,
Menu,
MenuItem,
Divider,
Stack,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
TextField,
Chip,
Container,
Link
} from "@mui/material"; //Интерфейсные компоненты
import { useTheme } from "@mui/material/styles"; //Взаимодействие со стилями MUI
import { P8PAppInlineError, P8PHintDialog } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { P8P_TABLE_AT, HEADER_INITIAL_STATE, hasValue, p8pTableReducer } from "./p8p_table_reducer"; //Редьюсер состояния
import { P8P_DATA_TYPES } from "../core/data_types"; //Типы данных
//---------
//Константы
//---------
//Размеры отступов
const P8P_TABLE_SIZE = {
SMALL: "small",
MEDIUM: "medium"
};
//Типы данных
const P8P_TABLE_DATA_TYPE = {
STR: P8P_DATA_TYPES.STR,
NUMB: P8P_DATA_TYPES.NUMB,
DATE: P8P_DATA_TYPES.DATE
};
//Направления сортировки
const P8P_TABLE_COLUMN_ORDER_DIRECTIONS = {
ASC: "ASC",
DESC: "DESC"
};
//Действия панели инструментов столбца
const P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS = {
ORDER_TOGGLE: "ORDER_TOGGLE",
FILTER_TOGGLE: "FILTER_TOGGLE",
EXPAND_TOGGLE: "EXPAND_TOGGLE"
};
//Действия меню столбца
const P8P_TABLE_COLUMN_MENU_ACTIONS = {
ORDER_ASC: "ORDER_ASC",
ORDER_DESC: "ORDER_DESC",
FILTER: "FILTER"
};
//Структура элемента описания фильтра
const P8P_TABLE_FILTER_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
from: PropTypes.any,
to: PropTypes.any
});
//Структура элемента описания сортировки
const P8P_TABLE_ORDER_SHAPE = PropTypes.shape({
direction: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
});
//Размещение области страниц по вертикали
const P8P_TABLE_PAGINATOR_ALIGN = {
LEFT: "left",
RIGHT: "right",
CENTER: "center"
};
//Размещение области страниц по горизонтали
const P8P_TABLE_PAGINATOR_POSITION = {
TOP: "top",
BOTTOM: "bottom",
BOTH: "both"
};
//Высота кнопки догрузки данных
const P8P_TABLE_MORE_HEIGHT = "49px";
//Высота фильтров таблицы
const P8P_TABLE_FILTERS_HEIGHT = "48px";
//Стили
const STYLES = {
TABLE: {},
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,
paddingLeft: 0,
paddingRight: 0
},
TABLE_CELL_GROUP_HEADER: {
backgroundColor: "lightgray"
},
TABLE_CELL_GROUP_HEADER_STICKY: {
position: "sticky",
left: 0
},
TABLE_COLUMN_STACK: {
alignItems: "center"
},
TABLE_COLUMN_MENU_ITEM_ICON: {
paddingRight: "10px"
},
FILTER_CHIP: {
alignItems: "center"
},
PAGINATION: (pagesAlign, position) => ({
display: "flex",
justifyContent:
pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.LEFT
? "flex-start"
: pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.CENTER
? "space-around"
: "flex-end",
...(position === P8P_TABLE_PAGINATOR_POSITION.TOP ? { paddingBottom: "10px" } : { paddingTop: "10px" })
}),
MORE_BUTTON_CONTAINER: {
with: "100%",
textAlign: "center",
padding: "5px"
}
};
//--------------------------------
//Вспомогательные классы и функции
//--------------------------------
//Панель инструментов столбца (левая)
const P8PTableColumnToolBarLeft = ({ columnDef, onItemClick }) => {
//Кнопка развёртывания/свёртывания
let expButton = null;
if (columnDef.expandable)
expButton = (
<IconButton onClick={() => (onItemClick ? onItemClick(P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.EXPAND_TOGGLE, columnDef.name) : null)}>
<Icon>{columnDef.expanded ? "indeterminate_check_box" : "add_box"}</Icon>
</IconButton>
);
//Генерация содержимого
return <>{expButton}</>;
};
//Контроль свойств - Панель инструментов столбца (левая)
P8PTableColumnToolBarLeft.propTypes = {
columnDef: PropTypes.object.isRequired,
onItemClick: PropTypes.func
};
//Панель инструментов столбца (правая)
const P8PTableColumnToolBarRight = ({ columnDef, orders, filters, onItemClick }) => {
//Кнопка сортировки
const order = orders.find(o => o.name == columnDef.name);
let orderButton = null;
if (order)
orderButton = (
<IconButton onClick={() => (onItemClick ? onItemClick(P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.ORDER_TOGGLE, columnDef.name) : null)}>
<Icon>{order.direction === P8P_TABLE_COLUMN_ORDER_DIRECTIONS.ASC ? "arrow_upward" : "arrow_downward"}</Icon>
</IconButton>
);
//Кнопка фильтрации
const filter = filters.find(f => f.name == columnDef.name);
let filterButton = null;
if (hasValue(filter?.from) || hasValue(filter?.to))
filterButton = (
<IconButton onClick={() => (onItemClick ? onItemClick(P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.FILTER_TOGGLE, columnDef.name) : null)}>
<Icon>filter_alt</Icon>
</IconButton>
);
//Генерация содержимого
return (
<>
{orderButton}
{filterButton}
</>
);
};
//Контроль свойств - Панель инструментов столбца (правая)
P8PTableColumnToolBarRight.propTypes = {
columnDef: PropTypes.object.isRequired,
orders: PropTypes.array.isRequired,
filters: PropTypes.array.isRequired,
onItemClick: PropTypes.func
};
//Меню столбца
const P8PTableColumnMenu = ({ columnDef, orderAscItemCaption, orderDescItemCaption, filterItemCaption, onItemClick }) => {
//Собственное состояние
const [anchorEl, setAnchorEl] = useState(null);
//Флаг отображения
const open = Boolean(anchorEl);
//По нажатию на открытие меню
const handleMenuButtonClick = event => {
setAnchorEl(event.currentTarget);
};
//По нажатию на пункт меню
const handleMenuItemClick = (event, index, action, columnName) => {
if (onItemClick) onItemClick(action, columnName);
setAnchorEl(null);
};
//При закрытии меню
const handleMenuClose = () => {
setAnchorEl(null);
};
//Формирование списка элементов меню в зависимости от описания колонки таблицы
const menuItems = [];
if (columnDef.order === true) {
menuItems.push(
<MenuItem
key={"orderAsc"}
onClick={(event, index) => handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.ORDER_ASC, columnDef.name)}
>
<Icon sx={STYLES.TABLE_COLUMN_MENU_ITEM_ICON}>arrow_upward</Icon>
{orderAscItemCaption}
</MenuItem>
);
menuItems.push(
<MenuItem
key={"orderDesc"}
onClick={(event, index) => handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.ORDER_DESC, columnDef.name)}
>
<Icon sx={STYLES.TABLE_COLUMN_MENU_ITEM_ICON}>arrow_downward</Icon>
{orderDescItemCaption}
</MenuItem>
);
}
if (columnDef.filter === true) {
if (menuItems.length > 0) menuItems.push(<Divider key={"divider"} sx={{ my: 0.5 }} />);
menuItems.push(
<MenuItem
key={"filter"}
onClick={(event, index) => handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.FILTER, columnDef.name)}
>
<Icon sx={STYLES.TABLE_COLUMN_MENU_ITEM_ICON}>filter_alt</Icon>
{filterItemCaption}
</MenuItem>
);
}
//Генерация содержимого
return menuItems.length > 0 ? (
<>
<IconButton id={`${columnDef.name}_menu_button`} aria-haspopup="true" onClick={handleMenuButtonClick}>
<Icon>more_vert</Icon>
</IconButton>
<Menu id={`${columnDef.name}_menu`} anchorEl={anchorEl} open={open} onClose={handleMenuClose}>
{menuItems}
</Menu>
</>
) : null;
};
//Контроль свойств - Меню столбца
P8PTableColumnMenu.propTypes = {
columnDef: PropTypes.object.isRequired,
orderAscItemCaption: PropTypes.string.isRequired,
orderDescItemCaption: PropTypes.string.isRequired,
filterItemCaption: PropTypes.string.isRequired,
onItemClick: PropTypes.func
};
//Диалог фильтра
const P8PTableColumnFilterDialog = ({
columnDef,
from,
to,
valueCaption,
valueFromCaption,
valueToCaption,
okBtnCaption,
clearBtnCaption,
cancelBtnCaption,
valueFormatter,
onOk,
onClear,
onCancel
}) => {
//Собственное состояние - значения с-по
const [filterValues, setFilterValues] = useState({ from, to });
//Отработка воода значения в фильтр
const handleFilterTextFieldChanged = e => {
setFilterValues(prev => ({ ...prev, [e.target.name]: e.target.value }));
};
//Элементы ввода значений фильтра
let inputs = null;
if (Array.isArray(columnDef.values) && columnDef.values.length > 0) {
inputs = (
<TextField
name="from"
fullWidth
select
label={valueCaption}
variant="standard"
value={filterValues.from}
onChange={handleFilterTextFieldChanged}
>
{columnDef.values.map((v, i) => (
<MenuItem key={i} value={v}>
{valueFormatter ? valueFormatter({ value: v, columnDef }) : v}
</MenuItem>
))}
</TextField>
);
} else {
switch (columnDef.dataType) {
case P8P_TABLE_DATA_TYPE.STR: {
inputs = (
<TextField
name="from"
fullWidth
InputLabelProps={{ shrink: true }}
value={filterValues.from}
onChange={handleFilterTextFieldChanged}
label={valueCaption}
variant="standard"
/>
);
break;
}
case P8P_TABLE_DATA_TYPE.NUMB:
case P8P_TABLE_DATA_TYPE.DATE: {
inputs = (
<>
<TextField
name="from"
InputLabelProps={{ shrink: true }}
type={columnDef.dataType == P8P_TABLE_DATA_TYPE.NUMB ? "number" : "date"}
value={filterValues.from}
onChange={handleFilterTextFieldChanged}
label={valueFromCaption}
variant="standard"
/>
&nbsp;
<TextField
name="to"
InputLabelProps={{ shrink: true }}
type={columnDef.dataType == P8P_TABLE_DATA_TYPE.NUMB ? "number" : "date"}
value={filterValues.to}
onChange={handleFilterTextFieldChanged}
label={valueToCaption}
variant="standard"
/>
</>
);
break;
}
}
}
return (
<Dialog
open={true}
aria-labelledby="filter-dialog-title"
aria-describedby="filter-dialog-description"
onClose={() => (onCancel ? onCancel(columnDef.name) : null)}
>
<DialogTitle id="filter-dialog-title">{columnDef.caption}</DialogTitle>
<DialogContent>{inputs}</DialogContent>
<DialogActions>
<Button onClick={() => (onOk ? onOk(columnDef.name, filterValues.from, filterValues.to) : null)}>{okBtnCaption}</Button>
<Button onClick={() => (onClear ? onClear(columnDef.name) : null)} variant="secondary">
{clearBtnCaption}
</Button>
<Button onClick={() => (onCancel ? onCancel(columnDef.name) : null)}>{cancelBtnCaption}</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог фильтра
P8PTableColumnFilterDialog.propTypes = {
columnDef: PropTypes.object.isRequired,
from: PropTypes.any,
to: PropTypes.any,
valueCaption: PropTypes.string.isRequired,
valueFromCaption: PropTypes.string.isRequired,
valueToCaption: PropTypes.string.isRequired,
okBtnCaption: PropTypes.string.isRequired,
clearBtnCaption: PropTypes.string.isRequired,
cancelBtnCaption: PropTypes.string.isRequired,
valueFormatter: PropTypes.func,
onOk: PropTypes.func,
onClear: PropTypes.func,
onCancel: PropTypes.func
};
//Сводный фильтр
const P8PTableFiltersChips = ({ filters, columnsDef, valueFromCaption, valueToCaption, onFilterChipClick, onFilterChipDelete, valueFormatter }) => {
return (
<Stack direction="row" spacing={1} p={1}>
{filters.map((filter, i) => {
const columnDef = columnsDef.find(columnDef => columnDef.name == filter.name);
return (
<Chip
key={i}
label={
<Stack direction="row" sx={STYLES.FILTER_CHIP}>
<strong>{columnDef.caption}</strong>:&nbsp;
{hasValue(filter.from) && !columnDef.values && columnDef.dataType != P8P_TABLE_DATA_TYPE.STR
? `${valueFromCaption.toLowerCase()} `
: null}
{hasValue(filter.from) ? (valueFormatter ? valueFormatter({ value: filter.from, columnDef }) : filter.from) : null}
{hasValue(filter.to) && !columnDef.values && columnDef.dataType != P8P_TABLE_DATA_TYPE.STR
? ` ${valueToCaption.toLowerCase()} `
: null}
{hasValue(filter.to) ? (valueFormatter ? valueFormatter({ value: filter.to, columnDef }) : filter.to) : null}
</Stack>
}
variant="outlined"
onClick={() => (onFilterChipClick ? onFilterChipClick(columnDef.name) : null)}
onDelete={() => (onFilterChipDelete ? onFilterChipDelete(columnDef.name) : null)}
/>
);
})}
</Stack>
);
};
//Контроль свойств - Сводный фильтр
P8PTableFiltersChips.propTypes = {
filters: PropTypes.array.isRequired,
columnsDef: PropTypes.array.isRequired,
valueFromCaption: PropTypes.string.isRequired,
valueToCaption: PropTypes.string.isRequired,
onFilterChipClick: PropTypes.func,
onFilterChipDelete: PropTypes.func,
valueFormatter: PropTypes.func
};
import { P8PAppInlineError, P8PHintDialog } from "../p8p_app_message"; //Встраиваемое сообщение об ошибке
import { P8P_TABLE_AT, HEADER_INITIAL_STATE, p8pTableReducer } from "./p8p_table_reducer"; //Редьюсер состояния
import { P8PTableColumnToolBarLeft } from "./p8p_table_column_toolbar_left"; //Таблица - Панель инструментов столбца (левая)
import { P8PTableColumnToolBarRight } from "./p8p_table_column_toolbar_right"; //Таблица - Панель инструментов столбца (правая)
import { P8PTableColumnMenu } from "./p8p_table_column_menu"; //Таблица - Меню столбца
import { P8PTableColumnFilterDialog } from "./p8p_table_column_filter_dialog"; //Таблица - Диалог фильтра
import { P8PTableFiltersChips } from "./p8p_table_filters_chips"; //Таблица - Сводный фильтр
import {
P8P_TABLE_SIZE,
P8P_TABLE_DATA_TYPE,
P8P_TABLE_COLUMN_ORDER_DIRECTIONS,
P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS,
P8P_TABLE_COLUMN_MENU_ACTIONS,
P8P_TABLE_FILTER_SHAPE,
P8P_TABLE_ORDER_SHAPE,
P8P_TABLE_PAGINATOR_ALIGN,
P8P_TABLE_PAGINATOR_POSITION,
P8P_TABLE_MORE_HEIGHT,
P8P_TABLE_FILTERS_HEIGHT
} from "./p8p_table_constants"; //Константы таблицы
import { P8P_TABLE_VARIANT } from "../../theme/variants/p8p_table_variants"; //Варианты таблиц
import { P8P_TABLE_HEAD_VARIANT } from "../../theme/variants/p8p_table_head_variants"; //Варианты заголовков таблиц
import { P8P_TABLE_CELL_VARIANT } from "../../theme/variants/p8p_table_cell_variants"; //Варианты ячеек таблиц
import { P8P_TABLE_ROW_VARIANT } from "../../theme/variants/p8p_table_row_variants"; //Варианты строк таблиц
import { P8P_CONTAINER_VARIANT } from "../../theme/variants/p8p_container_variants"; //Варианты контейнеров
import { P8P_PAGINATION_VARIANT } from "../../theme/variants/p8p_pagination_variants"; //Варианты пагинаторов
import { P8P_TYPOGRAPHY_VARIANT } from "../../theme/p8p_typography"; //Варианты шрифтов
//-----------
//Тело модуля
@ -530,6 +93,7 @@ const P8PTable = ({
groupCellRender,
rowExpandRender,
valueFormatter,
headExpandCellStyle,
onOrderChanged,
onFilterChanged,
onPagesCountChanged,
@ -555,9 +119,6 @@ const P8PTable = ({
//Собственное состояние - колонка с отображаемой подсказкой
const [displayHintColumn, setDisplayHintColumn] = useState(null);
//Стили
const theme = useTheme();
//Описание фильтруемой колонки
const filterColumnDef = filterColumn ? columnsDef.find(columnDef => columnDef.name == filterColumn) || null : null;
@ -688,20 +249,18 @@ const P8PTable = ({
const renderGroupCell = group => {
let customRender = {};
if (groupCellRender) customRender = groupCellRender({ columnsDef: header.columnsDef, group }) || {};
return header.displayDataColumns.map((columnDef, i) => (
return header.displayDataColumns.map((columnDef, i) => {
return (
<TableCell
variant={P8P_TABLE_CELL_VARIANT.GROUP_HEADER}
data-variant-props={{ width: columnDef.width, fixed: i == 0 && fixedColumns }}
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 : {})
}}
sx={{ ...customRender.cellStyle }}
colSpan={expandable && rowExpandRender ? 2 : 1}
>
{i == 0 ? (
<Stack direction="row" sx={STYLES.TABLE_COLUMN_STACK}>
<Stack direction="row" alignItems="center">
{group.expandable ? (
<IconButton
onClick={() => {
@ -715,7 +274,8 @@ const P8PTable = ({
</Stack>
) : null}
</TableCell>
));
);
});
};
//Генерация области страниц
@ -731,7 +291,8 @@ const P8PTable = ({
<>
{pagesCount && pagesCount > 0 && isVisible ? (
<Pagination
sx={STYLES.PAGINATION(pagesAlign, position)}
variant={P8P_PAGINATION_VARIANT.TABLE_PAGINATION}
data-variant-props={{ pagesAlign, position }}
count={pagesCount}
defaultPage={1}
page={pageNumber}
@ -777,18 +338,22 @@ const P8PTable = ({
) : null}
{renderPagination(P8P_TABLE_PAGINATOR_POSITION.TOP)}
<TableContainer component={containerComponent ? containerComponent : Paper} {...(containerComponentProps ? containerComponentProps : {})}>
<Table stickyHeader={fixedHeader} sx={{ ...STYLES.TABLE, ...(tableStyle || {}) }} size={size || P8P_TABLE_SIZE.MEDIUM}>
<TableHead sx={fixedHeader ? STYLES.TABLE_HEAD_STICKY : {}}>
<Table
variant={P8P_TABLE_VARIANT.PRIMARY}
stickyHeader={fixedHeader}
sx={{ ...(tableStyle || {}) }}
size={size || P8P_TABLE_SIZE.MEDIUM}
>
<TableHead variant={fixedHeader ? P8P_TABLE_HEAD_VARIANT.STICKY : "primary"}>
{header.displayLevels.map((level, i) => (
<TableRow key={level}>
{expandable && rowExpandRender && i == 0 ? (
<TableCell
variant={P8P_TABLE_CELL_VARIANT.HEADER_EXPAND}
data-variant-props={{ fixed: fixedColumns }}
key="head-cell-expand-control"
align="center"
sx={{
...STYLES.TABLE_CELL_EXPAND_CONTROL,
...(fixedColumns ? STYLES.TABLE_HEAD_CELL_STICKY(theme, 0) : {})
}}
sx={{ ...headExpandCellStyle }}
rowSpan={header.displayLevelsColumns[level][0].rowSpan}
></TableCell>
) : null}
@ -797,11 +362,11 @@ const P8PTable = ({
if (headCellRender) customRender = headCellRender({ columnDef }) || {};
return (
<TableCell
variant={P8P_TABLE_CELL_VARIANT.HEADER_CELL}
data-variant-props={{ width: columnDef.width, fixed: columnDef.fixed, left: columnDef.fixedLeft }}
key={`head-cell-${j}`}
align={getAlignByDataType(columnDef)}
sx={{
...(columnDef.width ? { minWidth: columnDef.width, maxWidth: columnDef.width } : {}),
...(columnDef.fixed ? STYLES.TABLE_HEAD_CELL_STICKY(theme, columnDef.fixedLeft) : {}),
...customRender.cellStyle
}}
rowSpan={columnDef.rowSpan}
@ -811,7 +376,8 @@ const P8PTable = ({
<Stack
direction="row"
justifyContent={getJustifyContentByDataType(columnDef)}
sx={{ ...STYLES.TABLE_COLUMN_STACK, ...customRender.stackStyle }}
alignItems="center"
sx={{ ...customRender.stackStyle }}
{...customRender.stackProps}
>
<P8PTableColumnToolBarLeft columnDef={columnDef} onItemClick={handleToolBarItemClick} />
@ -820,7 +386,7 @@ const P8PTable = ({
) : columnDef.hint ? (
<Link
component="button"
variant="body2"
variant={P8P_TYPOGRAPHY_VARIANT.COLUMN}
align="left"
underline="always"
onClick={() => handleColumnShowHintClick(columnDef.name)}
@ -856,15 +422,13 @@ const P8PTable = ({
const rowsView = rows.map((row, i) =>
!group?.name || group?.name == row.groupName ? (
<React.Fragment key={`data-${i}`}>
<TableRow key={`data-row-${i}`} sx={STYLES.TABLE_ROW}>
<TableRow key={`data-row-${i}`} variant={P8P_TABLE_ROW_VARIANT.PRIMARY}>
{expandable && rowExpandRender ? (
<TableCell
variant={P8P_TABLE_CELL_VARIANT.EXPAND}
data-variant-props={{ fixed: fixedColumns }}
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>
@ -876,11 +440,15 @@ const P8PTable = ({
if (dataCellRender) customRender = dataCellRender({ row, columnDef }) || {};
return (
<TableCell
variant={P8P_TABLE_CELL_VARIANT.CELL}
data-variant-props={{
width: columnDef.width,
fixed: columnDef.fixed,
left: columnDef.fixedLeft
}}
key={`data-cell-${j}`}
align={getAlignByDataType(columnDef)}
sx={{
...(columnDef.width ? { minWidth: columnDef.width, maxWidth: columnDef.width } : {}),
...(columnDef.fixed ? STYLES.TABLE_CELL_STICKY(theme, columnDef.fixedLeft) : {}),
...customRender.cellStyle
}}
{...customRender.cellProps}
@ -897,10 +465,8 @@ const P8PTable = ({
{expandable && rowExpandRender && expanded[i] === true ? (
<TableRow key={`data-row-expand-${i}`}>
<TableCell
sx={{
...STYLES.TABLE_CELL_EXPAND_CONTAINER,
...(fixedColumns ? STYLES.TABLE_CELL_STICKY(theme, 0) : {})
}}
variant={P8P_TABLE_CELL_VARIANT.EXPAND_CONTAINER}
data-variant-props={{ fixed: fixedColumns }}
colSpan={fixedColumns ? header.displayFixedColumnsCount + 1 : header.displayDataColumnsCount}
>
{rowExpandRender({ columnsDef, row })}
@ -931,7 +497,7 @@ const P8PTable = ({
</TableContainer>
{renderPagination(P8P_TABLE_PAGINATOR_POSITION.BOTTOM)}
{morePages && (!pagesCount || pagesCount <= 0) ? (
<Container style={STYLES.MORE_BUTTON_CONTAINER}>
<Container variant={P8P_CONTAINER_VARIANT.TABLE_MORE_BUTTON}>
<Button fullWidth onClick={handleMorePagesBtnClick} {...(morePagesBtnProps ? morePagesBtnProps : {})}>
{morePagesBtnCaption}
</Button>
@ -998,6 +564,7 @@ P8PTable.propTypes = {
groupCellRender: PropTypes.func,
rowExpandRender: PropTypes.func,
valueFormatter: PropTypes.func,
headExpandCellStyle: PropTypes.object,
onOrderChanged: PropTypes.func,
onFilterChanged: PropTypes.func,
onPagesCountChanged: PropTypes.func,

View File

@ -0,0 +1,164 @@
/*
Парус 8 - Панели мониторинга - Таблица
Компонент: Диалог фильтра
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { MenuItem, Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Typography } from "@mui/material"; //Интерфейсные компоненты
import { P8P_TABLE_DATA_TYPE } from "./p8p_table_constants"; //Типы данных
import { P8P_TEXT_FIELD_VARIANT } from "../../theme/variants/p8p_text_field_variants"; //Варианты полей ввода
import { P8P_TYPOGRAPHY_VARIANT } from "../../theme/p8p_typography"; //Варианты шрифтов
import { P8P_BUTTON_VARIANT } from "../../theme/variants/p8p_button_variants"; //Варианты кнопок
import { P8P_MENU_ITEM_VARIANT } from "../../theme/variants/p8p_menu_item_variants"; //Варианты элементов меню
//-----------
//Тело модуля
//-----------
//Диалог фильтра
const P8PTableColumnFilterDialog = ({
columnDef,
from,
to,
valueCaption,
valueFromCaption,
valueToCaption,
okBtnCaption,
clearBtnCaption,
cancelBtnCaption,
valueFormatter,
onOk,
onClear,
onCancel
}) => {
//Собственное состояние - значения с-по
const [filterValues, setFilterValues] = useState({ from, to });
//Отработка воода значения в фильтр
const handleFilterTextFieldChanged = e => {
setFilterValues(prev => ({ ...prev, [e.target.name]: e.target.value }));
};
//Элементы ввода значений фильтра
let inputs = null;
if (Array.isArray(columnDef.values) && columnDef.values.length > 0) {
inputs = (
<TextField
name="from"
fullWidth
select
label={valueCaption}
variant="standard"
data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY}
value={filterValues.from}
onChange={handleFilterTextFieldChanged}
>
{columnDef.values.map((v, i) => (
<MenuItem key={i} value={v} variant={P8P_MENU_ITEM_VARIANT.PRIMARY}>
{valueFormatter ? valueFormatter({ value: v, columnDef }) : v}
</MenuItem>
))}
</TextField>
);
} else {
switch (columnDef.dataType) {
case P8P_TABLE_DATA_TYPE.STR: {
inputs = (
<TextField
name="from"
fullWidth
InputLabelProps={{ shrink: true }}
value={filterValues.from}
onChange={handleFilterTextFieldChanged}
label={valueCaption}
variant="standard"
data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY}
/>
);
break;
}
case P8P_TABLE_DATA_TYPE.NUMB:
case P8P_TABLE_DATA_TYPE.DATE: {
inputs = (
<>
<TextField
name="from"
InputLabelProps={{ shrink: true }}
type={columnDef.dataType == P8P_TABLE_DATA_TYPE.NUMB ? "number" : "date"}
value={filterValues.from}
onChange={handleFilterTextFieldChanged}
label={valueFromCaption}
variant="standard"
data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY}
/>
&nbsp;
<TextField
name="to"
InputLabelProps={{ shrink: true }}
type={columnDef.dataType == P8P_TABLE_DATA_TYPE.NUMB ? "number" : "date"}
value={filterValues.to}
onChange={handleFilterTextFieldChanged}
label={valueToCaption}
variant="standard"
data-variant={P8P_TEXT_FIELD_VARIANT.PRIMARY}
/>
</>
);
break;
}
}
}
return (
<Dialog
open={true}
aria-labelledby="filter-dialog-title"
aria-describedby="filter-dialog-description"
onClose={() => (onCancel ? onCancel(columnDef.name) : null)}
>
<DialogTitle id="filter-dialog-title">
<Typography variant={P8P_TYPOGRAPHY_VARIANT.H6}>{columnDef.caption}</Typography>
</DialogTitle>
<DialogContent>{inputs}</DialogContent>
<DialogActions>
<Button variant={P8P_BUTTON_VARIANT.OUTLINED} onClick={() => (onClear ? onClear(columnDef.name) : null)}>
{clearBtnCaption}
</Button>
<Button variant={P8P_BUTTON_VARIANT.SECONDARY} onClick={() => (onCancel ? onCancel(columnDef.name) : null)}>
{cancelBtnCaption}
</Button>
<Button variant={P8P_BUTTON_VARIANT.PRIMARY} onClick={() => (onOk ? onOk(columnDef.name, filterValues.from, filterValues.to) : null)}>
{okBtnCaption}
</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог фильтра
P8PTableColumnFilterDialog.propTypes = {
columnDef: PropTypes.object.isRequired,
from: PropTypes.any,
to: PropTypes.any,
valueCaption: PropTypes.string.isRequired,
valueFromCaption: PropTypes.string.isRequired,
valueToCaption: PropTypes.string.isRequired,
okBtnCaption: PropTypes.string.isRequired,
clearBtnCaption: PropTypes.string.isRequired,
cancelBtnCaption: PropTypes.string.isRequired,
valueFormatter: PropTypes.func,
onOk: PropTypes.func,
onClear: PropTypes.func,
onCancel: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { P8PTableColumnFilterDialog };

View File

@ -0,0 +1,106 @@
/*
Парус 8 - Панели мониторинга - Таблица
Компонент: Меню столбца
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { IconButton, Icon, Menu, MenuItem, Divider, Typography } from "@mui/material"; //Интерфейсные компоненты
import { P8P_TABLE_COLUMN_MENU_ACTIONS } from "./p8p_table_constants"; //Действия меню столбца
import { P8P_TYPOGRAPHY_VARIANT } from "../../theme/p8p_typography"; //Варианты шрифтов
import { P8P_ICON_VARIANT } from "../../theme/variants/p8p_icon_variants"; //Варианты иконок
//-----------
//Тело модуля
//-----------
//Меню столбца
const P8PTableColumnMenu = ({ columnDef, orderAscItemCaption, orderDescItemCaption, filterItemCaption, onItemClick }) => {
//Собственное состояние
const [anchorEl, setAnchorEl] = useState(null);
//Флаг отображения
const open = Boolean(anchorEl);
//По нажатию на открытие меню
const handleMenuButtonClick = event => {
setAnchorEl(event.currentTarget);
};
//По нажатию на пункт меню
const handleMenuItemClick = (event, index, action, columnName) => {
if (onItemClick) onItemClick(action, columnName);
setAnchorEl(null);
};
//При закрытии меню
const handleMenuClose = () => {
setAnchorEl(null);
};
//Формирование списка элементов меню в зависимости от описания колонки таблицы
const menuItems = [];
if (columnDef.order === true) {
menuItems.push(
<MenuItem
key={"orderAsc"}
onClick={(event, index) => handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.ORDER_ASC, columnDef.name)}
>
<Icon variant={P8P_ICON_VARIANT.TABLE_COLUMN_MENU}>arrow_upward</Icon>
<Typography variant={P8P_TYPOGRAPHY_VARIANT.BODY1}>{orderAscItemCaption}</Typography>
</MenuItem>
);
menuItems.push(
<MenuItem
key={"orderDesc"}
onClick={(event, index) => handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.ORDER_DESC, columnDef.name)}
>
<Icon variant={P8P_ICON_VARIANT.TABLE_COLUMN_MENU}>arrow_downward</Icon>
<Typography variant={P8P_TYPOGRAPHY_VARIANT.BODY1}>{orderDescItemCaption}</Typography>
</MenuItem>
);
}
if (columnDef.filter === true) {
if (menuItems.length > 0) menuItems.push(<Divider key={"divider"} sx={{ my: 0.5 }} />);
menuItems.push(
<MenuItem
key={"filter"}
onClick={(event, index) => handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.FILTER, columnDef.name)}
>
<Icon variant={P8P_ICON_VARIANT.TABLE_COLUMN_MENU}>filter_alt</Icon>
<Typography variant={P8P_TYPOGRAPHY_VARIANT.BODY1}>{filterItemCaption}</Typography>
</MenuItem>
);
}
//Генерация содержимого
return menuItems.length > 0 ? (
<>
<IconButton id={`${columnDef.name}_menu_button`} aria-haspopup="true" onClick={handleMenuButtonClick}>
<Icon>more_vert</Icon>
</IconButton>
<Menu id={`${columnDef.name}_menu`} anchorEl={anchorEl} open={open} onClose={handleMenuClose}>
{menuItems}
</Menu>
</>
) : null;
};
//Контроль свойств - Меню столбца
P8PTableColumnMenu.propTypes = {
columnDef: PropTypes.object.isRequired,
orderAscItemCaption: PropTypes.string.isRequired,
orderDescItemCaption: PropTypes.string.isRequired,
filterItemCaption: PropTypes.string.isRequired,
onItemClick: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { P8PTableColumnMenu };

View File

@ -0,0 +1,44 @@
/*
Парус 8 - Панели мониторинга - Таблица
Компонент: Панель инструментов столбца (левая)
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
import { P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS } from "./p8p_table_constants"; //Действия панели инструментов столбца
//-----------
//Тело модуля
//-----------
//Панель инструментов столбца (левая)
const P8PTableColumnToolBarLeft = ({ columnDef, onItemClick }) => {
//Кнопка развёртывания/свёртывания
let expButton = null;
if (columnDef.expandable)
expButton = (
<IconButton onClick={() => (onItemClick ? onItemClick(P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.EXPAND_TOGGLE, columnDef.name) : null)}>
<Icon>{columnDef.expanded ? "indeterminate_check_box" : "add_box"}</Icon>
</IconButton>
);
//Генерация содержимого
return <>{expButton}</>;
};
//Контроль свойств - Панель инструментов столбца (левая)
P8PTableColumnToolBarLeft.propTypes = {
columnDef: PropTypes.object.isRequired,
onItemClick: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { P8PTableColumnToolBarLeft };

View File

@ -0,0 +1,63 @@
/*
Парус 8 - Панели мониторинга - Таблица
Компонент: Панель инструментов столбца (правая)
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
import { hasValue } from "./p8p_table_reducer"; //Редьюсер состояния
import { P8P_TABLE_COLUMN_ORDER_DIRECTIONS, P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS } from "./p8p_table_constants"; //Константы таблицы
//-----------
//Тело модуля
//-----------
//Панель инструментов столбца (правая)
const P8PTableColumnToolBarRight = ({ columnDef, orders, filters, onItemClick }) => {
//Кнопка сортировки
const order = orders.find(o => o.name == columnDef.name);
let orderButton = null;
if (order)
orderButton = (
<IconButton onClick={() => (onItemClick ? onItemClick(P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.ORDER_TOGGLE, columnDef.name) : null)}>
<Icon>{order.direction === P8P_TABLE_COLUMN_ORDER_DIRECTIONS.ASC ? "arrow_upward" : "arrow_downward"}</Icon>
</IconButton>
);
//Кнопка фильтрации
const filter = filters.find(f => f.name == columnDef.name);
let filterButton = null;
if (hasValue(filter?.from) || hasValue(filter?.to))
filterButton = (
<IconButton onClick={() => (onItemClick ? onItemClick(P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.FILTER_TOGGLE, columnDef.name) : null)}>
<Icon>filter_alt</Icon>
</IconButton>
);
//Генерация содержимого
return (
<>
{orderButton}
{filterButton}
</>
);
};
//Контроль свойств - Панель инструментов столбца (правая)
P8PTableColumnToolBarRight.propTypes = {
columnDef: PropTypes.object.isRequired,
orders: PropTypes.array.isRequired,
filters: PropTypes.array.isRequired,
onItemClick: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { P8PTableColumnToolBarRight };

View File

@ -0,0 +1,99 @@
/*
Парус 8 - Панели мониторинга - Таблица
Компонент: Константы
*/
//---------------------
//Подключение библиотек
//---------------------
import PropTypes from "prop-types"; //Контроль свойств компонента
import { P8P_DATA_TYPES } from "../../core/data_types"; //Типы данных
//-----------
//Тело модуля
//-----------
//Размеры отступов
const P8P_TABLE_SIZE = {
SMALL: "small",
MEDIUM: "medium"
};
//Типы данных
const P8P_TABLE_DATA_TYPE = {
STR: P8P_DATA_TYPES.STR,
NUMB: P8P_DATA_TYPES.NUMB,
DATE: P8P_DATA_TYPES.DATE
};
//Направления сортировки
const P8P_TABLE_COLUMN_ORDER_DIRECTIONS = {
ASC: "ASC",
DESC: "DESC"
};
//Действия панели инструментов столбца
const P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS = {
ORDER_TOGGLE: "ORDER_TOGGLE",
FILTER_TOGGLE: "FILTER_TOGGLE",
EXPAND_TOGGLE: "EXPAND_TOGGLE"
};
//Действия меню столбца
const P8P_TABLE_COLUMN_MENU_ACTIONS = {
ORDER_ASC: "ORDER_ASC",
ORDER_DESC: "ORDER_DESC",
FILTER: "FILTER"
};
//Структура элемента описания фильтра
const P8P_TABLE_FILTER_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
from: PropTypes.any,
to: PropTypes.any
});
//Структура элемента описания сортировки
const P8P_TABLE_ORDER_SHAPE = PropTypes.shape({
direction: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
});
//Размещение области страниц по вертикали
const P8P_TABLE_PAGINATOR_ALIGN = {
LEFT: "left",
RIGHT: "right",
CENTER: "center"
};
//Размещение области страниц по горизонтали
const P8P_TABLE_PAGINATOR_POSITION = {
TOP: "top",
BOTTOM: "bottom",
BOTH: "both"
};
//Высота кнопки догрузки данных
const P8P_TABLE_MORE_HEIGHT = "49px";
//Высота фильтров таблицы
const P8P_TABLE_FILTERS_HEIGHT = "48px";
//----------------
//Интерфейс модуля
//----------------
export {
P8P_TABLE_SIZE,
P8P_TABLE_DATA_TYPE,
P8P_TABLE_COLUMN_ORDER_DIRECTIONS,
P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS,
P8P_TABLE_COLUMN_MENU_ACTIONS,
P8P_TABLE_FILTER_SHAPE,
P8P_TABLE_ORDER_SHAPE,
P8P_TABLE_PAGINATOR_ALIGN,
P8P_TABLE_PAGINATOR_POSITION,
P8P_TABLE_MORE_HEIGHT,
P8P_TABLE_FILTERS_HEIGHT
};

View File

@ -0,0 +1,74 @@
/*
Парус 8 - Панели мониторинга - Таблица
Компонент: Сводный фильтр
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Stack, Chip, Typography } from "@mui/material"; //Интерфейсные компоненты
import { hasValue } from "./p8p_table_reducer"; //Редьюсер состояния
import { P8P_TABLE_DATA_TYPE } from "./p8p_table_constants"; //Типы данных
import { P8P_TYPOGRAPHY_VARIANT } from "../../theme/p8p_typography"; //Варианты шрифтов
//-----------
//Тело модуля
//-----------
//Сводный фильтр
const P8PTableFiltersChips = ({ filters, columnsDef, valueFromCaption, valueToCaption, onFilterChipClick, onFilterChipDelete, valueFormatter }) => {
return (
<Stack direction="row" spacing={1} p={1}>
{filters.map((filter, i) => {
const columnDef = columnsDef.find(columnDef => columnDef.name == filter.name);
return (
<Chip
key={i}
label={
<Stack direction="row" alignItems="center">
<Typography variant={P8P_TYPOGRAPHY_VARIANT.BODY2_BOLD}>{columnDef.caption}:&nbsp;</Typography>
<Typography variant={P8P_TYPOGRAPHY_VARIANT.BODY2}>
{hasValue(filter.from) && !columnDef.values && columnDef.dataType != P8P_TABLE_DATA_TYPE.STR
? `${valueFromCaption.toLowerCase()} `
: null}
{hasValue(filter.from)
? valueFormatter
? valueFormatter({ value: filter.from, columnDef })
: filter.from
: null}
{hasValue(filter.to) && !columnDef.values && columnDef.dataType != P8P_TABLE_DATA_TYPE.STR
? ` ${valueToCaption.toLowerCase()} `
: null}
{hasValue(filter.to) ? (valueFormatter ? valueFormatter({ value: filter.to, columnDef }) : filter.to) : null}
</Typography>
</Stack>
}
variant="outlined"
onClick={() => (onFilterChipClick ? onFilterChipClick(columnDef.name) : null)}
onDelete={() => (onFilterChipDelete ? onFilterChipDelete(columnDef.name) : null)}
/>
);
})}
</Stack>
);
};
//Контроль свойств - Сводный фильтр
P8PTableFiltersChips.propTypes = {
filters: PropTypes.array.isRequired,
columnsDef: PropTypes.array.isRequired,
valueFromCaption: PropTypes.string.isRequired,
valueToCaption: PropTypes.string.isRequired,
onFilterChipClick: PropTypes.func,
onFilterChipDelete: PropTypes.func,
valueFormatter: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { P8PTableFiltersChips };

View File

@ -12,7 +12,7 @@ import { deepCopyObject } from "./core/utils"; //Вспомогательные
import { TITLES, BUTTONS, TEXTS, CAPTIONS } from "../app.text"; //Текстовые ресурсы и константы
import { P8PPanelsMenuGrid, P8P_PANELS_MENU_PANEL_SHAPE } from "./components/p8p_panels_menu"; //Меню панелей
import { P8PAppWorkspace } from "./components/p8p_app_workspace"; //Рабочее пространство
import { P8PTable, P8P_TABLE_DATA_TYPE, P8P_TABLE_SIZE, P8P_TABLE_FILTER_SHAPE } from "./components/p8p_table"; //Таблица данных
import { P8PTable, P8P_TABLE_DATA_TYPE, P8P_TABLE_SIZE, P8P_TABLE_FILTER_SHAPE } from "./components/p8p_table/p8p_table"; //Таблица данных
import { P8PDataGrid, P8P_DATA_GRID_DATA_TYPE, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE } from "./components/p8p_data_grid"; //Таблица данных
import { P8PGantt, P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE } from "./components/p8p_gantt"; //Диаграмма Ганта
import { P8PCyclogram } from "./components/p8p_cyclogram"; //Циклограмма

View File

@ -10,6 +10,7 @@
import React from "react"; //Классы React
import { MessagingContext } from "./context/messaging"; //Контекст сообщений
import { BackEndContext } from "./context/backend"; //Контекст взаимодействия с сервером
import { SettingsContext } from "./context/settings"; //Контекст взаимодействия с параметрами
import { ApplicationContext } from "./context/application"; //Контекст приложения
import { App } from "./app"; //Приложение
import { ERRORS, TITLES, TEXTS, BUTTONS } from "../app.text"; //Текстовые ресурсы и константы
@ -17,6 +18,9 @@ import { getDisplaySize, genGUID } from "./core/utils"; //Вспомогател
import config from "../app.config"; //Настройки приложения
import client from "./core/client"; //Клиент для взаимодействия с сервером
import { ThemeProvider } from "@mui/material/styles"; //Подключение темы
import { theme } from ".//theme/theme"; //Тема компонентов
//-----------
//Тело модуля
//-----------
@ -24,13 +28,17 @@ import client from "./core/client"; //Клиент для взаимодейст
//Обёртка для контекста
const Root = () => {
return (
<ThemeProvider theme={theme}>
<MessagingContext titles={TITLES} texts={TEXTS} buttons={BUTTONS}>
<BackEndContext client={client}>
<ApplicationContext errors={ERRORS} displaySizeGetter={getDisplaySize} guidGenerator={genGUID} config={config}>
<SettingsContext>
<App />
</SettingsContext>
</ApplicationContext>
</BackEndContext>
</MessagingContext>
</ThemeProvider>
);
};

View File

@ -0,0 +1,42 @@
/*
Парус 8 - Панели мониторинга
Дополнительные цвета: общие
*/
//---------------------
//Подключение библиотек
//---------------------
import { STATE } from "../../../app.text"; //Типовые текстовые ресурсы и константы
import { P8P_COLOR_GREEN } from "../colors/green"; //Дополнительные цвета: зеленый
import { P8P_COLOR_RED } from "../colors/red"; //Дополнительные цвета: красный
import { P8P_COLOR_ORANGE } from "../colors/orange"; //Дополнительные цвета: оранжевый
import { P8P_COLOR_GREY } from "./grey"; //Дополнительные цвета: серый
//----------------
//Интерфейс модуля
//----------------
//Цвет - белый
export const P8P_COLOR_WHITE = "#FFF";
//Цвет - черный
export const P8P_COLOR_BLACK = "#000000";
//Цвет состояния
export const P8P_COLOR_STATE = {
[STATE.UNDEFINED]: P8P_COLOR_GREY[900],
[STATE.INFO]: P8P_COLOR_BLACK,
[STATE.OK]: P8P_COLOR_GREEN[900],
[STATE.ERR]: P8P_COLOR_RED[900],
[STATE.WARN]: P8P_COLOR_ORANGE[900]
};
//Цвет заливки состояния
export const P8P_COLOR_STATE_BG = {
[STATE.UNDEFINED]: P8P_COLOR_GREY[200],
[STATE.INFO]: P8P_COLOR_WHITE,
[STATE.OK]: P8P_COLOR_GREEN[200],
[STATE.ERR]: P8P_COLOR_RED[200],
[STATE.WARN]: P8P_COLOR_ORANGE[200]
};

21
app/theme/colors/green.js Normal file
View File

@ -0,0 +1,21 @@
/*
Парус 8 - Панели мониторинга
Дополнительные цвета: зеленый
*/
//----------------
//Интерфейс модуля
//----------------
export const P8P_COLOR_GREEN = {
50: "#e8f5e9",
100: "#c8e6c9",
200: "#a5d6a7",
300: "#81c784",
400: "#66bb6a",
500: "#4caf50",
600: "#43a047",
700: "#388e3c",
800: "#2e7d32",
900: "#1b5e20"
};

21
app/theme/colors/grey.js Normal file
View File

@ -0,0 +1,21 @@
/*
Парус 8 - Панели мониторинга
Дополнительные цвета: серый
*/
//----------------
//Интерфейс модуля
//----------------
export const P8P_COLOR_GREY = {
50: "#fafafa",
100: "#f5f5f5",
200: "#eeeeee",
300: "#e0e0e0",
400: "#bdbdbd",
500: "#9e9e9e",
600: "#757575",
700: "#616161",
800: "#424242",
900: "#212121"
};

View File

@ -0,0 +1,21 @@
/*
Парус 8 - Панели мониторинга
Дополнительные цвета: оранжевый
*/
//----------------
//Интерфейс модуля
//----------------
export const P8P_COLOR_ORANGE = {
50: "#fff3e0",
100: "#ffe0b2",
200: "#ffcc80",
300: "#ffb74d",
400: "#ffa726",
500: "#ff9800",
600: "#fb8c00",
700: "#f57c00",
800: "#ef6c00",
900: "#e65100"
};

21
app/theme/colors/red.js Normal file
View File

@ -0,0 +1,21 @@
/*
Парус 8 - Панели мониторинга
Дополнительные цвета: красный
*/
//----------------
//Интерфейс модуля
//----------------
export const P8P_COLOR_RED = {
50: "#ffebee",
100: "#fecdd2",
200: "#ef9a9a",
300: "#e57373",
400: "#ef5350",
500: "#f44336",
600: "#e53935",
700: "#d32f2f",
800: "#c62828",
900: "#b71c1c"
};

View File

@ -0,0 +1,67 @@
/*
Парус 8 - Панели мониторинга
Кастомные компоненты
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_BUTTON_OVERRIDES } from "./variants/p8p_button_variants"; //Расширение Button
import { P8P_TABLE_OVERRIDES } from "./variants/p8p_table_variants"; //Расширение Table
import { P8P_TABLE_HEAD_OVERRIDES } from "./variants/p8p_table_head_variants"; //Расширение TableHead
import { P8P_TABLE_CELL_OVERRIDES } from "./variants/p8p_table_cell_variants"; //Расширение TableCell
import { P8P_TABLE_ROW_OVERRIDES } from "./variants/p8p_table_row_variants"; //Расширение TableRow
import { P8P_PAGINATION_OVERRIDES } from "./variants/p8p_pagination_variants"; //Расширение Pagination
import { P8P_CONTAINER_OVERRIDES } from "./variants/p8p_container_variants"; //Расширение Container
import { P8P_ICON_OVERRIDES } from "./variants/p8p_icon_variants"; //Расширение Icon
import { P8P_ICON_BUTTON_OVERRIDES } from "./variants/p8p_icon_button_variants"; //Расширение IconButton
import { P8P_TEXT_FIELD_OVERRIDES } from "./variants/p8p_text_field_variants"; //Расширение TextField
import { P8P_LIST_OVERRIDES } from "./variants/p8p_list_variants"; //Расширение List
import { P8P_LIST_ITEM_TEXT_OVERRIDES } from "./variants/p8p_list_item_text_variants"; //Расширение ListItemText
import { P8P_DIALOG_TITLE_OVERRIDES } from "./variants/p8p_dialog_title_variants"; //Расширение DialogTitle
import { P8P_DIALOG_CONTENT_OVERRIDES } from "./variants/p8p_dialog_content_variants"; //Расширение DialogContent
import { P8P_DIALOG_CONTENT_TEXT_OVERRIDES } from "./variants/p8p_dialog_content_text_variants"; //Расширение DialogContentText
import { P8P_DRAWER_OVERRIDES } from "./variants/p8p_drawer_variants"; //Расширение Drawer
import { P8P_APP_BAR_OVERRIDES } from "./variants/p8p_app_bar_variants"; //Расширение AppBar
import { P8P_GRID_OVERRIDES } from "./variants/p8p_grid_variants"; //Расширение Grid
import { P8P_CARD_OVERRIDES } from "./variants/p8p_card_variants"; //Расширение Card
import { P8P_CARD_ACTIONS_OVERRIDES } from "./variants/p8p_card_actions_variants"; //Расширение CardActions
import { P8P_SELECT_OVERRIDES } from "./variants/p8p_select_variants"; //Расширение Select
import { P8P_INPUT_OVERRIDES } from "./variants/p8p_input_variants"; //Расширение Input
import { P8P_INPUT_LABEL_OVERRIDES } from "./variants/p8p_input_label_variants"; //Расширение InputLable
import { P8P_MENU_ITEM_OVERRIDES } from "./variants/p8p_menu_item_variants"; //Расширение MenuItem
import { P8P_AUTOCOMPLETE_OVERRIDES } from "./variants/p8p_autocomplete_variants"; //Расширение Autocomplete
//----------------
//Интерфейс модуля
//----------------
//Кастомные компоненты
export const P8P_COMPONENTS = {
MuiButton: P8P_BUTTON_OVERRIDES,
MuiTable: P8P_TABLE_OVERRIDES,
MuiTableHead: P8P_TABLE_HEAD_OVERRIDES,
MuiTableRow: P8P_TABLE_ROW_OVERRIDES,
MuiTableCell: P8P_TABLE_CELL_OVERRIDES,
MuiPagination: P8P_PAGINATION_OVERRIDES,
MuiContainer: P8P_CONTAINER_OVERRIDES,
MuiIcon: P8P_ICON_OVERRIDES,
MuiIconButton: P8P_ICON_BUTTON_OVERRIDES,
MuiTextField: P8P_TEXT_FIELD_OVERRIDES,
MuiDialogTitle: P8P_DIALOG_TITLE_OVERRIDES,
MuiDialogContent: P8P_DIALOG_CONTENT_OVERRIDES,
MuiDialogContentText: P8P_DIALOG_CONTENT_TEXT_OVERRIDES,
MuiList: P8P_LIST_OVERRIDES,
MuiListItemText: P8P_LIST_ITEM_TEXT_OVERRIDES,
MuiDrawer: P8P_DRAWER_OVERRIDES,
MuiAppBar: P8P_APP_BAR_OVERRIDES,
MuiGrid: P8P_GRID_OVERRIDES,
MuiCard: P8P_CARD_OVERRIDES,
MuiCardActions: P8P_CARD_ACTIONS_OVERRIDES,
MuiSelect: P8P_SELECT_OVERRIDES,
MuiInputLabel: P8P_INPUT_LABEL_OVERRIDES,
MuiInput: P8P_INPUT_OVERRIDES,
MuiMenuItem: P8P_MENU_ITEM_OVERRIDES,
MuiAutocomplete: P8P_AUTOCOMPLETE_OVERRIDES
};

72
app/theme/p8p_palette.js Normal file
View File

@ -0,0 +1,72 @@
/*
Парус 8 - Панели мониторинга
Кастомная палитра
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомная палитра
export const P8P_PALETTE = {
P8PText: {
primary: "#2a3039de",
secondary: "#2a303999",
disabled: "#2a303961"
},
P8PPrimary: {
main: "#0F71BD",
dark: "#005EA6",
light: "#8DC2E2",
contrastText: "#FFF",
hover: "#0f71bd0a",
selected: "#0f71bd14",
focus: "#0f71bd1f",
focusVisible: "#0f71bd4d",
outlinedBorder: "#2a30391f"
},
P8PSecondary: {
main: "#37474F",
dark: "#2A3039",
light: "#607D8B",
contrastText: "#FFF",
outlinedBorder: "#2a303980"
},
P8PError: {
main: "#D32F2F",
dark: "#C62828",
light: "#EF5350"
},
P8PWarning: {
main: "#EF6C00",
dark: "#E65100",
light: "#FF9800"
},
P8PInfo: {
main: "#0F71BD",
dark: "#005EA6",
light: "#03A9F4"
},
P8PSuccess: {
main: "#2E7D32",
dark: "#1B5E20",
light: "#4CAF50"
},
P8PBackground: {
primary: "#FFF",
secondary: "#F8FAFD",
tableHeader: "#E6EAF3"
},
P8PAction: {
active: "#0000008a"
},
P8PCyclogram: {
group: "#e6eaf3",
task: "#cfd8dc"
},
P8PDesktop: {
main: "#1976d2",
hover: "#c3e1ff"
},
P8PPurple: "#5e35b1"
};

217
app/theme/p8p_typography.js Normal file
View File

@ -0,0 +1,217 @@
/*
Парус 8 - Панели мониторинга
Кастомные шрифты
*/
//---------
//Константы
//---------
//Шрифт "Montserrat"
const P8PFontMontserrat = {
fontFamily: "Montserrat",
fontStyle: "normal"
};
//----------------
//Интерфейс модуля
//----------------
//Кастомные шрифты
export const P8P_TYPOGRAPHY = {
P8PFontMontserrat: "Montserrat",
P8PH1: {
...P8PFontMontserrat,
fontSize: "96px",
fontWeight: "500",
lineHeight: "117%",
letterSpacing: "-1.5px"
},
P8PH2: {
...P8PFontMontserrat,
fontSize: "60px",
fontWeight: "500",
lineHeight: "120%",
letterSpacing: "-0.5px"
},
P8PH3: {
...P8PFontMontserrat,
fontSize: "48px",
fontWeight: "500",
lineHeight: "118%"
},
P8PH4: {
...P8PFontMontserrat,
fontSize: "32px",
fontWeight: "600",
lineHeight: "120%",
letterSpacing: "0.25px"
},
P8PH5: {
...P8PFontMontserrat,
fontSize: "24px",
fontWeight: "600",
lineHeight: "130%"
},
P8PH6: {
...P8PFontMontserrat,
fontSize: "20px",
fontWeight: "600",
lineHeight: "140%",
letterSpacing: "0.15px"
},
P8PH6Light: {
...P8PFontMontserrat,
fontSize: "20px",
fontWeight: "500",
lineHeight: "140%",
letterSpacing: "0.15px"
},
P8PH7: {
...P8PFontMontserrat,
fontSize: "18px",
fontWeight: "600",
lineHeight: "130%",
letterSpacing: "0.15px"
},
P8PSubtitle1: {
...P8PFontMontserrat,
fontSize: "16px",
fontWeight: "600",
lineHeight: "130%",
letterSpacing: "0.15px"
},
P8PSubtitle2: {
...P8PFontMontserrat,
fontSize: "14px",
fontWeight: "600",
lineHeight: "140%",
letterSpacing: "0.1px"
},
P8PBody1: {
...P8PFontMontserrat,
fontSize: "16px",
fontWeight: "500",
lineHeight: "150%",
letterSpacing: "0.15px"
},
P8PBody2: {
...P8PFontMontserrat,
fontSize: "12px",
fontWeight: "500",
lineHeight: "143%",
letterSpacing: "0.17px"
},
P8PBody2Light: {
...P8PFontMontserrat,
fontSize: "12px",
fontWeight: "400",
lineHeight: "143%",
letterSpacing: "0.17px"
},
P8PBody3: {
...P8PFontMontserrat,
fontSize: "14px",
fontWeight: "500",
lineHeight: "143%",
letterSpacing: "0.17px"
},
P8PBody3Light: {
...P8PFontMontserrat,
fontSize: "14px",
fontWeight: "400",
lineHeight: "143%",
letterSpacing: "0.17px"
},
P8PBody4: {
...P8PFontMontserrat,
fontSize: "15px",
fontWeight: "500",
lineHeight: "143%",
letterSpacing: "0.17px"
},
P8PCaption: {
...P8PFontMontserrat,
fontSize: "12px",
fontWeight: "500",
lineHeight: "166%",
letterSpacing: "0.4px"
},
P8POverline: {
...P8PFontMontserrat,
fontSize: "12px",
fontWeight: "400",
lineHeight: "266%",
letterSpacing: "1px"
},
P8PButton: {
...P8PFontMontserrat,
fontSize: "15px",
fontWeight: "600",
lineHeight: "26px",
letterSpacing: "0.46px"
},
P8PColumn: {
...P8PFontMontserrat,
fontSize: "12px",
fontWeight: "600",
lineHeight: "24px",
letterSpacing: "0.17px"
},
P8PInputLabel: {
...P8PFontMontserrat,
fontSize: "16px"
},
P8PBody2Bold: {
...P8PFontMontserrat,
fontSize: "12px",
fontWeight: "700",
lineHeight: "143%",
letterSpacing: "0.17px"
},
P8PTitle: {
...P8PFontMontserrat,
fontSize: "15px",
fontWeight: "500",
lineHeight: "140%",
letterSpacing: "0.1px"
},
P8PDesktopGroup: {
fontFamily: "tahoma, arial, verdana, sans-serif!important",
fontSize: "13px !important",
fontWeight: "bold"
},
P8PDesktopCaption: {
fontFamily: "tahoma, arial, verdana, sans-serif!important",
fontSize: "12px",
lineHeight: "1.2"
}
};
//Наименование кастомных шрифтов
export const P8P_TYPOGRAPHY_VARIANT = {
H1: "P8PH1",
H2: "P8PH2",
H3: "P8PH3",
H4: "P8PH4",
H5: "P8PH5",
H6: "P8PH6",
H6_LIGHT: "P8PH6Light",
H7: "P8PH7",
SUBTITLE1: "P8PSubtitle1",
SUBTITLE2: "P8PSubtitle2",
BODY1: "P8PBody1",
BODY2: "P8PBody2",
BODY2_LIGHT: "P8PBody2Light",
BODY3: "P8PBody3",
BODY3_LIGHT: "P8PBody3Light",
CAPTION: "P8PCaption",
OVERLINE: "P8POverline",
BUTTON: "P8PButton",
COLUMN: "P8PColumn",
INPUT_LABEL: "P8PInputLabel",
BODY2_BOLD: "P8PBody2Bold",
TITLE: "P8PTitle",
DESKTOP_GROUP: "P8PDesktopGroup",
DESKTOP_CAPTION: "P8PDesktopCaption"
};

152
app/theme/styles/box.js Normal file
View File

@ -0,0 +1,152 @@
/*
Парус 8 - Панели мониторинга
Вспомогательные стили Box
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_COMPONENT_WIDTH, P8P_COMPONENT_HEIGHT, P8P_SCROLL_AUTO } from "./common"; //Стили - общие
import { useTheme } from "@mui/material/styles"; //Хук темы приложения
//---------
//Общие стили контейнеров
//---------
//Контейнер плавающий
export const P8P_BOX_FLEX = { display: "flex" };
//Контейнер плавающий, элементы по центру, к началу
export const P8P_BOX_CENTER_START = {
display: "flex",
alignItems: "center",
justifyContent: "flex-start"
};
//Контейнер плавающий, элементы по центру, к концу
export const P8P_BOX_CENTER_END = {
display: "flex",
alignItems: "center",
justifyContent: "flex-end"
};
//Контейнер плавающий, элементы по центру, равномерно
export const P8P_BOX_CENTER_BETWEEN = {
display: "flex",
alignItems: "center",
justifyContent: "space-between"
};
//Контейнер плавающий, элементы по центру
export const P8P_BOX_CENTER = { display: "flex", alignItems: "center", justifyContent: "center" };
//---------
//Стили приложения
//---------
//Контейнер рабочего пространства
export const P8P_BOX_APP_WORKSPACE = {
...P8P_COMPONENT_WIDTH({ width: "100vw" }),
//width: "100vw",
...P8P_BOX_CENTER_BETWEEN
};
//Меню панелей - контейнер грида
export const P8P_BOX_PANELS_MENU_CONTAINER = {
...P8P_BOX_CENTER_START,
...P8P_COMPONENT_HEIGHT({ minHeight: "100vh" })
};
//---------
//Стили настроек панелей
//---------
//Список настроек панели
export const P8P_BOX_SETTINGS_LIST = {
...P8P_COMPONENT_WIDTH({ width: "520px" }),
...P8P_COMPONENT_HEIGHT({ height: "500px" }),
...P8P_SCROLL_AUTO
};
//Список панелей настроек панелей
export const P8P_BOX_SETTINGS_PANELS = {
...P8P_COMPONENT_WIDTH({ width: "300px" }),
...P8P_COMPONENT_HEIGHT({ height: "500px" }),
...P8P_SCROLL_AUTO
};
//Контейнер настроек панелей
export const P8P_BOX_SETTINGS_CONTAINER = {
display: "flex",
flexDirection: "row",
alignItems: "flex-start"
};
//---------
//Стили Ганта
//---------
//Контейнер диаграмы Ганта
export const P8P_BOX_GANTT = ({ noData, zoomBarHeight, titleHeight }) => ({
...P8P_COMPONENT_HEIGHT({ height: `calc(100% - ${zoomBarHeight ? zoomBarHeight : "0px"} - ${titleHeight ? titleHeight : "0px"})` }),
//height: `calc(100% - ${zoomBarHeight ? zoomBarHeight : "0px"} - ${titleHeight ? titleHeight : "0px"})`,
display: noData ? "none" : ""
});
//---------
//Стили циклограммы
//---------
//Контейнер циклограммы
export const P8P_BOX_CYCLOGRAM = ({ noData, zoomBarHeight, titleHeight }) => ({
position: "relative",
overflow: "auto",
padding: "0px 8px",
...P8P_COMPONENT_HEIGHT({ height: `calc(100% - ${zoomBarHeight ? zoomBarHeight : "0px"} - ${titleHeight ? titleHeight : "0px"})` }),
//height: `calc(100% - ${zoomBarHeight ? zoomBarHeight : "0px"} - ${titleHeight ? titleHeight : "0px"})`,
display: noData ? "none" : ""
});
//Контейнер строки циклограммы
export const P8P_BOX_CYCLOGRAM_ROW = ({ index }) => {
//Определение темы приложения
const theme = useTheme();
//Возвращаем стиль
return {
...(index % 2 === 0 ? { backgroundColor: theme.palette.P8PBackground.primary } : { backgroundColor: theme.palette.P8PBackground.secondary })
};
};
//Контейнер заголовка группы циклограммы
export const P8P_BOX_CYCLOGRAM_GROUP = ({ height }) => {
//Определение темы приложения
const theme = useTheme();
//Возвращаем стиль
return {
border: "1px solid",
backgroundColor: theme.palette.P8PCyclogram.group,
...P8P_BOX_CENTER,
height
};
};
//Контейнер задачи циклограммы
export const P8P_BOX_CYCLOGRAM_TASK = ({ lineHeight, bgColor, textColor, highlightColor }) => {
//Определение темы приложения
const theme = useTheme();
//Возвращаем стиль
return {
display: "flex",
alignItems: "center",
backgroundColor: bgColor ? bgColor : theme.palette.P8PCyclogram.task,
...(textColor ? { color: textColor } : {}),
height: lineHeight,
"&:hover": {
...(highlightColor
? { backgroundColor: `${highlightColor} !important`, filter: "brightness(1) !important" }
: { filter: `brightness(${bgColor ? "1.25" : "1.1"}) !important` }),
cursor: "pointer !important"
}
};
};

View File

@ -0,0 +1,58 @@
/*
Парус 8 - Панели мониторинга
Вспомогательные стили - общие
*/
//---------------------
//Подключение библиотек
//---------------------
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
//----------------
//Интерфейс модуля
//----------------
//Ширина компонента
export const P8P_COMPONENT_WIDTH = ({ width, minWidth, maxWidth }) => ({
...(width ? { width } : {}),
...(minWidth ? { minWidth } : {}),
...(maxWidth ? { maxWidth } : {})
});
//Высота компонента
export const P8P_COMPONENT_HEIGHT = ({ height, minHeight, maxHeight }) => ({
...(height ? { height } : {}),
...(minHeight ? { minHeight } : {}),
...(maxHeight ? { maxHeight } : {})
});
//Отображаемый скролл
export const P8P_SCROLL_AUTO = {
overflow: "auto",
...APP_STYLES.SCROLL
};
//Отсутствие отступов
export const P8P_COMPONENT_ZERO_PADDING = {
padding: "0px"
};
//Стили
export const P8P_SCROLL = {
"&::-webkit-scrollbar": {
height: "8px",
width: "8px"
},
"&::-webkit-scrollbar-track": {
borderRadius: "8px",
backgroundColor: "#EBEBEB"
},
"&::-webkit-scrollbar-thumb": {
borderRadius: "8px",
backgroundColor: "#b4b4b4"
},
"&::-webkit-scrollbar-thumb:hover": {
backgroundColor: "#808080"
}
};

32
app/theme/styles/icon.js Normal file
View File

@ -0,0 +1,32 @@
/*
Парус 8 - Панели мониторинга
Вспомогательные стили Icon
*/
//---------------------
//Подключение библиотек
//---------------------
import { STATE } from "../../../app.text"; //Типовые текстовые ресурсы и константы
import { P8P_COLOR_STATE } from "../colors/common"; //Дополнительные цвета - общие
//---------
//Константы
//---------
//Цвета текста и иконок индикатора
const P8P_INDICATOR_COLOR = {
[STATE.OK]: P8P_COLOR_STATE[STATE.OK],
[STATE.ERR]: P8P_COLOR_STATE[STATE.ERR],
[STATE.WARN]: P8P_COLOR_STATE[STATE.WARN]
};
//----------------
//Интерфейс модуля
//----------------
//Иконка индикатора
export const P8P_ICON_INDICATOR = ({ state, fontSize, color }) => ({
fontSize,
color: color || P8P_INDICATOR_COLOR[state]
});

13
app/theme/styles/main.js Normal file
View File

@ -0,0 +1,13 @@
/*
Парус 8 - Панели мониторинга
Вспомогательные стили main
*/
//----------------
//Интерфейс модуля
//----------------
//Содержимое рабочего пространства
export const P8P_MAIN_APP_WORKSPACE = {
flexGrow: 1
};

66
app/theme/styles/paper.js Normal file
View File

@ -0,0 +1,66 @@
/*
Парус 8 - Панели мониторинга
Вспомогательные стили Paper
*/
//---------------------
//Подключение библиотек
//---------------------
import { useTheme } from "@mui/material/styles"; //Хук темы приложения
import { STATE } from "../../../app.text"; //Типовые текстовые ресурсы и константы
import { P8P_COLOR_STATE, P8P_COLOR_STATE_BG } from "../colors/common"; //Дополнительные цвета - общие
import { P8P_COMPONENT_WIDTH, P8P_COMPONENT_HEIGHT } from "./common"; //Стили - общие
//---------
//Константы
//---------
//Цвета заливки индикатора
const P8P_INDICATOR_BG_COLOR = {
[STATE.OK]: P8P_COLOR_STATE_BG[STATE.OK],
[STATE.ERR]: P8P_COLOR_STATE_BG[STATE.ERR],
[STATE.WARN]: P8P_COLOR_STATE_BG[STATE.WARN]
};
//Цвета текста и иконок индикатора
const P8P_INDICATOR_COLOR = {
[STATE.OK]: P8P_COLOR_STATE[STATE.OK],
[STATE.ERR]: P8P_COLOR_STATE[STATE.ERR],
[STATE.WARN]: P8P_COLOR_STATE[STATE.WARN]
};
//-----------------------
//Вспомогательные функции
//-----------------------
//----------------
//Интерфейс модуля
//----------------
//Стиль для контейнера индикатора
export const P8P_PAPER_INDICATOR = ({ state, color, backgroundColor, clickable }) => {
//Определение темы приложения
const theme = useTheme();
//Возвращаем стиль
return {
padding: "10px",
...P8P_COMPONENT_WIDTH({ width: "100%" }),
...P8P_COMPONENT_HEIGHT({ height: "100%" }),
//width: "100%",
//height: "100%",
overflow: "hidden",
backgroundColor: backgroundColor || P8P_INDICATOR_BG_COLOR[state],
color: color || P8P_INDICATOR_COLOR[state],
display: "flex",
flexDirection: "column",
justifyContent: "center",
...(clickable
? {
cursor: "pointer",
"&:hover": { filter: "brightness(0.92) !important" },
"&:active": { backgroundColor: theme.palette.P8PAction.active }
}
: {})
};
};

22
app/theme/styles/stack.js Normal file
View File

@ -0,0 +1,22 @@
/*
Парус 8 - Панели мониторинга
Вспомогательные стили Stack
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_COMPONENT_WIDTH } from "./common"; //Стили - общие
//----------------
//Интерфейс модуля
//----------------
//Адаптивный с сокрытием
export const P8P_STACK_INLINE_HIDDEN = {
...P8P_COMPONENT_WIDTH({ width: "100%" }),
containerType: "inline-size",
//width: "100%",
overflow: "hidden"
};

View File

@ -0,0 +1,86 @@
/*
Парус 8 - Панели мониторинга
Вспомогательные стили Typography
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_COMPONENT_WIDTH, P8P_COMPONENT_HEIGHT } from "./common"; //Стили - общие
//----------------
//Интерфейс модуля
//----------------
//---------
//Общие стили текста
//---------
//Текст с возможностью нажатия
export const P8P_TYPOGRAPHY_CLICKABLE = {
cursor: "pointer"
};
//Заголовок полноэкранного диалога
export const P8P_TYPOGRAPHY_DIALOG_TITLE = {
marginLeft: "16px",
flex: 1
};
//Заголовок
export const P8P_TYPOGRAPHY_TITLE = {
...P8P_COMPONENT_HEIGHT({ height: "44px" })
//height: "44px"
};
//---------
//Стили приложения
//---------
//Описание панели на рабочем столе
export const P8P_TYPOGRAPHY_PANEL_DESK = {
display: "-webkit-box",
overflow: "hidden",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 2,
...P8P_COMPONENT_WIDTH({ maxWidth: "140px" })
//maxWidth: "140px"
};
//---------
//Стили циклограммы
//---------
//Колонка циклограммы
export const P8P_TYPOGRAPHY_CG_HEADER = {
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "pre",
textAlign: "center",
lineHeight: "35px",
padding: "0px 5px"
};
//Группа циклограммы
export const P8P_TYPOGRAPHY_CG_GROUP = ({ maxWidth, maxHeight }) => ({
maxWidth,
maxHeight,
textAlign: "center",
wordWrap: "break-word"
});
//Задача циклограммы
export const P8P_TYPOGRAPHY_CG_TASK = ({ maxHeight, availableLines }) => ({
maxHeight,
...P8P_COMPONENT_WIDTH({ width: "100%" }),
//width: "100%",
padding: "0px 5px",
overflowWrap: "break-word",
wordBreak: "break-all",
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: availableLines < 1 ? 1 : availableLines
});

24
app/theme/theme.js Normal file
View File

@ -0,0 +1,24 @@
/*
Парус 8 - Панели мониторинга
Стилистика компонентов
*/
//---------------------
//Подключение библиотек
//---------------------
import { createTheme } from "@mui/material/styles"; //Функция создания темы MUI
import { P8P_TYPOGRAPHY } from "./p8p_typography"; //Кастомные шрифты
import { P8P_PALETTE } from "./p8p_palette"; //Кастомная палитра
import { P8P_COMPONENTS } from "./p8p_components"; //Кастомные компоненты
//----------------
//Интерфейс модуля
//----------------
//Кастомная тема MUI
export const theme = createTheme({
palette: P8P_PALETTE,
typography: P8P_TYPOGRAPHY,
components: P8P_COMPONENTS
});

43
app/theme/utils.js Normal file
View File

@ -0,0 +1,43 @@
//---------------------
//Подключение библиотек
//---------------------
import { alpha } from "@mui/material/styles"; //Интерфейсные стили компонентов
//----------------
//Интерфейс модуля
//----------------
//Универсальное определение цвета кнопки
export const getButtonColorStyles = (color, theme, isOutlined = false) => {
//Определяем цвет
const colorObj = !color ? theme.palette.primary : theme.palette[color];
//Если цвет неопределен или нет разбивки на оттенки
if (!colorObj || typeof colorObj !== "object" || !colorObj.main) {
return {};
}
//Возвращаем результат
return {
backgroundColor: !isOutlined ? colorObj.main : null,
color: !isOutlined ? colorObj.contrastText || "#FFF" : colorObj.main,
...(isOutlined ? { border: `1px solid ${colorObj.main}` } : {}),
"&:hover": {
textDecoration: "none",
backgroundColor: !isOutlined ? colorObj.dark || colorObj.main : alpha(colorObj.dark, 0.04),
...(!isOutlined
? { boxShadow: "0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)" }
: {})
},
"&:active": {
backgroundColor: !isOutlined ? colorObj.light || colorObj.main : null,
...(!isOutlined
? { boxShadow: "0px 5px 5px -3px rgba(0, 0, 0, 0.2) 0px 8px 10px 1px rgba(0, 0, 0, 0.14) 0px 3px 14px 2px rgba(0, 0, 0, 0.12)" }
: {})
},
"&.Mui-disabled": {
boxShadow: "none",
backgroundColor: "rgba(0, 0, 0, 0.12)",
color: "rgba(0, 0, 0, 0.26)"
}
};
};

View File

@ -0,0 +1,33 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики AppBar
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные области заголовка
export const P8P_APP_BARS = {
primary: {},
P8PFixed: { position: "fixed" },
P8PRelative: { position: "relative" }
};
//Наименование кастомных областей заголовка
export const P8P_APP_BAR_VARIANT = {
FIXED: "P8PFixed",
RELATIVE: "P8PRelative"
};
//Кастомная стилистика компонента
export const P8P_APP_BAR_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_APP_BARS[variant];
}
}
};

View File

@ -0,0 +1,31 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Autocomplete
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные поля выбора
export const P8P_AUTOCOMPLETES = theme => ({
primary: {},
P8PPrimary: { ...theme.typography.P8PBody1 }
});
//Наименование кастомных полей выбора
export const P8P_AUTOCOMPLETE_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_AUTOCOMPLETE_OVERRIDES = {
styleOverrides: {
option: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_AUTOCOMPLETES(theme)[variant];
}
}
};

View File

@ -0,0 +1,106 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Button
*/
//---------------------
//Подключение библиотек
//---------------------
import { getButtonColorStyles } from "../utils"; //Дополнительные функции стилизации
import { P8P_COMPONENT_HEIGHT, P8P_COMPONENT_WIDTH } from "../styles/common"; //Стили - общие
//---------
//Константы
//---------
//Размеры кнопок
const P8P_BUTTON_SIZE = {
small: {
fontSize: "13px",
padding: "4px 22px"
},
medium: {
fontSize: "14px",
padding: "6px 22px"
},
large: {
fontSize: "15px",
padding: "8px 22px"
},
default: {
fontSize: "15px",
padding: "6px 22px"
}
};
//Общие стили
const defaultStyles = (theme, size) => ({
borderRadius: "4px",
...theme.typography.P8PButton,
...(size ? P8P_BUTTON_SIZE[size] : P8P_BUTTON_SIZE.default)
});
//----------------
//Интерфейс модуля
//----------------
//Кастомные кнопки
export const P8P_BUTTONS = ({ theme, color, size }) => ({
P8PPrimary: {
...defaultStyles(theme, size),
...getButtonColorStyles(color === "primary" ? "P8PInfo" : color, theme)
},
P8PSecondary: {
...defaultStyles(theme, size),
...getButtonColorStyles(color === "primary" ? "P8PSecondary" : color, theme)
},
P8POutlined: {
...defaultStyles(theme, size),
...getButtonColorStyles(color === "primary" ? "P8PSecondary" : color, theme, true)
},
P8PText: {
...theme.typography.P8PSubtitle2,
...getButtonColorStyles(color === "primary" ? "P8PInfo" : color, theme, true),
...(size ? P8P_BUTTON_SIZE[size] : P8P_BUTTON_SIZE.default),
border: "none"
},
P8PDesktopPanel: {
fontSize: "12px",
textTransform: "none",
...P8P_COMPONENT_WIDTH({ width: "150px" }),
...P8P_COMPONENT_HEIGHT({ width: "90px" }),
// width: "150px",
// height: "90px",
flexDirection: "column",
justifyContent: "flex-start",
color: theme.palette.P8PDesktop.main,
"&:hover": { backgroundColor: theme.palette.P8PDesktop.hover }
}
});
//Наименование кастомных кнопок
export const P8P_BUTTON_VARIANT = {
PRIMARY: "P8PPrimary",
SECONDARY: "P8PSecondary",
OUTLINED: "P8POutlined",
TEXT: "P8PText",
DESKTOP_PANEL: "P8PDesktopPanel"
};
//Кастомная стилистика компонента
export const P8P_BUTTON_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем цвет
const { color, size } = ownerState;
//Возвращаем варианты
return {
variants: Object.entries(P8P_BUTTONS({ theme, color, size })).map(([name, styles]) => ({
props: { variant: name },
style: styles
}))
};
}
}
};

View File

@ -0,0 +1,31 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики CardActions
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные действия карточки
export const P8P_CARD_ACTIONS = {
primary: {},
P8PPanelCard: { marginTop: "auto", display: "flex", justifyContent: "flex-end", alignItems: "flex-start" }
};
//Наименование кастомных действий карточек
export const P8P_CARD_ACTIONS_VARIANT = {
PANEL_CARD: "P8PPanelCard"
};
//Кастомная стилистика компонента
export const P8P_CARD_ACTIONS_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_CARD_ACTIONS[variant];
}
}
};

View File

@ -0,0 +1,44 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Card
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_COMPONENT_HEIGHT, P8P_COMPONENT_WIDTH } from "../styles/common"; //Стили - общие
//----------------
//Интерфейс модуля
//----------------
//Кастомные карточки
export const P8P_CARDS = {
primary: {},
P8PPanelInfo: {
...P8P_COMPONENT_WIDTH({ maxWidth: 400 }),
...P8P_COMPONENT_HEIGHT({ height: "100%" }),
//maxWidth: 400,
//height: "100%",
flexDirection: "column",
display: "flex"
}
};
//Наименование кастомных карточек
export const P8P_CARD_VARIANT = {
PANEL_INFO: "P8PPanelInfo"
};
//Кастомная стилистика компонента
export const P8P_CARD_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_CARDS[variant];
}
}
};

View File

@ -0,0 +1,48 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Container
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_COMPONENT_WIDTH } from "../styles/common"; //Стили - общие
//----------------
//Интерфейс модуля
//----------------
//Кастомные контейнеры
export const P8P_CONTAINERS = {
primary: {},
P8PTableMoreButton: {
...P8P_COMPONENT_WIDTH({ width: "100%" }),
//width: "100%",
textAlign: "center",
padding: "5px"
},
P8PInlineMessage: {
...P8P_COMPONENT_WIDTH({ width: "100%" }),
//width: "100%",
textAlign: "center"
}
};
//Наименование кастомных контейнеров
export const P8P_CONTAINER_VARIANT = {
TABLE_MORE_BUTTON: "P8PTableMoreButton",
INLINE_MSG: "P8PInlineMessage"
};
//Кастомная стилистика компонента
export const P8P_CONTAINER_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState?.variant || "primary";
//Возвращаем стили варианта
return P8P_CONTAINERS[variant];
}
}
};

View File

@ -0,0 +1,37 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики DialogContentText
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные диалоги (текстовое содержимое)
export const P8P_DIALOG_CONTENT_TEXTS = theme => ({
primary: {},
P8PPrimary: { ...theme.typography.P8PBody1 },
P8PInfo: { color: theme.palette.P8PText.primary, ...theme.typography.P8PBody1 },
P8PWarn: { color: theme.palette.P8PWarning.main, ...theme.typography.P8PBody1 },
P8PError: { color: theme.palette.P8PError.main, ...theme.typography.P8PBody1 }
});
//Наименование кастомных диалогов (текстовое содержимое)
export const P8P_DIALOG_CONTENT_TEXT_VARIANT = {
PRIMARY: "P8PPrimary",
INFO: "P8PInfo",
WARN: "P8PWarn",
ERROR: "P8PError"
};
//Кастомная стилистика компонента
export const P8P_DIALOG_CONTENT_TEXT_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_DIALOG_CONTENT_TEXTS(theme)[variant];
}
}
};

View File

@ -0,0 +1,48 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики DialogContent
*/
//---------------------
//Подключение библиотек
//---------------------
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { P8P_COMPONENT_WIDTH } from "../styles/common"; //Стили - общие
//----------------
//Интерфейс модуля
//----------------
//Кастомные диалоги (содержимое)
export const P8P_DIALOG_CONTENTS = theme => ({
primary: {},
P8PPrimary: { overflow: "auto", ...APP_STYLES.SCROLL },
P8PTask: {
...P8P_COMPONENT_WIDTH({ minWidth: 400 }),
//minWidth: 400,
overflowX: "auto"
},
P8PHint: { ...theme.typography.P8PBody4, color: theme.palette.P8PText.primary },
P8PHidden: { display: "flex", flexDirection: "column", overflow: "hidden" }
});
//Наименование кастомных диалогов (содержимое)
export const P8P_DIALOG_CONTENT_VARIANT = {
PRIMARY: "P8PPrimary",
TASK: "P8PTask",
HINT: "P8PHint",
HIDDEN: "P8PHidden"
};
//Кастомная стилистика компонента
export const P8P_DIALOG_CONTENT_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_DIALOG_CONTENTS(theme)[variant];
}
}
};

View File

@ -0,0 +1,37 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики DialogTitle
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные диалоги (заголовок)
export const P8P_DIALOG_TITLES = theme => ({
primary: {},
P8PPrimary: { ...theme.typography.P8PH6, color: theme.palette.P8PText.primary },
P8PInfo: { ...theme.typography.P8PH6, color: theme.palette.P8PText.primary },
P8PWarn: { ...theme.typography.P8PH6, color: theme.palette.P8PWarning.main },
P8PError: { ...theme.typography.P8PH6, color: theme.palette.P8PError.main }
});
//Наименование кастомных диалогов (заголовок)
export const P8P_DIALOG_TITLE_VARIANT = {
PRIMARY: "P8PPrimary",
INFO: "P8PInfo",
WARN: "P8PWarn",
ERROR: "P8PError"
};
//Кастомная стилистика компонента
export const P8P_DIALOG_TITLE_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_DIALOG_TITLES(theme)[variant];
}
}
};

View File

@ -0,0 +1,37 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Drawer
*/
//---------------------
//Подключение библиотек
//---------------------
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
//----------------
//Интерфейс модуля
//----------------
//Кастомные выезжающие области
export const P8P_DRAWERS = {
primary: {},
P8PPrimary: { [`& .MuiDrawer-paper`]: { ...APP_STYLES.SCROLL } }
};
//Наименование кастомных выезжающих областей
export const P8P_DRAWER_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_DRAWER_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState["data-variant"] || "primary";
//Возвращаем стили варианта
return P8P_DRAWERS[variant];
}
}
};

View File

@ -0,0 +1,52 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Grid
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_COMPONENT_HEIGHT, P8P_COMPONENT_WIDTH } from "../styles/common"; //Стили - общие
//----------------
//Интерфейс модуля
//----------------
//Кастомные сетки
export const P8P_GRIDS = {
primary: {},
P8PPrimary: {},
P8PSvgContainer: {
...P8P_COMPONENT_WIDTH({ width: "100%" }),
...P8P_COMPONENT_HEIGHT({ height: "100%" })
//width: "100%",
//height: "100%"
},
P8PPanelsMenu: {
...P8P_COMPONENT_WIDTH({ maxWidth: 1200 }),
//maxWidth: 1200,
direction: "row",
justifyContent: "left",
alignItems: "stretch"
}
};
//Наименование кастомных сеток
export const P8P_GRID_VARIANT = {
PRIMARY: "P8PPrimary",
SVG_CONTAINER: "P8PSvgContainer",
PANELS_MENU: "P8PPanelsMenu"
};
//Кастомная стилистика компонента
export const P8P_GRID_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_GRIDS[variant];
}
}
};

View File

@ -0,0 +1,33 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики IconButton
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные кнопки-иконки
export const P8P_ICON_BUTTONS = {
primary: {},
P8PAppBarButton: {
marginRight: "16px"
}
};
//Наименования кастомных кнопок-иконкок
export const P8P_ICON_BUTTON_VARIANT = {
APP_BAR_BUTTON: "P8PAppBarButton"
};
//Кастомная стилистика компонента
export const P8P_ICON_BUTTON_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState?.variant || "primary";
//Возвращаем стили варианта
return P8P_ICON_BUTTONS[variant];
}
}
};

View File

@ -0,0 +1,51 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Icon
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_COMPONENT_HEIGHT, P8P_COMPONENT_WIDTH } from "../styles/common"; //Стили - общие
//----------------
//Интерфейс модуля
//----------------
//Кастомные иконки
export const P8P_ICONS = {
primary: {},
P8PTableColumnMenu: {
marginRight: "10px"
},
P8PPanelMenuTitle: {
paddingTop: "4px"
},
P8PDesktopPanel: {
...P8P_COMPONENT_WIDTH({ width: "48px" }),
...P8P_COMPONENT_HEIGHT({ height: "48px" }),
// width: "48px",
// height: "48px",
fontSize: "48px"
}
};
//Наименование кастомных иконок
export const P8P_ICON_VARIANT = {
TABLE_COLUMN_MENU: "P8PTableColumnMenu",
PANEL_MENU_TITLE: "P8PPanelMenuTitle",
DESKTOP_PANEL: "P8PDesktopPanel"
};
//Кастомная стилистика компонента
export const P8P_ICON_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState?.variant || "primary";
//Возвращаем стили варианта
return P8P_ICONS[variant];
}
}
};

View File

@ -0,0 +1,31 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики InputLabel
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные метки ввода
export const P8P_INPUT_LABELS = theme => ({
primary: {},
P8PPrimary: { ...theme.typography.P8PInputLabel }
});
//Наименование кастомных меток ввода
export const P8P_INPUT_LABEL_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_INPUT_LABEL_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["data-variant"] || "primary";
//Возвращаем стили варианта
return P8P_INPUT_LABELS(theme)[variant];
}
}
};

View File

@ -0,0 +1,40 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Input
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные поля ввода
export const P8P_INPUTS = theme => ({
primary: {},
P8PPrimary: {
"& .MuiOutlinedInput-notchedOutline": {
"& legend": {
fontFamily: theme.typography.P8PFontMontserrat
}
},
"& .MuiInputBase-input": {
...theme.typography.P8PBody1
}
}
});
//Наименование кастомных полей ввода
export const P8P_INPUT_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_INPUT_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_INPUTS(theme)[variant];
}
}
};

View File

@ -0,0 +1,38 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики ListItemText
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные значения списков
export const P8P_LIST_ITEM_TEXTS = theme => ({
primary: {},
P8PPrimary: {
"& .MuiListItemText-primary": {
...theme.typography.P8PBody1
},
"& .MuiListItemText-secondary": {
...theme.typography.P8PBody3
}
}
});
//Наименование кастомных значений списков
export const P8P_LIST_ITEM_TEXT_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_LIST_ITEM_TEXT_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_LIST_ITEM_TEXTS(theme)[variant];
}
}
};

View File

@ -0,0 +1,46 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики List
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_COMPONENT_WIDTH } from "../styles/common"; //Стили - общие
//----------------
//Интерфейс модуля
//----------------
//Кастомные списки
export const P8P_LISTS = {
primary: {},
P8PGanttTask: {
...P8P_COMPONENT_WIDTH({ width: "100%", minWidth: 300, maxWidth: 700 })
//width: "100%", minWidth: 300, maxWidth: 700
},
P8PSettings: {
...P8P_COMPONENT_WIDTH({ width: "510px" }),
//width: "510px",
overflowY: "auto"
}
};
//Наименование кастомных списков
export const P8P_LIST_VARIANT = {
GANTT_TASK: "P8PGanttTask",
SETTINGS: "P8PSettings"
};
//Кастомная стилистика компонента
export const P8P_LIST_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_LISTS[variant];
}
}
};

View File

@ -0,0 +1,31 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики MenuItem
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные элементы меню
export const P8P_MENU_ITEMS = theme => ({
primary: {},
P8PPrimary: { ...theme.typography.P8PBody1 }
});
//Наименование кастомных элементов меню
export const P8P_MENU_ITEM_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_MENU_ITEM_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["variant"] || "primary";
//Возвращаем стили варианта
return P8P_MENU_ITEMS(theme)[variant];
}
}
};

View File

@ -0,0 +1,51 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Pagination
*/
//---------------------
//Подключение библиотек
//---------------------
import { P8P_TABLE_PAGINATOR_ALIGN, P8P_TABLE_PAGINATOR_POSITION } from "../../components/p8p_table/p8p_table_constants"; //Константы таблиц
//----------------
//Интерфейс модуля
//----------------
//Кастомные пагинаторы
export const P8P_PAGINATIONS = ({ theme, pagesAlign, position }) => ({
primary: {},
P8PTablePagination: {
display: "flex",
justifyContent:
pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.LEFT
? "flex-start"
: pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.CENTER
? "space-around"
: "flex-end",
...(position === P8P_TABLE_PAGINATOR_POSITION.TOP ? { paddingBottom: "10px" } : { paddingTop: "10px" }),
"& .MuiPaginationItem-root": {
...theme.typography.P8PBody2
}
}
});
//Наименование кастомных пагинаторов
export const P8P_PAGINATION_VARIANT = {
TABLE_PAGINATION: "P8PTablePagination"
};
//Кастомная стилистика компонента
export const P8P_PAGINATION_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState?.variant || "primary";
//Определяем доп. параметры
const { pagesAlign, position } = ownerState["data-variant-props"] || {};
//Возвращаем стили варианта
return P8P_PAGINATIONS({ theme, pagesAlign, position })[variant];
}
}
};

View File

@ -0,0 +1,38 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Select
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные поля выбора
export const P8P_SELECTS = theme => ({
primary: {},
P8PPrimary: {
"& .MuiOutlinedInput-notchedOutline": {
"& legend": {
fontFamily: theme.typography.P8PFontMontserrat
}
},
"& .MuiSelect-select": { ...theme.typography.P8PBody1 }
}
});
//Наименование кастомных полей выбора
export const P8P_SELECT_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_SELECT_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["data-variant"] || "primary";
//Возвращаем стили варианта
return P8P_SELECTS(theme)[variant];
}
}
};

View File

@ -0,0 +1,93 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики TableCell
*/
//---------
//Константы
//---------
//Общие стили
const STYLES = {
CELL_WIDTH: width => ({
...(width ? { minWidth: width, maxWidth: width } : {})
}),
CELL_DEF: (fixed, theme, left, isHeader = false) => ({
...(fixed
? {
position: "sticky",
left,
zIndex: isHeader ? 1000 : 500
}
: {})
})
};
//----------------
//Интерфейс модуля
//----------------
//Кастомные ячейки таблицы
export const P8P_TABLE_CELLS = ({ theme, width, fixed, left }) => ({
primary: {},
P8PGroupHeader: {
...theme.typography.P8PBody2,
...STYLES.CELL_WIDTH(width),
...(fixed ? { position: "sticky", left: 0 } : {})
},
P8PHeaderExpand: {
...theme.typography.P8PColumn,
...STYLES.CELL_WIDTH("60px"),
...STYLES.CELL_DEF(fixed, theme, 0, true),
backgroundColor: theme.palette.P8PBackground.tableHeader
},
P8PHeaderCell: {
...theme.typography.P8PColumn,
...STYLES.CELL_WIDTH(width),
...STYLES.CELL_DEF(fixed, theme, left, true),
backgroundColor: theme.palette.P8PBackground.tableHeader
},
P8PExpand: {
...theme.typography.P8PBody2,
...STYLES.CELL_WIDTH("60px"),
...STYLES.CELL_DEF(fixed, theme, 0),
backgroundColor: "inherit"
},
P8PCell: {
...theme.typography.P8PBody2,
...STYLES.CELL_WIDTH(width),
...STYLES.CELL_DEF(fixed, theme, left),
backgroundColor: "inherit"
},
P8PExpandContainer: {
paddingBottom: 0,
paddingTop: 0,
paddingLeft: 0,
paddingRight: 0,
...STYLES.CELL_DEF(fixed, theme, left)
}
});
//Наименование кастомных ячеек таблиц
export const P8P_TABLE_CELL_VARIANT = {
GROUP_HEADER: "P8PGroupHeader",
HEADER_EXPAND: "P8PHeaderExpand",
HEADER_CELL: "P8PHeaderCell",
EXPAND: "P8PExpand",
CELL: "P8PCell",
EXPAND_CONTAINER: "P8PExpandContainer"
};
//Кастомная стилистика компонента
export const P8P_TABLE_CELL_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState?.variant || "primary";
//Определяем доп. параметры
const { width, fixed, left } = ownerState["data-variant-props"] || {};
//Возвращаем стили варианта
return P8P_TABLE_CELLS({ theme, width, fixed, left })[variant];
}
}
};

View File

@ -0,0 +1,35 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики TableHead
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные заголовки таблицы
export const P8P_TABLE_HEADS = {
primary: {},
P8PSticky: {
position: "sticky",
top: 0,
zIndex: 1000
}
};
//Наименование кастомных заголовков таблиц
export const P8P_TABLE_HEAD_VARIANT = {
STICKY: "P8PSticky"
};
//Кастомная стилистика компонента
export const P8P_TABLE_HEAD_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState?.variant || "primary";
//Возвращаем стили варианта
return P8P_TABLE_HEADS[variant];
}
}
};

View File

@ -0,0 +1,33 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики TableRow
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные строки таблицы
export const P8P_TABLE_ROWS = {
primary: {},
P8PPrimary: {
"&:last-child td, &:last-child th": { border: 0 }
}
};
//Наименование кастомных строк таблиц
export const P8P_TABLE_ROW_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_TABLE_ROW_OVERRIDES = {
styleOverrides: {
root: ({ ownerState }) => {
//Определяем вариант
const variant = ownerState?.variant || "primary";
//Возвращаем стили варианта
return P8P_TABLE_ROWS[variant];
}
}
};

View File

@ -0,0 +1,38 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики Table
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные таблицы
export const P8P_TABLES = theme => ({
primary: {},
P8PPrimary: {
".MuiTableRow-root:nth-of-type(even)": {
backgroundColor: theme.palette.P8PBackground.primary
},
".MuiTableRow-root:nth-of-type(odd)": {
backgroundColor: theme.palette.P8PBackground.secondary
}
}
});
//Наименование кастомных таблиц
export const P8P_TABLE_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_TABLE_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState?.variant || "primary";
//Возвращаем стили варианта
return P8P_TABLES(theme)[variant];
}
}
};

View File

@ -0,0 +1,56 @@
/*
Парус 8 - Панели мониторинга
Расширение стилистики TextField
*/
//----------------
//Интерфейс модуля
//----------------
//Кастомные поля ввода
export const P8P_TEXT_FIELDS = theme => ({
primary: {},
P8PPrimary: {
"& .MuiInputLabel-root": {
...theme.typography.P8PInputLabel
},
"& .MuiOutlinedInput-notchedOutline": {
"& legend": {
fontFamily: theme.typography.P8PFontMontserrat
}
},
"& .MuiInputBase-input": {
...theme.typography.P8PBody1
}
}
});
//Кастомные списки выбора
export const P8P_TEXT_FIELD_OPTIONS = theme => ({
primary: {},
P8PPrimary: { ...theme.typography.P8PBody1 }
});
//Наименование кастомных полей ввода
export const P8P_TEXT_FIELD_VARIANT = {
PRIMARY: "P8PPrimary"
};
//Кастомная стилистика компонента
export const P8P_TEXT_FIELD_OVERRIDES = {
styleOverrides: {
root: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["data-variant"] || "primary";
//Возвращаем стили варианта
return P8P_TEXT_FIELDS(theme)[variant];
},
option: ({ ownerState, theme }) => {
//Определяем вариант
const variant = ownerState["data-variant"] || "primary";
console.log({ option: P8P_TEXT_FIELD_OPTIONS(theme)[variant] });
//Возвращаем стили варианта
return P8P_TEXT_FIELD_OPTIONS(theme)[variant];
}
}
};