Compare commits

..

No commits in common. "main" and "EqsPrfrm" have entirely different histories.

117 changed files with 8136 additions and 21037 deletions

1016
README.md

File diff suppressed because it is too large Load Diff

View File

@ -3,49 +3,10 @@
Типовые стили Типовые стили
*/ */
//---------------------
//Подключение библиотек
//---------------------
import { STATE } from "./app.text"; //Текстовые ресурсы и константы
import { red, green, orange, grey } from "@mui/material/colors";
//---------------- //----------------
//Интерфейс модуля //Интерфейс модуля
//---------------- //----------------
//Цвета
export const APP_COLORS = {
[STATE.UNDEFINED]: {
color: "#dcdcdca0",
contrColor: "black"
},
[STATE.INFO]: {
color: "white",
contrColor: "black"
},
[STATE.OK]: {
color: green[200],
contrColor: green[900]
},
[STATE.ERR]: {
color: red[200],
contrColor: red[900]
},
[STATE.WARN]: {
color: orange[200],
contrColor: orange[900]
},
HOVER: {
color: grey[200],
contrColor: grey[900]
},
ACTIVE: {
color: grey[400],
contrColor: grey[900]
}
};
//Стили //Стили
export const APP_STYLES = { export const APP_STYLES = {
SCROLL: { SCROLL: {

View File

@ -18,8 +18,7 @@ export const TITLES = {
//Текст //Текст
export const TEXTS = { export const TEXTS = {
LOADING: "Ожидайте...", //Ожидание завершения процесса LOADING: "Ожидайте...", //Ожидание завершения процесса
NO_DATA_FOUND: "Данных не найдено", //Отсутствие данных NO_DATA_FOUND: "Данных не найдено" //Отсутствие данных
NO_DATA_FOUND_SHORT: "Н.Д." //Отсутствие данных (кратко)
}; };
//Текст кнопок //Текст кнопок
@ -30,15 +29,11 @@ export const BUTTONS = {
OK: "ОК", //Ок OK: "ОК", //Ок
CANCEL: "Отмена", //Отмена CANCEL: "Отмена", //Отмена
CLOSE: "Закрыть", //Сокрытие CLOSE: "Закрыть", //Сокрытие
DETAIL: "Подробнее", //Отображение подробностей/детализации
HIDE: "Скрыть", //Скрытие информации
CLEAR: "Очистить", //Очистка CLEAR: "Очистить", //Очистка
ORDER_ASC: "По возрастанию", //Сортировка по возрастанию ORDER_ASC: "По возрастанию", //Сортировка по возрастанию
ORDER_DESC: "По убыванию", //Сортировка по убыванию ORDER_DESC: "По убыванию", //Сортировка по убыванию
FILTER: "Фильтр", //Фильтрация FILTER: "Фильтр", //Фильтрация
MORE: "Ещё", //Догрузка данных MORE: "Ещё" //Догрузка данных
APPLY: "Применить", //Сохранение без закрытия интерфейса ввода
SAVE: "Сохранить" //Сохранение
}; };
//Метки атрибутов, сопроводительные надписи //Метки атрибутов, сопроводительные надписи
@ -58,7 +53,6 @@ export const CAPTIONS = {
export const ERRORS = { export const ERRORS = {
UNDER_CONSTRUCTION: "Панель в разработке", UNDER_CONSTRUCTION: "Панель в разработке",
P8O_API_UNAVAILABLE: '"ПАРУС 8 Онлайн" недоступен', P8O_API_UNAVAILABLE: '"ПАРУС 8 Онлайн" недоступен',
P8O_API_UNSUPPORTED: 'Функция "ПАРУС 8 Онлайн" не поддерживается',
DEFAULT: "Неожиданная ошибка" DEFAULT: "Неожиданная ошибка"
}; };
@ -66,12 +60,3 @@ export const ERRORS = {
export const ERRORS_HTTP = { export const ERRORS_HTTP = {
404: "Адрес не найден" 404: "Адрес не найден"
}; };
//Типовые статусы
export const STATE = {
UNDEFINED: "UNDEFINED",
INFO: "INFORMATION",
OK: "OK",
ERR: "ERR",
WARN: "WARN"
};

View File

@ -86,9 +86,6 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
//Подключение к контексту навигации //Подключение к контексту навигации
const { navigateRoot, navigatePanel } = useContext(NavigationCtx); const { navigateRoot, navigatePanel } = useContext(NavigationCtx);
//Подключение к контексту приложения
const { appState } = useContext(ApplicationСtx);
//Отработка действия навигации домой //Отработка действия навигации домой
const handleHomeNavigate = () => navigateRoot(); const handleHomeNavigate = () => navigateRoot();
@ -101,7 +98,6 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
{...P8P_APP_WORKSPACE_CONFIG_PROPS} {...P8P_APP_WORKSPACE_CONFIG_PROPS}
panels={panels} panels={panels}
selectedPanel={selectedPanel} selectedPanel={selectedPanel}
caption={appState.appBarTitle}
onHomeNavigate={handleHomeNavigate} onHomeNavigate={handleHomeNavigate}
onItemNavigate={handleItemNavigate} onItemNavigate={handleItemNavigate}
> >

View File

@ -7,7 +7,7 @@
//Подключение библиотек //Подключение библиотек
//--------------------- //---------------------
import React, { useState } from "react"; //Классы React import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import Dialog from "@mui/material/Dialog"; //базовый класс диалога Material UI import Dialog from "@mui/material/Dialog"; //базовый класс диалога Material UI
import DialogTitle from "@mui/material/DialogTitle"; //Заголовок диалога import DialogTitle from "@mui/material/DialogTitle"; //Заголовок диалога
@ -18,8 +18,6 @@ import Typography from "@mui/material/Typography"; //Текст
import Button from "@mui/material/Button"; //Кнопки import Button from "@mui/material/Button"; //Кнопки
import Container from "@mui/material/Container"; //Контейнер import Container from "@mui/material/Container"; //Контейнер
import Box from "@mui/material/Box"; //Обёртка import Box from "@mui/material/Box"; //Обёртка
import { BUTTONS, STATE } from "../../app.text"; //Типовые текстовые ресурсы и константы
import { APP_COLORS } from "../../app.styles"; //Типовые стили
//--------- //---------
//Константы //Константы
@ -27,9 +25,9 @@ import { APP_COLORS } from "../../app.styles"; //Типовые стили
//Варианты исполнения //Варианты исполнения
const P8P_APP_MESSAGE_VARIANT = { const P8P_APP_MESSAGE_VARIANT = {
INFO: STATE.INFO, INFO: "information",
WARN: STATE.WARN, WARN: "warning",
ERR: STATE.ERR ERR: "error"
}; };
//Стили //Стили
@ -38,35 +36,28 @@ const STYLES = {
wordBreak: "break-word" wordBreak: "break-word"
}, },
INFO: { INFO: {
titleText: { titleText: {},
color: APP_COLORS[STATE.INFO].contrColor bodyText: {}
},
bodyText: {
color: APP_COLORS[STATE.INFO].contrColor
}
}, },
WARN: { WARN: {
titleText: { titleText: {
color: APP_COLORS[STATE.WARN].contrColor color: "orange"
}, },
bodyText: { bodyText: {
color: APP_COLORS[STATE.WARN].contrColor color: "orange"
} }
}, },
ERR: { ERR: {
titleText: { titleText: {
color: APP_COLORS[STATE.ERR].contrColor color: "red"
}, },
bodyText: { bodyText: {
color: APP_COLORS[STATE.ERR].contrColor color: "red"
} }
}, },
INLINE_MESSAGE: { INLINE_MESSAGE: {
with: "100%", with: "100%",
textAlign: "center" textAlign: "center"
},
FULL_ERROR_TEXT_BUTTON: {
color: APP_COLORS[STATE.WARN].contrColor
} }
}; };
@ -75,25 +66,7 @@ const STYLES = {
//----------- //-----------
//Сообщение //Сообщение
const P8PAppMessage = ({ const P8PAppMessage = ({ variant, title, titleText, cancelBtn, onCancel, cancelBtnCaption, okBtn, onOk, okBtnCaption, open, text }) => {
variant,
title,
titleText,
cancelBtn,
onCancel,
cancelBtnCaption,
okBtn,
onOk,
okBtnCaption,
open,
text,
fullErrorText,
showErrMoreCaption,
hideErrMoreCaption
}) => {
//Состояние подробной информации об ошибке
const [showFullErrorText, setShowFullErrorText] = useState(false);
//Подбор стиля и ресурсов //Подбор стиля и ресурсов
let style = STYLES.INFO; let style = STYLES.INFO;
switch (variant) { switch (variant) {
@ -113,7 +86,12 @@ const P8PAppMessage = ({
//Заголовок //Заголовок
let titlePart; let titlePart;
if (title && titleText) titlePart = <DialogTitle style={{ ...style.DEFAULT, ...style.titleText }}>{titleText}</DialogTitle>; if (title && titleText)
titlePart = (
<DialogTitle id="message-dialog-title" style={{ ...style.DEFAULT, ...style.titleText }}>
{titleText}
</DialogTitle>
);
//Кнопка Отмена //Кнопка Отмена
let cancelBtnPart; let cancelBtnPart;
@ -124,26 +102,16 @@ const P8PAppMessage = ({
let okBtnPart; let okBtnPart;
if (okBtn && okBtnCaption) if (okBtn && okBtnCaption)
okBtnPart = ( okBtnPart = (
<Button onClick={() => (onOk ? onOk() : null)} autoFocus> <Button onClick={() => (onOk ? onOk() : null)} color="primary" autoFocus>
{okBtnCaption} {okBtnCaption}
</Button> </Button>
); );
//Кнопка Подробнее
let fullErrorTextBtn;
if (fullErrorText && showErrMoreCaption && hideErrMoreCaption && variant === P8P_APP_MESSAGE_VARIANT.ERR)
fullErrorTextBtn = (
<Button onClick={() => setShowFullErrorText(!showFullErrorText)} sx={STYLES.FULL_ERROR_TEXT_BUTTON} autoFocus>
{!showFullErrorText ? showErrMoreCaption : hideErrMoreCaption}
</Button>
);
//Все действия //Все действия
let actionsPart; let actionsPart;
if (cancelBtnPart || okBtnPart) if (cancelBtnPart || okBtnPart)
actionsPart = ( actionsPart = (
<DialogActions> <DialogActions>
{fullErrorTextBtn}
{okBtnPart} {okBtnPart}
{cancelBtnPart} {cancelBtnPart}
</DialogActions> </DialogActions>
@ -151,10 +119,17 @@ const P8PAppMessage = ({
//Генерация содержимого //Генерация содержимого
return ( return (
<Dialog open={open || false} onClose={() => (onCancel ? onCancel() : null)}> <Dialog
open={open || false}
aria-labelledby="message-dialog-title"
aria-describedby="message-dialog-description"
onClose={() => (onCancel ? onCancel() : null)}
>
{titlePart} {titlePart}
<DialogContent> <DialogContent>
<DialogContentText style={style.bodyText}>{!showFullErrorText ? text : fullErrorText}</DialogContentText> <DialogContentText id="message-dialog-description" style={style.bodyText}>
{text}
</DialogContentText>
</DialogContent> </DialogContent>
{actionsPart} {actionsPart}
</Dialog> </Dialog>
@ -173,10 +148,7 @@ P8PAppMessage.propTypes = {
onOk: PropTypes.func, onOk: PropTypes.func,
okBtnCaption: PropTypes.string, okBtnCaption: PropTypes.string,
open: PropTypes.bool, open: PropTypes.bool,
text: PropTypes.string, text: PropTypes.string
fullErrorText: PropTypes.string,
showErrMoreCaption: PropTypes.string,
hideErrMoreCaption: PropTypes.string
}; };
//Встроенное сообщение //Встроенное сообщение
@ -186,19 +158,13 @@ const P8PAppInlineMessage = ({ variant, text, okBtn, onOk, okBtnCaption }) => {
<Container style={STYLES.INLINE_MESSAGE}> <Container style={STYLES.INLINE_MESSAGE}>
<Box p={1}> <Box p={1}>
<Typography <Typography
color={ color={variant === P8P_APP_MESSAGE_VARIANT.ERR ? "error" : variant === P8P_APP_MESSAGE_VARIANT.WARN ? "primary" : "textSecondary"}
variant === P8P_APP_MESSAGE_VARIANT.ERR
? APP_COLORS[STATE.ERR].contrColor
: variant === P8P_APP_MESSAGE_VARIANT.WARN
? APP_COLORS[STATE.WARN].contrColor
: APP_COLORS[STATE.INFO].contrColor
}
> >
{text} {text}
</Typography> </Typography>
{okBtn && okBtnCaption ? ( {okBtn && okBtnCaption ? (
<Box pt={1}> <Box pt={1}>
<Button onClick={() => (onOk ? onOk() : null)} autoFocus> <Button onClick={() => (onOk ? onOk() : null)} color="primary" autoFocus>
{okBtnCaption} {okBtnCaption}
</Button> </Button>
</Box> </Box>
@ -250,28 +216,6 @@ const P8PAppInlineWarn = props => buildVariantInlineMessage(props, P8P_APP_MESSA
//Встраиваемое сообщение информации //Встраиваемое сообщение информации
const P8PAppInlineInfo = props => buildVariantInlineMessage(props, P8P_APP_MESSAGE_VARIANT.INFO); const P8PAppInlineInfo = props => buildVariantInlineMessage(props, P8P_APP_MESSAGE_VARIANT.INFO);
//Диалог подсказки
const P8PHintDialog = ({ title, hint, onOk }) => {
return (
<Dialog open={true} onClose={() => (onOk ? onOk() : null)}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<div dangerouslySetInnerHTML={{ __html: hint }}></div>
</DialogContent>
<DialogActions>
<Button onClick={() => (onOk ? onOk() : null)}>{BUTTONS.OK}</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог подсказки
P8PHintDialog.propTypes = {
title: PropTypes.string.isRequired,
hint: PropTypes.string.isRequired,
onOk: PropTypes.func
};
//---------------- //----------------
//Интерфейс модуля //Интерфейс модуля
//---------------- //----------------
@ -285,6 +229,5 @@ export {
P8PAppInlineMessage, P8PAppInlineMessage,
P8PAppInlineError, P8PAppInlineError,
P8PAppInlineWarn, P8PAppInlineWarn,
P8PAppInlineInfo, P8PAppInlineInfo
P8PHintDialog
}; };

View File

@ -23,8 +23,7 @@ import {
ListItemIcon, ListItemIcon,
ListItemText ListItemText
} from "@mui/material"; //Интерфейсные компоненты } from "@mui/material"; //Интерфейсные компоненты
import { P8PPanelsMenuDrawer, P8P_PANELS_MENU_PANEL_SHAPE } from "./p8p_panels_menu"; //Меню import { P8PPanelsMenuDrawer, P8P_PANELS_MENU_PANEL_SHAPE } from "./p8p_panels_menu";
import { APP_STYLES } from "../../app.styles"; //Типовые стили
//--------- //---------
//Константы //Константы
@ -35,7 +34,6 @@ const APP_BAR_HEIGHT = "64px";
//Стили //Стили
const STYLES = { const STYLES = {
DRAWER: { [`& .MuiDrawer-paper`]: { ...APP_STYLES.SCROLL } },
ROOT_BOX: { display: "flex" }, ROOT_BOX: { display: "flex" },
APP_BAR: { position: "fixed" }, APP_BAR: { position: "fixed" },
APP_BAR_BUTTON: { mr: 2 }, APP_BAR_BUTTON: { mr: 2 },
@ -47,7 +45,7 @@ const STYLES = {
//----------- //-----------
//Рабочее пространство //Рабочее пространство
const P8PAppWorkspace = ({ children, panels = [], selectedPanel, caption, closeCaption, homeCaption, onHomeNavigate, onItemNavigate } = {}) => { const P8PAppWorkspace = ({ children, panels = [], selectedPanel, closeCaption, homeCaption, onHomeNavigate, onItemNavigate } = {}) => {
//Собственное состояния //Собственное состояния
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -86,11 +84,11 @@ const P8PAppWorkspace = ({ children, panels = [], selectedPanel, caption, closeC
<Icon>{open ? "chevron_left" : "menu"}</Icon> <Icon>{open ? "chevron_left" : "menu"}</Icon>
</IconButton> </IconButton>
<Typography variant="h6" noWrap component="div"> <Typography variant="h6" noWrap component="div">
{caption || selectedPanel?.caption} {selectedPanel?.caption}
</Typography> </Typography>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Drawer anchor="left" open={open} onClose={handleDrawerClose} sx={STYLES.DRAWER}> <Drawer anchor="left" open={open} onClose={handleDrawerClose}>
<List> <List>
<ListItemButton onClick={handleDrawerClose}> <ListItemButton onClick={handleDrawerClose}>
<ListItemIcon> <ListItemIcon>
@ -120,7 +118,6 @@ P8PAppWorkspace.propTypes = {
children: PropTypes.element, children: PropTypes.element,
panels: PropTypes.arrayOf(P8P_PANELS_MENU_PANEL_SHAPE).isRequired, panels: PropTypes.arrayOf(P8P_PANELS_MENU_PANEL_SHAPE).isRequired,
selectedPanel: P8P_PANELS_MENU_PANEL_SHAPE, selectedPanel: P8P_PANELS_MENU_PANEL_SHAPE,
caption: PropTypes.string,
closeCaption: PropTypes.string.isRequired, closeCaption: PropTypes.string.isRequired,
homeCaption: PropTypes.string.isRequired, homeCaption: PropTypes.string.isRequired,
onHomeNavigate: PropTypes.func, onHomeNavigate: PropTypes.func,

View File

@ -7,7 +7,7 @@
//Подключение библиотек //Подключение библиотек
//--------------------- //---------------------
import React, { useCallback, useEffect, useRef } from "react"; //Классы React import React, { useEffect, useRef } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import Chart from "chart.js/auto"; //Диаграммы и графики import Chart from "chart.js/auto"; //Диаграммы и графики
@ -37,26 +37,23 @@ const P8P_CHART_DATASET_SHAPE = PropTypes.shape({
//----------- //-----------
//График //График
const P8PChart = ({ type, title, legendPosition, options = {}, labels = [], datasets = [], onClick, style }) => { const P8PChart = ({ type, title, legendPosition, options, labels, datasets, onClick, style }) => {
//Ссылки на DOM //Ссылки на DOM
const chartCanvasRef = useRef(null); const chartCanvasRef = useRef(null);
const chartRef = useRef(null); const chartRef = useRef(null);
//Обработка нажатия на элемент графика //Обработка нажатия на элемент графика
const handleClick = useCallback( const handleClick = e => {
e => { const bar = chartRef.current.getElementsAtEventForMode(e, "nearest", { intersect: true }, true)[0];
const bar = chartRef.current.getElementsAtEventForMode(e, "nearest", { intersect: true }, true)[0]; if (onClick && bar)
if (onClick && bar) onClick({
onClick({ datasetIndex: bar.datasetIndex,
datasetIndex: bar.datasetIndex, itemIndex: bar.index,
itemIndex: bar.index, item: chartRef.current.data.datasets[bar.datasetIndex].items
item: chartRef.current.data.datasets[bar.datasetIndex].items ? chartRef.current.data.datasets[bar.datasetIndex].items[bar.index]
? chartRef.current.data.datasets[bar.datasetIndex].items[bar.index] : null
: null });
}); };
},
[onClick]
);
//При подключении к старнице //При подключении к старнице
useEffect(() => { useEffect(() => {
@ -92,10 +89,9 @@ const P8PChart = ({ type, title, legendPosition, options = {}, labels = [], data
if (chartRef.current) { if (chartRef.current) {
chartRef.current.data.labels = [...labels]; chartRef.current.data.labels = [...labels];
chartRef.current.data.datasets = [...datasets]; chartRef.current.data.datasets = [...datasets];
chartRef.current.options.onClick = handleClick;
chartRef.current.update(); chartRef.current.update();
} }
}, [datasets, labels, handleClick]); }, [datasets, labels]);
//Генерация содержимого //Генерация содержимого
return ( return (
@ -111,7 +107,7 @@ P8PChart.propTypes = {
title: PropTypes.string, title: PropTypes.string,
legendPosition: PropTypes.string, legendPosition: PropTypes.string,
options: PropTypes.object, options: PropTypes.object,
labels: PropTypes.arrayOf(PropTypes.string), labels: PropTypes.arrayOf(PropTypes.string).isRequired,
datasets: PropTypes.arrayOf(P8P_CHART_DATASET_SHAPE), datasets: PropTypes.arrayOf(P8P_CHART_DATASET_SHAPE),
onClick: PropTypes.func, onClick: PropTypes.func,
style: PropTypes.object style: PropTypes.object

View File

@ -1,819 +0,0 @@
/*
Парус 8 - Панели мониторинга
Компонент: Циклограмма
*/
//---------------------
//Подключение библиотек
//---------------------
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 { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { hasValue } from "../core/utils"; //Вспомогательный функции
//---------
//Константы
//---------
//Уровни масштаба
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 = ({
containerStyle,
lineHeight,
title,
titleStyle,
onTitleClick,
zoomBar,
zoom,
columns,
columnRenderer,
groups,
groupHeaderRenderer,
tasks,
taskRenderer,
taskAttributes,
taskAttributeRenderer,
taskDialogRenderer,
noDataFoundText,
nameTaskEditorCaption,
okTaskEditorBtnCaption,
cancelTaskEditorBtnCaption
}) => {
//Хук основного блока (для последующего определения доступной ширины)
const mainBlock = useRef(null);
//Хук для заголовка таблицы
const headerBlock = useRef(null);
//Собственное состояние
const [state, setState] = useState({
noData: true,
loaded: false,
lineHeight: NDEFAULT_LINE_HEIGHT,
maxWidth: 0,
maxHeight: 0,
shift: 0,
zoom: P8P_CYCLOGRAM_ZOOM.includes(zoom) ? zoom : 1,
tasks: [],
editTask: null
});
//Обновление масштаба циклограммы
const handleZoomChange = direction => {
//Считываем текущий индекс
const currentIndex = P8P_CYCLOGRAM_ZOOM.indexOf(state.zoom);
setState(pv => ({
...pv,
zoom:
currentIndex + direction !== P8P_CYCLOGRAM_ZOOM.length && currentIndex + direction !== -1
? P8P_CYCLOGRAM_ZOOM[currentIndex + direction]
: pv.zoom
}));
};
//Открытие редактора задачи
const openTaskEditor = task => setState(pv => ({ ...pv, editTask: { ...task } }));
//При сохранении задачи в редакторе
const handleTaskEditorSave = () => {
setState(pv => ({ ...pv, editTask: null }));
};
//При закрытии редактора задачи без сохранения
const handleTaskEditorCancel = () => setState(pv => ({ ...pv, editTask: null }));
//При скролле блока
const handleScroll = e => {
//Изменяем позицию заголовка таблицы относительно скролла
headerBlock.current.setAttribute("transform", "translate(0," + e.currentTarget.scrollTop + ")");
};
//При изменении данных
useEffect(() => {
//Если есть колонки и задачи
if (Array.isArray(columns) && columns.length > 0 && Array.isArray(tasks) && tasks.length > 0) {
//Определяем текущую максимальную ширину колонок
let currentColumnsMaxWidth = Math.max(...columns.map(o => o.end));
//Определяем доступный сдвиг для ширины колонок (16 - паддинг по бокам)
let columnShift = getShift(columns, currentColumnsMaxWidth, mainBlock.current.offsetWidth - 16) * state.zoom;
//Устанавливаем значения исходя из колонок/задач
setState(pv => ({
...pv,
loaded: true,
lineHeight: lineHeight ? lineHeight : NDEFAULT_LINE_HEIGHT,
maxWidth: columnShift !== 0 ? currentColumnsMaxWidth * columnShift : currentColumnsMaxWidth,
maxHeight: NDEFAULT_HEADER_HEIGHT + (Math.max(...tasks.map(o => o.lineNumb)) + 1) * (lineHeight ? lineHeight : NDEFAULT_LINE_HEIGHT),
shift: columnShift,
tasks: tasks,
noData: false
}));
} else {
//Устанавливаем значения исходя из колонок/задач
setState(pv => ({
...pv,
noData: true
}));
}
}, [columns, lineHeight, state.zoom, tasks]);
//Генерация содержимого
return (
<>
<div ref={mainBlock} style={{ ...(containerStyle ? containerStyle : {}) }}>
{state.noData ? <P8PAppInlineError text={noDataFoundText} /> : null}
{state.loaded ? (
<>
{title ? (
<Typography
p={1}
sx={{ ...STYLES.CYCLOGRAM_TITLE, ...(titleStyle ? titleStyle : {}) }}
align="center"
color="textSecondary"
variant="subtitle1"
>
{onTitleClick ? (
<Link component="button" variant="body2" underline="hover" onClick={() => onTitleClick()}>
{title}
</Link>
) : (
title
)}
</Typography>
) : null}
{zoomBar ? (
<Box p={1} sx={STYLES.CYCLOGRAM_ZOOM}>
<IconButton
onClick={() => handleZoomChange(1)}
disabled={state.zoom == P8P_CYCLOGRAM_ZOOM[P8P_CYCLOGRAM_ZOOM.length - 1]}
>
<Icon>zoom_in</Icon>
</IconButton>
<IconButton onClick={() => handleZoomChange(-1)} disabled={state.zoom == P8P_CYCLOGRAM_ZOOM[0]}>
<Icon>zoom_out</Icon>
</IconButton>
</Box>
) : null}
<Box className="scroll" sx={STYLES.CYCLOGRAM_BOX(state.noData, title, zoomBar)} onScroll={handleScroll}>
<svg id="cyclogram" width={state.maxWidth} height={state.maxHeight}>
<P8PCyclogramGrid
tasks={state.tasks}
columns={columns}
shift={state.shift}
maxWidth={state.maxWidth}
maxHeight={state.maxHeight}
lineHeight={state.lineHeight}
/>
<P8PCyclogramMain
columns={columns}
groups={groups}
tasks={state.tasks}
shift={state.shift}
lineHeight={state.lineHeight}
maxWidth={state.maxWidth}
maxHeight={state.maxHeight}
groupHeaderRenderer={groupHeaderRenderer}
openTaskEditor={openTaskEditor}
taskRenderer={taskRenderer}
columnRenderer={columnRenderer}
headerBlock={headerBlock}
/>
</svg>
</Box>
</>
) : null}
{state.editTask ? (
<P8PCyclogramTaskEditor
task={state.editTask}
taskAttributes={taskAttributes}
onOk={handleTaskEditorSave}
onCancel={handleTaskEditorCancel}
taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={taskDialogRenderer}
nameCaption={nameTaskEditorCaption}
okBtnCaption={okTaskEditorBtnCaption}
cancelBtnCaption={cancelTaskEditorBtnCaption}
/>
) : null}
</div>
</>
);
};
//Контроль свойств - Циклограмма
P8PCyclogram.propTypes = {
containerStyle: PropTypes.object,
lineHeight: PropTypes.number,
title: PropTypes.string,
titleStyle: PropTypes.object,
onTitleClick: PropTypes.func,
zoomBar: PropTypes.bool,
zoom: PropTypes.number,
columns: PropTypes.arrayOf(P8P_CYCLOGRAM_COLUMN_SHAPE).isRequired,
columnRenderer: PropTypes.func,
groups: PropTypes.arrayOf(P8P_CYCLOGRAM_GROUP_SHAPE),
groupHeaderRenderer: PropTypes.func,
tasks: PropTypes.arrayOf(P8P_CYCLOGRAM_TASK_SHAPE).isRequired,
taskRenderer: PropTypes.func,
taskAttributes: PropTypes.arrayOf(P8P_CYCLOGRAM_TASK_ATTRIBUTE_SHAPE),
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
noDataFoundText: PropTypes.string.isRequired,
nameTaskEditorCaption: PropTypes.string.isRequired,
okTaskEditorBtnCaption: PropTypes.string.isRequired,
cancelTaskEditorBtnCaption: PropTypes.string.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { P8PCyclogram };

View File

@ -36,16 +36,15 @@ const P8P_DATA_GRID_FILTERS_HEIGHT = P8P_TABLE_FILTERS_HEIGHT;
//Таблица данных //Таблица данных
const P8PDataGrid = ({ const P8PDataGrid = ({
style = {}, columnsDef,
columnsDef = [],
filtersInitial, filtersInitial,
groups = [], groups,
rows = [], rows,
size, size,
fixedHeader = false, fixedHeader = false,
fixedColumns = 0, fixedColumns = 0,
morePages = false, morePages = false,
reloading = false, reloading,
expandable, expandable,
orderAscMenuItemCaption, orderAscMenuItemCaption,
orderDescMenuItemCaption, orderDescMenuItemCaption,
@ -115,7 +114,6 @@ const P8PDataGrid = ({
//Генерация содержимого //Генерация содержимого
return ( return (
<P8PTable <P8PTable
style={style}
columnsDef={columnsDef} columnsDef={columnsDef}
groups={groups} groups={groups}
rows={rows} rows={rows}
@ -156,16 +154,15 @@ const P8PDataGrid = ({
//Контроль свойств - Таблица данных //Контроль свойств - Таблица данных
P8PDataGrid.propTypes = { P8PDataGrid.propTypes = {
style: PropTypes.object, columnsDef: PropTypes.array.isRequired,
columnsDef: PropTypes.array,
filtersInitial: PropTypes.arrayOf(P8P_DATA_GRID_FILTER_SHAPE), filtersInitial: PropTypes.arrayOf(P8P_DATA_GRID_FILTER_SHAPE),
groups: PropTypes.array, groups: PropTypes.array,
rows: PropTypes.array, rows: PropTypes.array.isRequired,
size: PropTypes.string, size: PropTypes.string,
fixedHeader: PropTypes.bool, fixedHeader: PropTypes.bool,
fixedColumns: PropTypes.number, fixedColumns: PropTypes.number,
morePages: PropTypes.bool, morePages: PropTypes.bool,
reloading: PropTypes.bool, reloading: PropTypes.bool.isRequired,
expandable: PropTypes.bool, expandable: PropTypes.bool,
orderAscMenuItemCaption: PropTypes.string.isRequired, orderAscMenuItemCaption: PropTypes.string.isRequired,
orderDescMenuItemCaption: PropTypes.string.isRequired, orderDescMenuItemCaption: PropTypes.string.isRequired,

View File

@ -27,7 +27,7 @@ const STYLES = {
//----------- //-----------
//Полноэкранный диалог //Полноэкранный диалог
const P8PFullScreenDialog = ({ title, onClose, contentProps, children }) => { const P8PFullScreenDialog = ({ title, onClose, children }) => {
const handleClose = () => { const handleClose = () => {
onClose ? onClose() : null; onClose ? onClose() : null;
}; };
@ -46,7 +46,7 @@ const P8PFullScreenDialog = ({ title, onClose, contentProps, children }) => {
</Toolbar> </Toolbar>
</AppBar> </AppBar>
</DialogTitle> </DialogTitle>
<DialogContent {...(contentProps ? contentProps : {})}>{children}</DialogContent> <DialogContent>{children}</DialogContent>
</Dialog> </Dialog>
); );
}; };
@ -55,8 +55,7 @@ const P8PFullScreenDialog = ({ title, onClose, contentProps, children }) => {
P8PFullScreenDialog.propTypes = { P8PFullScreenDialog.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
onClose: PropTypes.func, onClose: PropTypes.func,
children: PropTypes.element, children: PropTypes.element
contentProps: PropTypes.object
}; };
//---------------- //----------------

View File

@ -33,7 +33,7 @@ import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемо
//--------- //---------
//Уровни масштаба //Уровни масштаба
const P8P_GANTT_ZOOM = [0, 1, 2, 3, 4, 5]; const P8P_GANTT_ZOOM = [0, 1, 2, 3, 4];
//Уровни масштаба (строковые наименования в терминах библиотеки) //Уровни масштаба (строковые наименования в терминах библиотеки)
const P8P_GANTT_ZOOM_VIEW_MODES = { const P8P_GANTT_ZOOM_VIEW_MODES = {
@ -41,8 +41,7 @@ const P8P_GANTT_ZOOM_VIEW_MODES = {
1: "Half Day", 1: "Half Day",
2: "Day", 2: "Day",
3: "Week", 3: "Week",
4: "Month", 4: "Month"
5: "Year"
}; };
//Структура задачи //Структура задачи
@ -139,7 +138,6 @@ const P8PGanttTaskEditor = ({
onCancel, onCancel,
taskAttributeRenderer, taskAttributeRenderer,
taskDialogRenderer, taskDialogRenderer,
taskDialogProps,
numbCaption, numbCaption,
nameCaption, nameCaption,
startCaption, startCaption,
@ -187,7 +185,7 @@ const P8PGanttTaskEditor = ({
//Генерация содержимого //Генерация содержимого
return ( return (
<Dialog open onClose={handleCancel} {...(taskDialogProps ? taskDialogProps : {})}> <Dialog open onClose={handleCancel}>
{taskDialogRenderer ? ( {taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel }) taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
) : ( ) : (
@ -316,7 +314,6 @@ P8PGanttTaskEditor.propTypes = {
onCancel: PropTypes.func, onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func, taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func, taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
numbCaption: PropTypes.string.isRequired, numbCaption: PropTypes.string.isRequired,
nameCaption: PropTypes.string.isRequired, nameCaption: PropTypes.string.isRequired,
startCaption: PropTypes.string.isRequired, startCaption: PropTypes.string.isRequired,
@ -349,7 +346,6 @@ const P8PGantt = ({
onTaskProgressChange, onTaskProgressChange,
taskAttributeRenderer, taskAttributeRenderer,
taskDialogRenderer, taskDialogRenderer,
taskDialogProps,
noDataFoundText, noDataFoundText,
numbTaskEditorCaption, numbTaskEditorCaption,
nameTaskEditorCaption, nameTaskEditorCaption,
@ -470,7 +466,6 @@ const P8PGantt = ({
onCancel={handleTaskEditorCancel} onCancel={handleTaskEditorCancel}
taskAttributeRenderer={taskAttributeRenderer} taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={taskDialogRenderer} taskDialogRenderer={taskDialogRenderer}
taskDialogProps={taskDialogProps}
numbCaption={numbTaskEditorCaption} numbCaption={numbTaskEditorCaption}
nameCaption={nameTaskEditorCaption} nameCaption={nameTaskEditorCaption}
startCaption={startTaskEditorCaption} startCaption={startTaskEditorCaption}
@ -506,7 +501,6 @@ P8PGantt.propTypes = {
onTaskProgressChange: PropTypes.func, onTaskProgressChange: PropTypes.func,
taskAttributeRenderer: PropTypes.func, taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func, taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
noDataFoundText: PropTypes.string.isRequired, noDataFoundText: PropTypes.string.isRequired,
numbTaskEditorCaption: PropTypes.string.isRequired, numbTaskEditorCaption: PropTypes.string.isRequired,
nameTaskEditorCaption: PropTypes.string.isRequired, nameTaskEditorCaption: PropTypes.string.isRequired,

View File

@ -1,186 +0,0 @@
/*
Парус 8 - Панели мониторинга
Компонент: Индикатор
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
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"; //Типовые стили
//---------
//Константы
//---------
//Варианты исполнения
const P8P_INDICATOR_VARIANT = {
ELEVATION: "elevation",
OUTLINED: "outlined"
};
//Состояния
const P8P_INDICATOR_STATE = {
UNDEFINED: STATE.UNDEFINED,
OK: STATE.OK,
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": { backgroundColor: APP_COLORS.HOVER.color },
"&: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: { width: "99cqw" }
};
//-----------------------
//Вспомогательные функции
//-----------------------
//Подбор цвета заливки
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 P8PIndicator = ({
caption,
value,
icon = null,
state = STATE.UNDEFINED,
square = false,
elevation = 3,
variant = P8P_INDICATOR_VARIANT.ELEVATION,
hint = null,
onClick = null,
backgroundColor = null,
color = null
} = {}) => {
//Собственное состояние - отображение окна подсказки
const [showHint, setShowHint] = useState(false);
//При нажатии на индикатор
const handleClick = () => (onClick && !showHint ? onClick() : null);
//При нажатии на кнопку получения подсказки
const handleHintClick = e => {
setShowHint(true);
e.stopPropagation();
};
//При нажатии на кнопку закрытия подсказки
const handleHintClose = () => setShowHint(false);
//Представление текста значения индикатора
const valueTextView = <Typography variant={"h4"}>{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}</Typography>;
//Представление текста подписи индикатора
const captionView = (
<Typography align={"left"} noWrap={true} sx={STYLES.CAPTION_TYPOGRAPHY} title={caption}>
{caption}
</Typography>
);
//Представление подписи индикатора
const valueView = hint ? (
<>
{showHint && <P8PHintDialog title={caption} hint={hint} onOk={handleHintClose} />}
<Stack direction={"row"} alignItems={"start"}>
{valueTextView}
<IconButton onClick={handleHintClick}>
<Icon sx={STYLES.HINT_ICON(state, color)}>help_outline</Icon>
</IconButton>
</Stack>
</>
) : (
valueTextView
);
//Флаг активности индикатора
const clickable = onClick ? true : false;
//Представление
return (
<Paper
elevation={variant === P8P_INDICATOR_VARIANT.ELEVATION ? elevation : 0}
sx={STYLES.CONTAINER(state, clickable, color, backgroundColor)}
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}>
{valueView}
{captionView}
</Stack>
{icon ? <Icon sx={STYLES.ICON(state, color)}>{icon}</Icon> : null}
</Stack>
</Paper>
);
};
//Контроль свойств - Индикатор
P8PIndicator.propTypes = {
caption: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
icon: PropTypes.string,
state: PropTypes.oneOf(Object.values(P8P_INDICATOR_STATE)),
square: PropTypes.bool,
elevation: PropTypes.number,
variant: PropTypes.oneOf(Object.values(P8P_INDICATOR_VARIANT)),
hint: PropTypes.string,
onClick: PropTypes.func,
backgroundColor: PropTypes.string,
color: PropTypes.string
};
//----------------
//Интерфейс модуля
//----------------
export { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator };

View File

@ -62,15 +62,8 @@ const STYLES = {
GRID_PANEL_CARD_CONTENT_TITLE_ICON: { paddingTop: "4px" }, GRID_PANEL_CARD_CONTENT_TITLE_ICON: { paddingTop: "4px" },
GRID_PANEL_CARD_ACTIONS: { marginTop: "auto", display: "flex", justifyContent: "flex-end", alignItems: "flex-start" }, 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_GROUP_HEADER: { fontWeight: "bold", fontFamily: "tahoma, arial, verdana, sans-serif!important", fontSize: "13px!important" },
DESKTOP_ITEM_BUTTON: { DESKTOP_ITEM_BUTTON: { fontSize: "12px", textTransform: "none", "&:hover": { backgroundColor: "#c3e1ff" }, maxWidth: "150px" },
fontSize: "12px", DESKTOP_ITEM_STACK: { justifyContent: "center", alignItems: "center", 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_ICON: { width: "48px", height: "48px", fontSize: "48px" },
DESKTOP_ITEM_CATION: { DESKTOP_ITEM_CATION: {
display: "-webkit-box", display: "-webkit-box",
@ -135,14 +128,7 @@ const getPanelsLinks = ({ variant, panels, selectedPanel, group, defaultGroupTyt
<Card sx={STYLES.GRID_PANEL_CARD}> <Card sx={STYLES.GRID_PANEL_CARD}>
{panel.preview ? ( {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={STYLES.GRID_PANEL_CARD_MEDIA} />
) : ( ) : null}
<CardMedia
component="img"
alt={panel.name}
image={"./img/default_preview.png"}
sx={STYLES.GRID_PANEL_CARD_MEDIA}
/>
)}
<CardContent> <CardContent>
<Stack gap={1} direction="row" sx={STYLES.GRID_PANEL_CARD_CONTENT_TITLE}> <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} {panel.icon ? <Icon sx={STYLES.GRID_PANEL_CARD_CONTENT_TITLE_ICON}>{panel.icon}</Icon> : null}
@ -179,10 +165,12 @@ const getPanelsLinks = ({ variant, panels, selectedPanel, group, defaultGroupTyt
sx={STYLES.DESKTOP_ITEM_BUTTON} sx={STYLES.DESKTOP_ITEM_BUTTON}
title={panel.caption} title={panel.caption}
> >
<Icon sx={STYLES.DESKTOP_ITEM_ICON}>{panel.icon}</Icon> <Stack sx={STYLES.DESKTOP_ITEM_STACK}>
<Typography sx={STYLES.DESKTOP_ITEM_CATION} variant="body1"> <Icon sx={STYLES.DESKTOP_ITEM_ICON}>{panel.icon}</Icon>
{panel.caption} <Typography sx={STYLES.DESKTOP_ITEM_CATION} variant="body1">
</Typography> {panel.caption}
</Typography>
</Stack>
</Button> </Button>
) )
); );
@ -242,12 +230,7 @@ const P8PPanelsMenuDesktop = ({ group, onItemNavigate, panels = [], defaultGroup
const panelsLinks = getPanelsLinks({ variant: P8P_PANELS_MENU_VARIANT.DESKTOP, panels, group, defaultGroupTytle, onItemNavigate }); const panelsLinks = getPanelsLinks({ variant: P8P_PANELS_MENU_VARIANT.DESKTOP, panels, group, defaultGroupTytle, onItemNavigate });
//Генерация содержимого //Генерация содержимого
return ( return <Box p={2}>{panelsLinks}</Box>;
<Box p={2}>
{panelsLinks[0]}
<Stack direction="row">{panelsLinks.map((l, i) => (i > 0 ? l : null))}</Stack>
</Box>
);
}; };
//Контроль свойств - Меню панелей - рабочий стол //Контроль свойств - Меню панелей - рабочий стол

View File

@ -34,7 +34,7 @@ import {
Link Link
} from "@mui/material"; //Интерфейсные компоненты } from "@mui/material"; //Интерфейсные компоненты
import { useTheme } from "@mui/material/styles"; //Взаимодействие со стилями MUI import { useTheme } from "@mui/material/styles"; //Взаимодействие со стилями MUI
import { P8PAppInlineError, P8PHintDialog } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { P8P_TABLE_AT, HEADER_INITIAL_STATE, hasValue, p8pTableReducer } from "./p8p_table_reducer"; //Редьюсер состояния import { P8P_TABLE_AT, HEADER_INITIAL_STATE, hasValue, p8pTableReducer } from "./p8p_table_reducer"; //Редьюсер состояния
//--------- //---------
@ -89,7 +89,9 @@ const P8P_TABLE_FILTERS_HEIGHT = "48px";
//Стили //Стили
const STYLES = { const STYLES = {
TABLE: {}, TABLE: {
with: "100%"
},
TABLE_HEAD_STICKY: { TABLE_HEAD_STICKY: {
position: "sticky", position: "sticky",
top: 0, top: 0,
@ -116,9 +118,7 @@ const STYLES = {
}, },
TABLE_CELL_EXPAND_CONTAINER: { TABLE_CELL_EXPAND_CONTAINER: {
paddingBottom: 0, paddingBottom: 0,
paddingTop: 0, paddingTop: 0
paddingLeft: 0,
paddingRight: 0
}, },
TABLE_CELL_GROUP_HEADER: { TABLE_CELL_GROUP_HEADER: {
backgroundColor: "lightgray" backgroundColor: "lightgray"
@ -288,6 +288,28 @@ P8PTableColumnMenu.propTypes = {
onItemClick: PropTypes.func onItemClick: PropTypes.func
}; };
//Диалог подсказки
const P8PTableColumnHintDialog = ({ columnDef, okBtnCaption, onOk }) => {
return (
<Dialog open={true} aria-labelledby="filter-dialog-title" aria-describedby="filter-dialog-description" onClose={() => (onOk ? onOk() : null)}>
<DialogTitle id="filter-dialog-title">{columnDef.caption}</DialogTitle>
<DialogContent>
<div dangerouslySetInnerHTML={{ __html: columnDef.hint }}></div>
</DialogContent>
<DialogActions>
<Button onClick={() => (onOk ? onOk() : null)}>{okBtnCaption}</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог подсказки
P8PTableColumnHintDialog.propTypes = {
columnDef: PropTypes.object.isRequired,
okBtnCaption: PropTypes.string.isRequired,
onOk: PropTypes.func
};
//Диалог фильтра //Диалог фильтра
const P8PTableColumnFilterDialog = ({ const P8PTableColumnFilterDialog = ({
columnDef, columnDef,
@ -464,17 +486,16 @@ P8PTableFiltersChips.propTypes = {
//Таблица //Таблица
const P8PTable = ({ const P8PTable = ({
style = {}, columnsDef,
columnsDef = [], groups,
groups = [], rows,
rows = [],
orders, orders,
filters, filters,
size, size,
fixedHeader = false, fixedHeader = false,
fixedColumns = 0, fixedColumns = 0,
morePages = false, morePages = false,
reloading = false, reloading,
expandable, expandable,
orderAscMenuItemCaption, orderAscMenuItemCaption,
orderDescMenuItemCaption, orderDescMenuItemCaption,
@ -510,9 +531,7 @@ const P8PTable = ({
const [expanded, setExpanded] = useState({}); const [expanded, setExpanded] = useState({});
//Собственное состояния - развёрнутые группы //Собственное состояния - развёрнутые группы
const [expandedGroups, setExpandedGroups] = useState( const [expandedGroups, setExpandedGroups] = useState({});
Array.isArray(groups) && groups.length > 0 ? Object.assign({}, ...groups.map(g => ({ [g.name]: g.expanded }))) : {}
);
//Собственное состояние - колонка с отображаемой подсказкой //Собственное состояние - колонка с отображаемой подсказкой
const [displayHintColumn, setDisplayHintColumn] = useState(null); const [displayHintColumn, setDisplayHintColumn] = useState(null);
@ -679,8 +698,10 @@ const P8PTable = ({
//Генерация содержимого //Генерация содержимого
return ( return (
<div style={{ ...(style || {}) }}> <div>
{displayHintColumn ? <P8PHintDialog title={displayHintColumnDef.caption} hint={displayHintColumnDef.hint} onOk={handleHintOk} /> : null} {displayHintColumn ? (
<P8PTableColumnHintDialog columnDef={displayHintColumnDef} okBtnCaption={okFilterBtnCaption} onOk={handleHintOk} />
) : null}
{filterColumn ? ( {filterColumn ? (
<P8PTableColumnFilterDialog <P8PTableColumnFilterDialog
columnDef={filterColumnDef} columnDef={filterColumnDef}
@ -875,7 +896,6 @@ const P8PTable = ({
//Контроль свойств - Таблица //Контроль свойств - Таблица
P8PTable.propTypes = { P8PTable.propTypes = {
style: PropTypes.object,
columnsDef: PropTypes.arrayOf( columnsDef: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
@ -911,7 +931,7 @@ P8PTable.propTypes = {
fixedHeader: PropTypes.bool, fixedHeader: PropTypes.bool,
fixedColumns: PropTypes.number, fixedColumns: PropTypes.number,
morePages: PropTypes.bool, morePages: PropTypes.bool,
reloading: PropTypes.bool, reloading: PropTypes.bool.isRequired,
expandable: PropTypes.bool, expandable: PropTypes.bool,
orderAscMenuItemCaption: PropTypes.string.isRequired, orderAscMenuItemCaption: PropTypes.string.isRequired,
orderDescMenuItemCaption: PropTypes.string.isRequired, orderDescMenuItemCaption: PropTypes.string.isRequired,

View File

@ -15,7 +15,6 @@ 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"; //Таблица данных
import { P8PDataGrid, P8P_DATA_GRID_DATA_TYPE, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE } from "./components/p8p_data_grid"; //Таблица данных 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 { 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"; //Циклограмма
//--------- //---------
//Константы //Константы
@ -77,14 +76,6 @@ const P8P_GANTT_CONFIG_PROPS = {
cancelTaskEditorBtnCaption: BUTTONS.CANCEL cancelTaskEditorBtnCaption: BUTTONS.CANCEL
}; };
//Конфигурируемые свойства "Циклограммы" (P8PCyclogram)
const P8P_CYCLOGRAM_CONFIG_PROPS = {
noDataFoundText: TEXTS.NO_DATA_FOUND,
nameTaskEditorCaption: CAPTIONS.NAME,
okTaskEditorBtnCaption: BUTTONS.OK,
cancelTaskEditorBtnCaption: BUTTONS.CANCEL
};
//----------------------- //-----------------------
//Вспомогательные функции //Вспомогательные функции
//----------------------- //-----------------------
@ -99,7 +90,6 @@ const addConfigChildProps = children =>
if (child.type.name === "P8PTable") configProps = P8P_TABLE_CONFIG_PROPS; if (child.type.name === "P8PTable") configProps = P8P_TABLE_CONFIG_PROPS;
if (child.type.name === "P8PDataGrid") configProps = P8P_DATA_GRID_CONFIG_PROPS; if (child.type.name === "P8PDataGrid") configProps = P8P_DATA_GRID_CONFIG_PROPS;
if (child.type.name === "P8PGantt") configProps = P8P_GANTT_CONFIG_PROPS; if (child.type.name === "P8PGantt") configProps = P8P_GANTT_CONFIG_PROPS;
if (child.type.name === "P8PCyclogram") configProps = P8P_CYCLOGRAM_CONFIG_PROPS;
return React.createElement(child.type, { ...configProps, ...restProps }, addConfigChildProps(children)); return React.createElement(child.type, { ...configProps, ...restProps }, addConfigChildProps(children));
}); });
@ -122,9 +112,6 @@ const P8PDataGridConfigWrapped = (props = {}) => <P8PDataGrid {...P8P_DATA_GRID_
//Обёртка для компонента "Диаграмма Ганта" (P8PGantt) //Обёртка для компонента "Диаграмма Ганта" (P8PGantt)
const P8PGanttConfigWrapped = (props = {}) => <P8PGantt {...P8P_GANTT_CONFIG_PROPS} {...props} />; const P8PGanttConfigWrapped = (props = {}) => <P8PGantt {...P8P_GANTT_CONFIG_PROPS} {...props} />;
//Обёртка для компонента "Циклограмма" (P8PCyclogram)
const P8PCyclogramConfigWrapped = (props = {}) => <P8PCyclogram {...P8P_GANTT_CONFIG_PROPS} {...props} />;
//Универсальный элемент-обёртка в параметры конфигурации //Универсальный элемент-обёртка в параметры конфигурации
const ConfigWrapper = ({ children }) => addConfigChildProps(children); const ConfigWrapper = ({ children }) => addConfigChildProps(children);
@ -145,7 +132,6 @@ export {
P8P_DATA_GRID_SIZE, P8P_DATA_GRID_SIZE,
P8P_DATA_GRID_FILTER_SHAPE, P8P_DATA_GRID_FILTER_SHAPE,
P8P_GANTT_CONFIG_PROPS, P8P_GANTT_CONFIG_PROPS,
P8P_CYCLOGRAM_CONFIG_PROPS,
P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_SHAPE,
P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE,
P8P_GANTT_TASK_COLOR_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE,
@ -154,6 +140,5 @@ export {
P8PTableConfigWrapped, P8PTableConfigWrapped,
P8PDataGridConfigWrapped, P8PDataGridConfigWrapped,
P8PGanttConfigWrapped, P8PGanttConfigWrapped,
P8PCyclogramConfigWrapped,
ConfigWrapper ConfigWrapper
}; };

View File

@ -22,8 +22,7 @@ const P8O_API = window.top?.parus?.clientApi;
//Структура объекта с описанием ошибок //Структура объекта с описанием ошибок
const APPLICATION_CONTEXT_ERRORS_SHAPE = PropTypes.shape({ const APPLICATION_CONTEXT_ERRORS_SHAPE = PropTypes.shape({
P8O_API_UNAVAILABLE: PropTypes.string.isRequired, P8O_API_UNAVAILABLE: PropTypes.string.isRequired
P8O_API_UNSUPPORTED: PropTypes.string.isRequired
}); });
//---------------- //----------------
@ -56,9 +55,6 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
//Установка списка панелей //Установка списка панелей
const setPanels = panels => dispatch({ type: APP_AT.LOAD_PANELS, payload: panels }); const setPanels = panels => dispatch({ type: APP_AT.LOAD_PANELS, payload: panels });
//Установка заголовка в шапке приложения
const setAppBarTitle = appBarTitle => dispatch({ type: APP_AT.SET_APP_BAR_TITLE, payload: appBarTitle });
//Поиск раздела по имени //Поиск раздела по имени
const findPanelByName = name => state.panels.find(panel => panel.name == name); const findPanelByName = name => state.panels.find(panel => panel.name == name);
@ -76,38 +72,21 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
//Отображение раздела "ПАРУС 8 Онлайн" //Отображение раздела "ПАРУС 8 Онлайн"
const pOnlineShowUnit = useCallback( const pOnlineShowUnit = useCallback(
({ unitCode, showMethod = "main", inputParameters, modal = true }) => { ({ unitCode, showMethod = "main", inputParameters }) => {
if (P8O_API) if (P8O_API) P8O_API.fn.openDocumentModal({ unitcode: unitCode, method: showMethod, inputParameters });
modal
? P8O_API.fn.openDocumentModal({ unitcode: unitCode, method: showMethod, inputParameters })
: P8O_API.fn.openDocument
? P8O_API.fn.openDocument({ unitcode: unitCode, method: showMethod, inputParameters })
: showMsgErr(errors.P8O_API_UNSUPPORTED);
else showMsgErr(errors.P8O_API_UNAVAILABLE); else showMsgErr(errors.P8O_API_UNAVAILABLE);
}, },
[showMsgErr, errors.P8O_API_UNAVAILABLE, errors.P8O_API_UNSUPPORTED] [showMsgErr, errors.P8O_API_UNAVAILABLE]
); );
//Отображение документа "ПАРУС 8 Онлайн" //Отображение документа "ПАРУС 8 Онлайн"
const pOnlineShowDocument = useCallback( const pOnlineShowDocument = useCallback(
({ unitCode, document, showMethod = "main", inRnParameter = "in_RN", modal = true }) => { ({ unitCode, document, showMethod = "main", inRnParameter = "in_RN" }) => {
if (P8O_API) if (P8O_API)
modal P8O_API.fn.openDocumentModal({ unitcode: unitCode, method: showMethod, inputParameters: [{ name: inRnParameter, value: document }] });
? P8O_API.fn.openDocumentModal({
unitcode: unitCode,
method: showMethod,
inputParameters: [{ name: inRnParameter, value: document }]
})
: P8O_API.fn.openDocument
? P8O_API.fn.openDocument({
unitcode: unitCode,
method: showMethod,
inputParameters: [{ name: inRnParameter, value: document }]
})
: showMsgErr(errors.P8O_API_UNSUPPORTED);
else showMsgErr(errors.P8O_API_UNAVAILABLE); else showMsgErr(errors.P8O_API_UNAVAILABLE);
}, },
[showMsgErr, errors.P8O_API_UNAVAILABLE, errors.P8O_API_UNSUPPORTED] [showMsgErr, errors.P8O_API_UNAVAILABLE]
); );
//Отображение словаря "ПАРУС 8 Онлайн" //Отображение словаря "ПАРУС 8 Онлайн"
@ -172,7 +151,6 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
return ( return (
<ApplicationСtx.Provider <ApplicationСtx.Provider
value={{ value={{
setAppBarTitle,
findPanelByName, findPanelByName,
pOnlineShowTab, pOnlineShowTab,
pOnlineShowUnit, pOnlineShowUnit,

View File

@ -12,14 +12,12 @@ const APP_AT = {
SET_URL_BASE: "SET_URL_BASE", //Установка базового URL приложения SET_URL_BASE: "SET_URL_BASE", //Установка базового URL приложения
LOAD_PANELS: "LOAD_PANELS", //Загрузка списка панелей LOAD_PANELS: "LOAD_PANELS", //Загрузка списка панелей
SET_INITIALIZED: "SET_INITIALIZED", //Установка флага инициализированности приложения SET_INITIALIZED: "SET_INITIALIZED", //Установка флага инициализированности приложения
SET_DISPLAY_SIZE: "SET_DISPLAY_SIZE", //Установка текущего типового размера экрана SET_DISPLAY_SIZE: "SET_DISPLAY_SIZE" //Установка текущего типового размера экрана
SET_APP_BAR_TITLE: "SET_APP_BAR_TITLE" //Установка заголовка в шапке приложения
}; };
//Состояние приложения по умолчанию //Состояние приложения по умолчанию
const INITIAL_STATE = displaySizeGetter => ({ const INITIAL_STATE = displaySizeGetter => ({
displaySize: displaySizeGetter(), displaySize: displaySizeGetter(),
appBarTitle: "",
urlBase: "", urlBase: "",
panels: [], panels: [],
panelsLoaded: false, panelsLoaded: false,
@ -48,8 +46,6 @@ const handlers = {
[APP_AT.SET_INITIALIZED]: state => ({ ...state, initialized: true }), [APP_AT.SET_INITIALIZED]: state => ({ ...state, initialized: true }),
//Установка текущего типового размера экрана //Установка текущего типового размера экрана
[APP_AT.SET_DISPLAY_SIZE]: (state, { payload }) => ({ ...state, displaySize: payload }), [APP_AT.SET_DISPLAY_SIZE]: (state, { payload }) => ({ ...state, displaySize: payload }),
//Установка заголовка в шапке приложения
[APP_AT.SET_APP_BAR_TITLE]: (state, { payload }) => ({ ...state, appBarTitle: payload }),
//Обработчик по умолчанию //Обработчик по умолчанию
DEFAULT: state => state DEFAULT: state => state
}; };

View File

@ -10,7 +10,6 @@
import React, { createContext, useContext, useCallback } from "react"; //ReactJS import React, { createContext, useContext, useCallback } from "react"; //ReactJS
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { MessagingСtx } from "./messaging"; //Контекст сообщений import { MessagingСtx } from "./messaging"; //Контекст сообщений
import { formatErrorMessage } from "../core/utils"; //Вспомогательные функции
//--------- //---------
//Константы //Константы
@ -64,8 +63,7 @@ export const BackEndContext = ({ client, children }) => {
throwError = true, throwError = true,
showErrorMessage = true, showErrorMessage = true,
fullResponse = false, fullResponse = false,
spreadOutArguments = true, spreadOutArguments = true
signal = null
} = {}) => { } = {}) => {
try { try {
if (loader !== false) showLoader(loaderMessage); if (loader !== false) showLoader(loaderMessage);
@ -77,18 +75,12 @@ export const BackEndContext = ({ client, children }) => {
tagValueProcessor, tagValueProcessor,
attributeValueProcessor, attributeValueProcessor,
throwError, throwError,
spreadOutArguments, spreadOutArguments
signal
}); });
if (fullResponse === true || isRespErr(result)) return result; if (fullResponse === true || isRespErr(result)) return result;
else return result.XPAYLOAD; else return result.XPAYLOAD;
} catch (e) { } catch (e) {
if (showErrorMessage) { if (showErrorMessage) showMsgErr(e.message);
//Разбираем текст ошибки
let errMsg = formatErrorMessage(e.message);
//Отображаем ошибку
showMsgErr(errMsg.text, null, errMsg.fullErrorText);
}
throw e; throw e;
} finally { } finally {
if (loader !== false) hideLoader(); if (loader !== false) hideLoader();

View File

@ -33,9 +33,7 @@ const MESSAGING_CONTEXT_TEXTS_SHAPE = PropTypes.shape({
const MESSAGING_CONTEXT_BUTTONS_SHAPE = PropTypes.shape({ const MESSAGING_CONTEXT_BUTTONS_SHAPE = PropTypes.shape({
CLOSE: PropTypes.string.isRequired, CLOSE: PropTypes.string.isRequired,
OK: PropTypes.string.isRequired, OK: PropTypes.string.isRequired,
CANCEL: PropTypes.string.isRequired, CANCEL: PropTypes.string.isRequired
DETAIL: PropTypes.string.isRequired,
HIDE: PropTypes.string.isRequired
}); });
//---------------- //----------------
@ -58,16 +56,12 @@ export const MessagingContext = ({ titles, texts, buttons, children }) => {
//Отображение сообщения //Отображение сообщения
const showMsg = useCallback( const showMsg = useCallback(
(type, text, msgOnOk = null, msgOnCancel = null, fullErrorText = null) => (type, text, msgOnOk = null, msgOnCancel = null) => dispatch({ type: MSG_AT.SHOW_MSG, payload: { type, text, msgOnOk, msgOnCancel } }),
dispatch({ type: MSG_AT.SHOW_MSG, payload: { type, text, msgOnOk, msgOnCancel, fullErrorText } }),
[] []
); );
//Отображение сообщения - ошибка //Отображение сообщения - ошибка
const showMsgErr = useCallback( const showMsgErr = useCallback((text, msgOnOk = null) => showMsg(MSG_TYPE.ERR, text, msgOnOk), [showMsg]);
(text, msgOnOk = null, fullErrorText = null) => showMsg(MSG_TYPE.ERR, text, msgOnOk, null, fullErrorText),
[showMsg]
);
//Отображение сообщения - информация //Отображение сообщения - информация
const showMsgInfo = useCallback((text, msgOnOk = null) => showMsg(MSG_TYPE.INFO, text, msgOnOk), [showMsg]); const showMsgInfo = useCallback((text, msgOnOk = null) => showMsg(MSG_TYPE.INFO, text, msgOnOk), [showMsg]);
@ -132,7 +126,6 @@ export const MessagingContext = ({ titles, texts, buttons, children }) => {
open={true} open={true}
variant={state.msgType} variant={state.msgType}
text={state.msgText} text={state.msgText}
fullErrorText={state.msgFullErrorText}
title title
titleText={state.msgType == MSG_TYPE.ERR ? titles.ERR : state.msgType == MSG_TYPE.WARN ? titles.WARN : titles.INFO} titleText={state.msgType == MSG_TYPE.ERR ? titles.ERR : state.msgType == MSG_TYPE.WARN ? titles.WARN : titles.INFO}
okBtn={true} okBtn={true}
@ -141,8 +134,6 @@ export const MessagingContext = ({ titles, texts, buttons, children }) => {
cancelBtn={state.msgType == MSG_TYPE.WARN} cancelBtn={state.msgType == MSG_TYPE.WARN}
onCancel={handleMessageCancelClick} onCancel={handleMessageCancelClick}
cancelBtnCaption={buttons.CANCEL} cancelBtnCaption={buttons.CANCEL}
showErrMoreCaption={buttons.DETAIL}
hideErrMoreCaption={buttons.HIDE}
/> />
) : null} ) : null}
{children} {children}

View File

@ -35,7 +35,6 @@ const INITIAL_STATE = {
msg: false, msg: false,
msgType: MSG_TYPE.ERR, msgType: MSG_TYPE.ERR,
msgText: null, msgText: null,
msgFullErrorText: null,
msgOnOk: null, msgOnOk: null,
msgOnCancel: null msgOnCancel: null
}; };
@ -60,7 +59,6 @@ const handlers = {
msg: true, msg: true,
msgType: payload.type || MSG_TYPE.APP_ERR, msgType: payload.type || MSG_TYPE.APP_ERR,
msgText: payload.text, msgText: payload.text,
msgFullErrorText: payload.fullErrorText,
msgOnOk: payload.msgOnOk, msgOnOk: payload.msgOnOk,
msgOnCancel: payload.msgOnCancel msgOnCancel: payload.msgOnCancel
}), }),

View File

@ -41,7 +41,7 @@ export const NavigationContext = ({ children }) => {
const navigate = useNavigate(); const navigate = useNavigate();
//Подключение к контексту приложения //Подключение к контексту приложения
const { findPanelByName, setAppBarTitle } = useContext(ApplicationСtx); const { findPanelByName } = useContext(ApplicationСtx);
//Проверка наличия параметров запроса //Проверка наличия параметров запроса
const isNavigationSearch = () => (location.search ? true : false); const isNavigationSearch = () => (location.search ? true : false);
@ -65,8 +65,6 @@ export const NavigationContext = ({ children }) => {
const navigateTo = ({ path, search, state, replace = false }) => { const navigateTo = ({ path, search, state, replace = false }) => {
//Если указано куда переходить //Если указано куда переходить
if (path) { if (path) {
//Сброс кастомного заголовка
setAppBarTitle("");
//Переходим к адресу //Переходим к адресу
if (state) navigate(path, { state: JSON.stringify(state), replace }); if (state) navigate(path, { state: JSON.stringify(state), replace });
else navigate({ pathname: path, search: queryString.stringify(search), replace }); else navigate({ pathname: path, search: queryString.stringify(search), replace });

View File

@ -34,7 +34,6 @@ const ERR_APPSERVER = "Ошибка сервера приложений"; //Об
const ERR_UNEXPECTED = "Неожиданный ответ сервера"; //Неожиданный ответ сервера const ERR_UNEXPECTED = "Неожиданный ответ сервера"; //Неожиданный ответ сервера
const ERR_NETWORK = "Ошибка соединения с сервером"; //Ошибка сети const ERR_NETWORK = "Ошибка соединения с сервером"; //Ошибка сети
const ERR_UNAUTH = "Сеанс завершен. Пройдите аутентификацию повторно."; //Ошибка аутентификации const ERR_UNAUTH = "Сеанс завершен. Пройдите аутентификацию повторно."; //Ошибка аутентификации
const ERR_ABORTED = "Запрос прерван принудительно";
//----------- //-----------
//Тело модуля //Тело модуля
@ -77,16 +76,7 @@ const getRespErrMessage = resp => (isRespErr(resp) && resp.SMESSAGE ? resp.SMESS
const getRespPayload = resp => (resp && resp.XPAYLOAD ? resp.XPAYLOAD : null); const getRespPayload = resp => (resp && resp.XPAYLOAD ? resp.XPAYLOAD : null);
//Исполнение действия на сервере //Исполнение действия на сервере
const executeAction = async ({ const executeAction = async ({ serverURL, action, payload = {}, isArray, transformTagName, tagValueProcessor, attributeValueProcessor } = {}) => {
serverURL,
action,
payload = {},
isArray,
transformTagName,
tagValueProcessor,
attributeValueProcessor,
signal = null
} = {}) => {
console.log(`EXECUTING ${action ? action : ""} ON ${serverURL} WITH PAYLOAD:`); console.log(`EXECUTING ${action ? action : ""} ON ${serverURL} WITH PAYLOAD:`);
console.log(payload ? payload : "NO PAYLOAD"); console.log(payload ? payload : "NO PAYLOAD");
let response = null; let response = null;
@ -102,14 +92,11 @@ const executeAction = async ({
body: await buildXML(rqBody), body: await buildXML(rqBody),
headers: { headers: {
"content-type": "application/xml" "content-type": "application/xml"
}, }
...(signal ? { signal } : {})
}); });
} catch (e) { } catch (e) {
//Прервано принудительно
if (signal?.aborted === true) throw new Error(ERR_ABORTED);
//Сетевая ошибка //Сетевая ошибка
else throw new Error(`${ERR_NETWORK}: ${e.message || "неопределённая ошибка"}`); throw new Error(`${ERR_NETWORK}: ${e.message}`);
} }
//Проверим на наличие ошибок HTTP - если есть вернём их //Проверим на наличие ошибок HTTP - если есть вернём их
if (!response.ok) throw new Error(`${ERR_APPSERVER}: ${response.statusText}`); if (!response.ok) throw new Error(`${ERR_APPSERVER}: ${response.statusText}`);
@ -149,8 +136,7 @@ const executeStored = async ({
tagValueProcessor, tagValueProcessor,
attributeValueProcessor, attributeValueProcessor,
throwError = true, throwError = true,
spreadOutArguments = false, spreadOutArguments = false
signal = null
} = {}) => { } = {}) => {
let res = null; let res = null;
try { try {
@ -171,8 +157,7 @@ const executeStored = async ({
payload: { SSTORED: stored, XARGUMENTS: serverArgs, SRESP_ARG: respArg }, payload: { SSTORED: stored, XARGUMENTS: serverArgs, SRESP_ARG: respArg },
isArray, isArray,
tagValueProcessor, tagValueProcessor,
attributeValueProcessor, attributeValueProcessor
signal
}); });
if (spreadOutArguments === true && Array.isArray(res?.XPAYLOAD?.XOUT_ARGUMENTS)) { if (spreadOutArguments === true && Array.isArray(res?.XPAYLOAD?.XOUT_ARGUMENTS)) {
let spreadArgs = {}; let spreadArgs = {};
@ -208,11 +193,6 @@ const getConfig = async ({ throwError = true } = {}) => {
//---------------- //----------------
export default { export default {
ERR_APPSERVER,
ERR_UNEXPECTED,
ERR_NETWORK,
ERR_UNAUTH,
ERR_ABORTED,
SERV_DATA_TYPE_STR, SERV_DATA_TYPE_STR,
SERV_DATA_TYPE_NUMB, SERV_DATA_TYPE_NUMB,
SERV_DATA_TYPE_DATE, SERV_DATA_TYPE_DATE,

View File

@ -33,42 +33,34 @@ const DISPLAY_SIZE = {
//Типовые пути конвертации в массив (при переводе XML -> JSON) //Типовые пути конвертации в массив (при переводе XML -> JSON)
const XML_ALWAYS_ARRAY_PATHS = [ const XML_ALWAYS_ARRAY_PATHS = [
"XRESPOND.XPAYLOAD.XOUT_ARGUMENTS", "XRESPOND.XPAYLOAD.XOUT_ARGUMENTS",
"XRESPOND.XPAYLOAD.XDATA_GRID.rows", "XRESPOND.XPAYLOAD.XROWS",
"XRESPOND.XPAYLOAD.XDATA_GRID.columnsDef", "XRESPOND.XPAYLOAD.XCOLUMNS_DEF",
"XRESPOND.XPAYLOAD.XDATA_GRID.columnsDef.values", "XRESPOND.XPAYLOAD.XCOLUMNS_DEF.values",
"XRESPOND.XPAYLOAD.XDATA_GRID.groups", "XRESPOND.XPAYLOAD.XGROUPS",
"XRESPOND.XPAYLOAD.XGANTT.taskAttributes", "XRESPOND.XPAYLOAD.XGANTT_DEF.taskAttributes",
"XRESPOND.XPAYLOAD.XGANTT.taskColors", "XRESPOND.XPAYLOAD.XGANTT_DEF.taskColors",
"XRESPOND.XPAYLOAD.XGANTT.tasks", "XRESPOND.XPAYLOAD.XGANTT_TASKS",
"XRESPOND.XPAYLOAD.XGANTT.tasks.dependencies", "XRESPOND.XPAYLOAD.XGANTT_TASKS.dependencies",
"XRESPOND.XPAYLOAD.XCHART.labels", "XRESPOND.XPAYLOAD.XCHART.labels",
"XRESPOND.XPAYLOAD.XCHART.datasets", "XRESPOND.XPAYLOAD.XCHART.datasets",
"XRESPOND.XPAYLOAD.XCHART.datasets.data", "XRESPOND.XPAYLOAD.XCHART.datasets.data",
"XRESPOND.XPAYLOAD.XCHART.datasets.items", "XRESPOND.XPAYLOAD.XCHART.datasets.items"
"XRESPOND.XPAYLOAD.XCYCLOGRAM.taskAttributes",
"XRESPOND.XPAYLOAD.XCYCLOGRAM.columns",
"XRESPOND.XPAYLOAD.XCYCLOGRAM.groups",
"XRESPOND.XPAYLOAD.XCYCLOGRAM.tasks"
]; ];
//Типовые шаблоны конвертации в массив (при переводе XML -> JSON) //Типовые шаблоны конвертации в массив (при переводе XML -> JSON)
const XML_ALWAYS_ARRAY_PATH_PATTERNS = [ const XML_ALWAYS_ARRAY_PATH_PATTERNS = [
/(.*)XDATA_GRID.rows$/, /(.*)XROWS$/,
/(.*)XDATA_GRID.columnsDef$/, /(.*)XCOLUMNS_DEF$/,
/(.*)XDATA_GRID.columnsDef.values$/, /(.*)XCOLUMNS_DEF.values$/,
/(.*)XDATA_GRID.groups$/, /(.*)XGROUPS$/,
/(.*)XGANTT.taskAttributes$/, /(.*)XGANTT_DEF.taskAttributes$/,
/(.*)XGANTT.taskColors$/, /(.*)XGANTT_DEF.taskColors$/,
/(.*)XGANTT.tasks$/, /(.*)XGANTT_TASKS$/,
/(.*)XGANTT.tasks.dependencies$/, /(.*)XGANTT_TASKS.dependencies$/,
/(.*)XCHART.labels$/, /(.*)XCHART.labels$/,
/(.*)XCHART.datasets$/, /(.*)XCHART.datasets$/,
/(.*)XCHART.datasets.data$/, /(.*)XCHART.datasets.data$/,
/(.*)XCHART.datasets.items$/, /(.*)XCHART.datasets.items$/
/(.*)XCYCLOGRAM.taskAttributes$/,
/(.*)XCYCLOGRAM.columns$/,
/(.*)XCYCLOGRAM.groups$/,
/(.*)XCYCLOGRAM.tasks$/
]; ];
//Типовой постфикс тега для массива (при переводе XML -> JSON) //Типовой постфикс тега для массива (при переводе XML -> JSON)
@ -76,13 +68,11 @@ const XML_ALWAYS_ARRAY_POSTFIX = "__SYSTEM__ARRAY__";
//Типовые шаблоны конвертации значения атрибута в строку (при переводе XML -> JSON) //Типовые шаблоны конвертации значения атрибута в строку (при переводе XML -> JSON)
const XML_ATTR_ALWAYS_STR_PATH_PATTERNS = [ const XML_ATTR_ALWAYS_STR_PATH_PATTERNS = [
/(.*)XDATA_GRID.columnsDef.name$/, /(.*)XCOLUMNS_DEF.name$/,
/(.*)XDATA_GRID.columnsDef.caption$/, /(.*)XCOLUMNS_DEF.caption$/,
/(.*)XDATA_GRID.columnsDef.parent$/, /(.*)XCOLUMNS_DEF.parent$/,
/(.*)XDATA_GRID.groups.name$/, /(.*)XGROUPS.name$/,
/(.*)XDATA_GRID.groups.caption$/, /(.*)XGROUPS.caption$/
/(.*)XCYCLOGRAM.columns.name$/,
/(.*)XCYCLOGRAM.groups.name$/
]; ];
//----------- //-----------
@ -102,11 +92,12 @@ const getDisplaySize = () => {
}; };
//Глубокое копирование объекта //Глубокое копирование объекта
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj))); const deepCopyObject = obj => JSON.parse(JSON.stringify(obj));
//Конвертация объекта в Base64 XML //Конвертация объекта в Base64 XML
const object2Base64XML = (obj, builderOptions) => { const object2Base64XML = (obj, builderOptions) => {
const builder = new XMLBuilder(builderOptions); const builder = new XMLBuilder(builderOptions);
//onOrderChanged({ orders: btoa(ordersBuilder.build(newOrders)) });
return btoa(unescape(encodeURIComponent(builder.build(obj)))); return btoa(unescape(encodeURIComponent(builder.build(obj))));
}; };
@ -149,43 +140,12 @@ const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attrib
//Форматирование даты в формат РФ //Форматирование даты в формат РФ
const formatDateRF = value => (value ? dayjs(value).format("DD.MM.YYYY") : null); const formatDateRF = value => (value ? dayjs(value).format("DD.MM.YYYY") : null);
//Форматирование даты и времени в формат РФ
const formatDateTimeRF = value => (value ? dayjs(value).format("DD.MM.YYYY HH:mm:ss") : null);
//Форматирование даты в формат JSON (только дата, без времени) //Форматирование даты в формат JSON (только дата, без времени)
const formatDateJSONDateOnly = value => (value ? dayjs(value).format("YYYY-MM-DD") : null); const formatDateJSONDateOnly = value => (value ? dayjs(value).format("YYYY-MM-DD") : null);
//Форматирование числа в "Денежном" формате РФ //Форматирование числа в "Денежном" формате РФ
const formatNumberRFCurrency = value => (hasValue(value) ? new Intl.NumberFormat("ru-RU", { minimumFractionDigits: 2 }).format(value) : null); const formatNumberRFCurrency = value => (hasValue(value) ? new Intl.NumberFormat("ru-RU", { minimumFractionDigits: 2 }).format(value) : null);
//Форматирование текста ошибки
const formatErrorMessage = errorMsg => {
//Инициализируем текст заголовка ошибки
let text = "";
//Пробуем извлечь заголовок текста ошибки
try {
//Если это ошибка Oracle
if (errorMsg.match(/^ORA-/)) {
//Считываем первую строку с заголовочным текстом ошибки
text = errorMsg.match(/^.*(?=(\nORA-))/)[0];
//Убираем лишнюю информацию и пробелы
text = text.replace(/ORA-\d*:/g, "").trim();
}
//Если это ошибка PG
if (errorMsg.match(/^SQL Error/)) {
//Считываем первую строку с заголовочным текстом ошибки
text = errorMsg.match(/.*(?=(\n.*Where)|(.*Where))/)[0];
//Убираем лишнюю информацию и пробелы
text = text.replace(/SQL Error \[\d*\]: ERROR:/g, "").trim();
}
} catch {
//Если произошла ошибка - оставляем полный текст ошибки
text = errorMsg;
}
//Возвращаем результат
return { text: text || errorMsg, fullErrorText: text ? errorMsg : null };
};
//Формирование уникального идентификатора //Формирование уникального идентификатора
const genGUID = () => const genGUID = () =>
"10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
@ -203,9 +163,7 @@ export {
object2Base64XML, object2Base64XML,
xml2JSON, xml2JSON,
formatDateRF, formatDateRF,
formatDateTimeRF,
formatDateJSONDateOnly, formatDateJSONDateOnly,
formatNumberRFCurrency, formatNumberRFCurrency,
formatErrorMessage,
genGUID genGUID
}; };

View File

@ -119,8 +119,8 @@ const EqsPrfrm = () => {
let cF = 0; let cF = 0;
let sF = 0; let sF = 0;
let properties = []; let properties = [];
if (data.XDATA_GRID.rows != null) { if (data.XROWS != null) {
data.XDATA_GRID.rows.map(row => { data.XROWS.map(row => {
properties = []; properties = [];
Object.entries(row).forEach(([key, value]) => properties.push({ name: key, data: value })); Object.entries(row).forEach(([key, value]) => properties.push({ name: key, data: value }));
let info2 = properties.find(element => { let info2 = properties.find(element => {
@ -156,10 +156,11 @@ const EqsPrfrm = () => {
} }
setDataGrid(pv => ({ setDataGrid(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: [...(data.XROWS || [])],
rows: [...(data.XDATA_GRID.rows || [])], fixedHeader: data.XDATA_GRID.fixedHeader,
groups: [...(data.XDATA_GRID.groups || [])], fixedColumns: data.XDATA_GRID.fixedColumns,
groups: [...(data.XGROUPS || [])],
dataLoaded: true, dataLoaded: true,
reload: false reload: false
})); }));

View File

@ -15,7 +15,6 @@ import { P8PSVG } from "../../../components/p8p_svg"; //Интерактивны
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useCostProductComposition, useProductDetailsTable } from "../hooks"; //Вспомогательные хуки import { useCostProductComposition, useProductDetailsTable } from "../hooks"; //Вспомогательные хуки
import { ProgressBox } from "./progress_box"; //Информация по прогрессу объекта import { ProgressBox } from "./progress_box"; //Информация по прогрессу объекта
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
//--------- //---------
//Константы //Константы
@ -27,7 +26,6 @@ const STYLES = {
border: "1px solid", border: "1px solid",
borderRadius: "25px", borderRadius: "25px",
height: "35vh", height: "35vh",
minHeight: "250px",
backgroundColor: "background.detail_table" backgroundColor: "background.detail_table"
}, },
BOX_INFO_SUB: isMessage => ({ BOX_INFO_SUB: isMessage => ({
@ -49,7 +47,6 @@ const STYLES = {
border: "1px solid", border: "1px solid",
borderRadius: "25px", borderRadius: "25px",
height: "17vh", height: "17vh",
minHeight: "120px",
backgroundColor: "background.detail_info" backgroundColor: "background.detail_info"
}, },
PRODUCT_SELECTOR_CONTAINER: { PRODUCT_SELECTOR_CONTAINER: {
@ -60,7 +57,6 @@ const STYLES = {
border: "1px solid", border: "1px solid",
borderRadius: "25px", borderRadius: "25px",
height: "53vh", height: "53vh",
minHeight: "379px",
marginTop: "16px", marginTop: "16px",
backgroundColor: "background.product_selector" backgroundColor: "background.product_selector"
}, },
@ -76,12 +72,7 @@ const STYLES = {
width: "280px", width: "280px",
borderBottom: "1px solid" borderBottom: "1px solid"
}, },
TABLE_DETAILS: { TABLE_DETAILS: { backgroundColor: "background.detail_table", height: "240px" },
backgroundColor: "background.detail_table",
height: "28vh",
minHeight: "187px",
...APP_STYLES.SCROLL
},
TABLE_DETAILS_HEADER_CELL: maxWidth => ({ TABLE_DETAILS_HEADER_CELL: maxWidth => ({
backgroundColor: "background.detail_table", backgroundColor: "background.detail_table",
color: "text.detail_table.fontColor", color: "text.detail_table.fontColor",
@ -116,7 +107,7 @@ const PlanSpecInfo = ({ planSpec }) => {
<Box sx={STYLES.PLAN_INFO_MAIN}> <Box sx={STYLES.PLAN_INFO_MAIN}>
<Box sx={STYLES.PLAN_INFO_SUB}> <Box sx={STYLES.PLAN_INFO_SUB}>
<Typography variant="PlanSpecInfo" mt={1}> <Typography variant="PlanSpecInfo" mt={1}>
Номер заказа: Номер борта:
</Typography> </Typography>
<Typography variant="subtitle2">{planSpec.SNUMB}</Typography> <Typography variant="subtitle2">{planSpec.SNUMB}</Typography>
</Box> </Box>

View File

@ -70,11 +70,11 @@ const PlanSpecsListItem = ({ card, cardIndex, onClick }) => {
return ( return (
<Box sx={STYLES.CONTAINER} onClick={() => (onClick ? onClick(card, cardIndex) : null)}> <Box sx={STYLES.CONTAINER} onClick={() => (onClick ? onClick(card, cardIndex) : null)}>
<PlanSpecsListItemImage card={card} /> <PlanSpecsListItemImage card={card} />
<Box textAlign="center" height="70px"> <Box textAlign="center">
<Typography variant="PlanSpecInfo" sx={STYLES.TEXT_INFO_FIELD}> <Typography variant="PlanSpecInfo" sx={STYLES.TEXT_INFO_FIELD}>
Номер заказа Номер борта
</Typography> </Typography>
<Typography variant="h2">{card.SNUMB || "-"}</Typography> <Typography variant="h2">{card.SNUMB}</Typography>
</Box> </Box>
<ProgressBox <ProgressBox
progress={card.NPROGRESS} progress={card.NPROGRESS}
@ -84,12 +84,12 @@ const PlanSpecsListItem = ({ card, cardIndex, onClick }) => {
progressVariant={"h3"} progressVariant={"h3"}
detailVariant={"PlanSpecProgressDetail"} detailVariant={"PlanSpecProgressDetail"}
/> />
<Box height="70px"> <Box>
<Typography variant="PlanSpecInfo" sx={STYLES.TEXT_INFO_FIELD}> <Typography variant="PlanSpecInfo" sx={STYLES.TEXT_INFO_FIELD}>
Год выпуска: Год выпуска:
</Typography> </Typography>
<Typography variant="subtitle1" mt={-1}> <Typography variant="subtitle1" mt={-1}>
{card.NYEAR || "-"} {card.NYEAR}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>

View File

@ -195,10 +195,9 @@ const useProductDetailsTable = (planSpec, product, orders, pageNumber, stored) =
}); });
setData(pv => ({ setData(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])], morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE,
morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE,
init: true init: true
})); }));
} finally { } finally {

View File

@ -27,7 +27,6 @@ import {
Icon Icon
} from "@mui/material"; //Интерфейсные элементы } from "@mui/material"; //Интерфейсные элементы
import { ThemeProvider } from "@mui/material/styles"; //Подключение темы import { ThemeProvider } from "@mui/material/styles"; //Подключение темы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { PlanSpecsList } from "./components/plans_list"; //Список планов import { PlanSpecsList } from "./components/plans_list"; //Список планов
import { PlanSpecDetail } from "./components/plan_detail"; //Детали плана import { PlanSpecDetail } from "./components/plan_detail"; //Детали плана
import { lightTheme, darkTheme } from "./styles/themes"; //Стиль темы import { lightTheme, darkTheme } from "./styles/themes"; //Стиль темы
@ -64,8 +63,7 @@ const STYLES = {
display: "inline-block", display: "inline-block",
boxSizing: "border-box", boxSizing: "border-box",
backgroundColor: "background.plans_drawer_paper", backgroundColor: "background.plans_drawer_paper",
color: "text.plans_finder.fontColor", color: "text.plans_finder.fontColor"
...APP_STYLES.SCROLL
} }
}, },
PLANS_LIST_BOX: { paddingTop: "20px" }, PLANS_LIST_BOX: { paddingTop: "20px" },
@ -242,32 +240,26 @@ const MechRecAssemblyMon = () => {
</Stack> </Stack>
{state.init == true ? ( {state.init == true ? (
state.selectedPlanCtlg.NRN ? ( state.selectedPlanCtlg.NRN ? (
state.planSpecs.length !== 0 ? ( <>
<> <Typography variant="h3" sx={STYLES.MAIN_TITLE} pb={2}>
<Typography variant="h3" sx={STYLES.MAIN_TITLE} pb={2}> {title}
{title}
</Typography>
{state.planSpecsLoaded == true ? (
state.selectedPlanSpec.NRN ? (
<PlanSpecDetail
planSpec={state.selectedPlanSpec}
disableNavigatePrev={planDetailNavigation.disableNavigatePrev}
disableNavigateNext={planDetailNavigation.disableNavigateNext}
onNavigate={handlePlanDetailNavigateClick}
onBack={handlePlanDetailBackClick}
/>
) : (
<Box sx={STYLES.PLANS_LIST_BOX}>
<PlanSpecsList planSpecs={state.planSpecs} onItemClick={handlePlanClick} />
</Box>
)
) : null}
</>
) : (
<Typography variant="h4" sx={STYLES.MAIN_TITLE}>
В каталоге планов отсутствуют записи подходящих первичных документов
</Typography> </Typography>
) {state.planSpecsLoaded == true ? (
state.selectedPlanSpec.NRN ? (
<PlanSpecDetail
planSpec={state.selectedPlanSpec}
disableNavigatePrev={planDetailNavigation.disableNavigatePrev}
disableNavigateNext={planDetailNavigation.disableNavigateNext}
onNavigate={handlePlanDetailNavigateClick}
onBack={handlePlanDetailBackClick}
/>
) : (
<Box sx={STYLES.PLANS_LIST_BOX}>
<PlanSpecsList planSpecs={state.planSpecs} onItemClick={handlePlanClick} />
</Box>
)
) : null}
</>
) : ( ) : (
<Typography variant="h4" sx={STYLES.MAIN_TITLE}> <Typography variant="h4" sx={STYLES.MAIN_TITLE}>
Укажите каталог планов для отображения спецификаций Укажите каталог планов для отображения спецификаций

View File

@ -150,13 +150,12 @@ const useCostJobsSpecs = task => {
}); });
setCostJobsSpecs(pv => ({ setCostJobsSpecs(pv => ({
...pv, ...pv,
...data.XDATA_GRID,
task: task, task: task,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])], rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE
})); }));
}; };
loadData(); loadData();
@ -257,13 +256,12 @@ const useEquipConfiguration = (task, fromAction) => {
}); });
setEquipConfiguration(pv => ({ setEquipConfiguration(pv => ({
...pv, ...pv,
...data.XDATA_GRID,
task: task, task: task,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])], rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE
})); }));
}; };
loadData(); loadData();

View File

@ -10,7 +10,6 @@
import React, { useContext, useState } from "react"; //Классы React import React, { useContext, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField } from "@mui/material"; //Интерфейсные элементы import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField } from "@mui/material"; //Интерфейсные элементы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { CostJobsSpecsDataGrid } from "./fcjobssp"; //Собственные хуки таблиц import { CostJobsSpecsDataGrid } from "./fcjobssp"; //Собственные хуки таблиц
import { useCostJobs, useFilteredFcjobs } from "./hooks"; //Вспомогательные хуки import { useCostJobs, useFilteredFcjobs } from "./hooks"; //Вспомогательные хуки
@ -36,7 +35,7 @@ const STYLES = {
width: "350px", width: "350px",
display: "inline-block", display: "inline-block",
flexShrink: 0, flexShrink: 0,
[`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box", ...APP_STYLES.SCROLL } [`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box" }
}, },
CONTAINER: { textAlign: "center" } CONTAINER: { textAlign: "center" }
}; };

View File

@ -1,281 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
Кастомные хуки
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useCallback, useEffect, useContext } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Размер страницы данных
const DATA_GRID_PAGE_SIZE = 50;
//---------------------------------------------
//Вспомогательные функции форматирования данных
//---------------------------------------------
//-----------
//Тело модуля
//-----------
//Хук для основной таблицы
const useCostJobs = () => {
//Собственное состояние - таблица данных
const [state, setState] = useState({
init: false,
loaded: false,
jobInfo: {},
haveNote: false,
coeff: "1.0"
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Подключение к контексту навигации
const { getNavigationSearch } = useContext(NavigationCtx);
//При подключении компонента к странице
useEffect(() => {
const initJob = async fcJob => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBS_MP_INIT",
args: { NFCJOBS: parseInt(fcJob) },
respArg: "COUT",
attributeValueProcessor: (name, val) => (["NHAVE_NOTE"].includes(name) ? val == 1 : val)
});
setState(pv => ({
...pv,
init: true,
jobInfo: data.XFCJOBS ? data.XFCJOBS : {},
loaded: true
}));
};
if (!state.init) {
//Считаем параметры, переданные из действия
const actionPrms = getNavigationSearch();
//Иницализируем сменное задание
initJob(actionPrms.NRN);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return [state, setState];
};
//Хук для таблицы операций
const useCostJobsSpecs = task => {
//Собственное состояние - таблица данных
const [costJobsSpecs, setCostJobsSpecs] = useState({
task: null,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
selectedRow: {},
reload: true,
pageNumber: 1,
morePages: true,
fixedHeader: false,
fixedColumns: 0
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//Выдача задания
const issueCostJobsSpecs = useCallback(
async prms => {
try {
await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBSSP_MP_ISSUE",
args: { NFCJOBS: prms.NFCJOBS, NCOEFF: parseFloat(prms.NCOEFF) }
});
} catch (e) {
throw new Error(e.message);
}
},
[executeStored]
);
//При необходимости обновить данные таблицы
useEffect(() => {
//Если изменилось сменное задание - обновляем состояние
if (costJobsSpecs.dataLoaded && costJobsSpecs.task !== task) {
setCostJobsSpecs(pv => ({
...pv,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
selectedRow: {},
reload: true,
pageNumber: 1,
morePages: true
}));
}
//Если необходимо перезагрузить
if (costJobsSpecs.reload && task) {
const loadData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBSSP_MP_DG_GET",
args: {
NFCJOBS: task,
NPAGE_NUMBER: costJobsSpecs.pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
CORDERS: { VALUE: object2Base64XML(costJobsSpecs.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NINCLUDE_DEF: costJobsSpecs.dataLoaded ? 0 : 1
},
respArg: "COUT",
attributeValueProcessor: (name, val) =>
name === "NSELECT" ? val === 1 : name === "SWORKERS_LIST" ? (val ? val.split(",").map(Number) : []) : val
});
setCostJobsSpecs(pv => ({
...pv,
...data.XDATA_GRID,
task: task,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true,
reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE
}));
};
loadData();
}
}, [
SERV_DATA_TYPE_CLOB,
costJobsSpecs.dataLoaded,
costJobsSpecs.orders,
costJobsSpecs.pageNumber,
costJobsSpecs.reload,
costJobsSpecs.task,
executeStored,
task
]);
return [costJobsSpecs, setCostJobsSpecs, issueCostJobsSpecs];
};
//Хук для рабочих
const useCostJobsWorkers = task => {
//Собственное состояние - таблица данных
const [costJobsWorkers, setCostJobsWorkers] = useState({
task: null,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
selectedRows: [],
reload: true,
pageNumber: 1,
morePages: true
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//Включение рабочего в строку сменного задания
const includeWorker = useCallback(
async prms => {
try {
await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBSSP_MP_INC_PERFORM",
args: {
NFCJOBSSP: prms.NFCJOBSSP,
SPERFORM_LIST: {
VALUE: Array.isArray(prms.SELECTED_WORKERS) ? prms.SELECTED_WORKERS.join(";") : prms.SELECTED_WORKERS,
SDATA_TYPE: SERV_DATA_TYPE_CLOB
},
NQUANT_PLAN: prms.NQUANT_PLAN
}
});
} catch (e) {
throw new Error(e.message);
}
},
[SERV_DATA_TYPE_CLOB, executeStored]
);
//Исключение рабочего из строки сменного задания
const excludeWorker = useCallback(
async prms => {
try {
await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBSSP_MP_EXC_PERFORM",
args: { NFCJOBSSP: prms.NFCJOBSSP }
});
} catch (e) {
throw new Error(e.message);
}
},
[executeStored]
);
//При необходимости обновить данные таблицы
useEffect(() => {
//Если изменилось сменное задание - обновляем состояние
if (costJobsWorkers.dataLoaded && costJobsWorkers.task !== task) {
setCostJobsWorkers(pv => ({
...pv,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
selectedRows: [],
reload: true,
pageNumber: 1,
morePages: true
}));
}
//Если необходимо перезагрузить
if (costJobsWorkers.reload && task) {
const loadData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.WORKERS_MP_DG_GET",
args: {
NFCJOBS: task,
NPAGE_NUMBER: costJobsWorkers.pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
CORDERS: { VALUE: object2Base64XML(costJobsWorkers.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NINCLUDE_DEF: costJobsWorkers.dataLoaded ? 0 : 1
},
respArg: "COUT",
attributeValueProcessor: (name, val) => (["NSELECT"].includes(name) ? val === 1 : val)
});
setCostJobsWorkers(pv => ({
...pv,
...data.XDATA_GRID,
task: task,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true,
reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE
}));
};
loadData();
}
}, [
SERV_DATA_TYPE_CLOB,
costJobsWorkers.dataLoaded,
costJobsWorkers.orders,
costJobsWorkers.pageNumber,
costJobsWorkers.reload,
costJobsWorkers.task,
executeStored,
task
]);
return [costJobsWorkers, setCostJobsWorkers, includeWorker, excludeWorker];
};
export { useCostJobs, useCostJobsSpecs, useCostJobsWorkers };

View File

@ -1,16 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
Панель мониторинга: Точка входа
*/
//---------------------
//Подключение библиотек
//---------------------
import { MechRecCostJobs } from "./mech_rec_cost_jobs_manage_mp"; //Корневая панель выдачи сменного задания на участок
//----------------
//Интерфейс модуля
//----------------
export const RootClass = MechRecCostJobs;

View File

@ -1,484 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
Панель мониторинга: Корневая панель выдачи сменного задания на участок
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import { Grid, Box, Typography, Checkbox, Icon, Stack, Button, Tooltip, TextField } from "@mui/material"; //Интерфейсные элементы
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_MORE_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useCostJobs, useCostJobsSpecs, useCostJobsWorkers } from "./hooks"; //Вспомогательные хуки
import { CostJobsSpecsInclude } from "./worker_include_dialog"; //Компонент диалога включения в задание
import { hasValue } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Мнемокод раздела операций
const UNIT_COST_JOBS_SPECS = "CostJobsSpecs";
//Мнемокод раздела исполнений должности
const UNIT_WORKERS = "ClientPostPerform";
//Высота основного заголовка
const MAIN_HEADER_HEIGHT = "35px";
//Высота подзаголовка
const SUB_HEADER_HEIGHT = "35px";
//Высота заголовка таблицы
const TABLE_HEADER_HEIGHT = "35px";
//Высота панели кнопок таблицы
const TABLE_BUTTONS_HEIGHT = "35px";
//Отступ таблицы
const TABLE_PADDING_TOP = "15px";
//Формат для коэффициент выполнения норм
const issueCoeffFormat = /^(?!.*\..*\.)[0-9]{0,3}(\.[0-9]{0,1})?$/;
//Стили
const STYLES = {
MAIN_HEADER: { height: MAIN_HEADER_HEIGHT, overflow: "hidden" },
SUB_HEADER: { height: SUB_HEADER_HEIGHT, overflow: "hidden" },
CONTAINER: { textAlign: "center" },
TABLE: { paddingTop: TABLE_PADDING_TOP },
TABLE_HEADER: { height: TABLE_HEADER_HEIGHT, overflow: "hidden" },
TABLE_BUTTONS: { display: "flex", justifyContent: "flex-end", height: TABLE_BUTTONS_HEIGHT, overflow: "hidden", alignItems: "flex-end" },
DATA_GRID_CONTAINER: morePages => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${MAIN_HEADER_HEIGHT} - ${SUB_HEADER_HEIGHT} - ${TABLE_HEADER_HEIGHT} - ${TABLE_BUTTONS_HEIGHT} - ${TABLE_PADDING_TOP} - 32px - ${
morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"
})`,
...APP_STYLES.SCROLL
})
};
//Цвета
const colors = {
LINKED: "#bce0de",
UNAVAILABLE: "#949494",
WITH_WORKER: "#82df83"
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Проверка правильности значения коэффициент выполнения норм
const isValidIssueCoeff = value => {
return issueCoeffFormat.test(value);
};
//Форматирование значения ячейки
const dataCellRender = ({ row, columnDef, handleSelectChange, sUnit, selectedWorkerRows = [], selectedJobSpec }) => {
//Стиль
let cellStyle = {};
//Если это рабочие
if (sUnit === UNIT_WORKERS) {
//Признак недоступности
let disabled = true;
//Если в выбранной строке смены указан исполнитель факт
if (selectedJobSpec.NPERFORM_FACT) {
//Если это текущей исполнитель
if (selectedJobSpec.SWORKERS_LIST.includes(row["NRN"])) {
//Подсвечиваем строку рабочего
cellStyle = { backgroundColor: colors.LINKED };
}
} else {
//Если выбрана строка смены
if (selectedJobSpec.NRN) {
//Если текущий рабочий может принять задание
if (row["NLOADING"] < 100) {
//Подсвечиваем строку рабочего
cellStyle = { backgroundColor: colors.LINKED };
disabled = false;
}
}
}
//Если уже выбрано достаточное количество рабочих и текущий рабочий не отмечен
if (selectedJobSpec.NRESOURCE_NUMB === selectedWorkerRows.length && !selectedWorkerRows.includes(row["NRN"])) {
//Устанавливаем признак недоступности
disabled = true;
}
//Если загрузка рабочего больше 100
if (row["NLOADING"] >= 100) {
//Если поле не поле выбора
if (columnDef.name !== "NSELECT") {
//Указываем, что рабочее место недоступно
cellStyle = { ...cellStyle, color: colors.UNAVAILABLE };
}
}
//Для колонки выбора
if (columnDef.name === "NSELECT") {
return {
cellStyle,
data: (
<Box sx={STYLES.CONTAINER}>
<Checkbox
disabled={disabled}
checked={selectedWorkerRows.includes(row["NRN"])}
onChange={() => handleSelectChange({ NRN: row["NRN"], SUNIT: sUnit, BFULL_LOADED: row["NLOADING"] >= 100 })}
/>
</Box>
)
};
}
//Отформатированная колонка
return {
cellStyle,
data: row[columnDef.name]
};
}
//Если это сменное задание
if (sUnit === UNIT_COST_JOBS_SPECS) {
//Если указан исполнитель факт
if (row["NPERFORM_FACT"]) {
//Подсвечиваем сменное задание зеленым
cellStyle = { ...cellStyle, backgroundColor: colors.WITH_WORKER };
}
//Для колонки выбора
if (columnDef.name === "NSELECT") {
return {
cellStyle,
data: (
<Box sx={STYLES.CONTAINER}>
<Checkbox
disabled={row["DBEG_FACT"] ? true : false}
checked={row["NRN"] === selectedJobSpec.NRN}
onChange={() =>
handleSelectChange({
NRN: row["NRN"],
SUNIT: sUnit,
NPERFORM_FACT: row["NPERFORM_FACT"],
NRESOURCE_NUMB: row["NRESOURCE_NUMB"],
NQUANT_PLAN: row["NQUANT_PLAN"],
SWORKERS_LIST: row["SWORKERS_LIST"]
})
}
/>
</Box>
)
};
}
//Отформатированная колонка
return {
cellStyle,
data: row[columnDef.name]
};
}
//Необрабатываемый раздел
return {
data: row[columnDef.name]
};
};
//Генерация представления ячейки заголовка группы
export const headCellRender = ({ columnDef }) => {
if (columnDef.name === "NSELECT") {
return {
stackStyle: { padding: "2px", justifyContent: "space-around" },
data: <Icon>done</Icon>
};
} else {
return {
stackStyle: { padding: "2px" },
data: columnDef.caption
};
}
};
//-----------
//Тело модуля
//-----------
//Корневая панель выдачи сменного задания на участок
const MechRecCostJobs = () => {
//Состояние диалога включения в задание
const [showInclude, setShowInclude] = useState(false);
//Состояние информации о сменном задании
const [state, setState] = useCostJobs();
//Состояние таблицы сменных заданий
const [costJobsSpecs, setCostJobsSpecs, issueCostJobsSpecs] = useCostJobsSpecs(state.jobInfo.NRN);
//Состояние таблицы рабочих
const [costJobsWorkers, setCostJobsWorkers, includeWorker, excludeWorker] = useCostJobsWorkers(state.jobInfo.NRN);
//При изменении состояния сортировки операций
const handleCostJobsSpecOrderChanged = ({ orders }) => setCostJobsSpecs(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
//При изменении количества отображаемых страниц операций
const handleCostJobsSpecPagesCountChanged = () => setCostJobsSpecs(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
//При изменении состояния сортировки рабочих
const handleCostJobsWorkersOrderChanged = ({ orders }) => setCostJobsWorkers(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
//При изменении количества отображаемых страниц рабочих
const handleCostJobsWorkersPagesCountChanged = () => setCostJobsWorkers(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
//При исключении рабочих из строки сменного задания
const handleCostJobsSpecExcludeWorker = () => {
//Делаем асинхронно, чтобы при ошибке ничего не обновлять
const excludeAsync = async () => {
//Исключаем рабочего из строки сменного задания
try {
await excludeWorker({
NFCJOBSSP: costJobsSpecs.selectedRow.NRN
});
//Необходимо обновить данные
setCostJobsSpecs(pv => ({ ...pv, selectedRow: {}, pageNumber: 1, reload: true }));
setCostJobsWorkers(pv => ({ ...pv, selectedRows: [], pageNumber: 1, reload: true }));
} catch (e) {
throw new Error(e.message);
}
};
//Исключаем рабочего асинхронно
excludeAsync();
};
//Выдача задания операции
const handleCostJobsSpecIssue = () => {
//Делаем асинхронно, чтобы при ошибке ничего не обновлять
const issueAsync = async () => {
//Включаем рабочих в операции
try {
await issueCostJobsSpecs({ NFCJOBS: state.jobInfo.NRN, NCOEFF: state.coeff });
//Необходимо обновить данные
setCostJobsSpecs(pv => ({ ...pv, selectedRow: {}, pageNumber: 1, reload: true }));
setCostJobsWorkers(pv => ({ ...pv, selectedRows: [], pageNumber: 1, reload: true }));
} catch (e) {
throw new Error(e.message);
}
};
//Выдаем задание асинхронно
issueAsync();
};
//При изменение состояния выбора
const handleSelectChange = prms => {
//Выбранный элемент
let selectedRow = null;
//Буфер для выбранных рабочих
let selectedWorkers = [];
//Индекс рабочего в списке выбранных
let workerIndex = null;
//Исходим от раздела
switch (prms.SUNIT) {
//Сменное задание
case UNIT_COST_JOBS_SPECS:
//Определяем это новое отмеченное сменное задание или сброс старого
selectedRow = costJobsSpecs.selectedRow.NRN ? (costJobsSpecs.selectedRow.NRN === prms.NRN ? null : prms.NRN) : prms.NRN;
//Актуализируем строки
setCostJobsSpecs(pv => ({
...pv,
selectedRow: selectedRow
? {
NRN: selectedRow,
NPERFORM_FACT: prms.NPERFORM_FACT,
NRESOURCE_NUMB: prms.NRESOURCE_NUMB,
NQUANT_PLAN: prms.NQUANT_PLAN,
SWORKERS_LIST: prms.SWORKERS_LIST
}
: { NRN: null, NPERFORM_FACT: null, NRESOURCE_NUMB: null, NQUANT_PLAN: null, SWORKERS_LIST: [] }
}));
//Выходим
break;
//Рабочие центры
case UNIT_WORKERS:
//Инициализируем рабочими центрами
selectedWorkers = costJobsWorkers.selectedRows || [];
//Определяем индекс элемента в массиве
workerIndex = selectedWorkers.indexOf(prms.NRN);
//Если такого рег. номера нет в списке - добавляем, иначе удаляем
workerIndex > -1 ? selectedWorkers.splice(workerIndex, 1) : selectedWorkers.push(prms.NRN);
//Актуализируем строки
setCostJobsWorkers(pv => ({ ...pv, selectedRows: selectedWorkers }));
//Выходим
break;
default:
return;
}
};
//При открытии/закрытии диалога добавления
const handleShowIncludeChange = needShow => setShowInclude(needShow);
//При изменении коэффициент выполнения норм
const handleIssueCoeffChange = e => {
isValidIssueCoeff(e.target.value) ? setState(pv => ({ ...pv, coeff: e.target.value })) : null;
};
return (
<Box p={2}>
{state.loaded ? (
<Box sx={STYLES.CONTAINER}>
<Typography
sx={STYLES.MAIN_HEADER}
variant={"h6"}
>{`Сменное задание №${state.jobInfo.SDOC_NUMB} на ${state.jobInfo.SPERIOD}`}</Typography>
<Typography sx={STYLES.SUB_HEADER} variant={"h6"}>{`${state.jobInfo.SSUBDIV}`}</Typography>
<Box sx={STYLES.CONTAINER}>
<Grid container spacing={2}>
<Grid item sx={STYLES.CONTAINER} xs={6}>
<Typography sx={STYLES.TABLE_HEADER} variant={"h6"} color={"text.secondary"}>
Сменное задание
</Typography>
{costJobsWorkers.dataLoaded ? (
<>
<Box sx={STYLES.TABLE_BUTTONS}>
<Stack direction={"row"} spacing={1}>
<Tooltip
title={
state.jobInfo.NHAVE_NOTE
? "Сменное задание имеет строку с примечанием"
: "Коэффициент выполнения норм"
}
>
<TextField
name="editIssueValue"
variant="outlined"
sx={{ width: "68px" }}
inputProps={{ sx: { padding: "4.2px 14px" } }}
size="small"
value={state.coeff}
onChange={handleIssueCoeffChange}
disabled={state.jobInfo.NHAVE_NOTE}
/>
</Tooltip>
<Tooltip
title={
state.jobInfo.NHAVE_NOTE
? "Сменное задание имеет строку с примечанием"
: !hasValue(state.coeff)
? "Не указано значение коэффициент выполнения норм"
: null
}
>
<Box>
<Button
variant="contained"
size="small"
disabled={state.jobInfo.NHAVE_NOTE || !hasValue(state.coeff)}
onClick={handleCostJobsSpecIssue}
>
Выдать задания
</Button>
</Box>
</Tooltip>
</Stack>
</Box>
<Box sx={STYLES.TABLE}>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER(costJobsSpecs.morePages), elevation: 4 }}
columnsDef={costJobsSpecs.columnsDef}
rows={costJobsSpecs.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
morePages={costJobsSpecs.morePages}
reloading={costJobsSpecs.reload}
onOrderChanged={handleCostJobsSpecOrderChanged}
onPagesCountChanged={handleCostJobsSpecPagesCountChanged}
dataCellRender={prms =>
dataCellRender({
...prms,
handleSelectChange,
sUnit: UNIT_COST_JOBS_SPECS,
selectedJobSpec: costJobsSpecs.selectedRow
})
}
headCellRender={prms => headCellRender({ ...prms })}
fixedHeader={costJobsSpecs.fixedHeader}
fixedColumns={costJobsSpecs.fixedColumns}
/>
</Box>
</>
) : null}
</Grid>
<Grid item sx={STYLES.CONTAINER} xs={6}>
<Typography sx={STYLES.TABLE_HEADER} variant={"h6"} color={"text.secondary"}>
Рабочие
</Typography>
{costJobsWorkers.dataLoaded ? (
<>
<Box sx={STYLES.TABLE_BUTTONS}>
<Stack direction={"row"} spacing={1}>
<Button
variant="contained"
size="small"
disabled={!(costJobsSpecs.selectedRow.NRESOURCE_NUMB === costJobsWorkers.selectedRows.length)}
onClick={() => handleShowIncludeChange(true)}
>
Включить в задание
</Button>
<Button
variant="contained"
size="small"
disabled={!costJobsSpecs.selectedRow.NRN || !costJobsSpecs.selectedRow.NPERFORM_FACT}
onClick={handleCostJobsSpecExcludeWorker}
>
Исключить из задания
</Button>
</Stack>
</Box>
<Box sx={STYLES.TABLE}>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER(costJobsWorkers.morePages), elevation: 4 }}
columnsDef={costJobsWorkers.columnsDef}
rows={costJobsWorkers.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
morePages={costJobsWorkers.morePages}
reloading={costJobsWorkers.reload}
onOrderChanged={handleCostJobsWorkersOrderChanged}
onPagesCountChanged={handleCostJobsWorkersPagesCountChanged}
dataCellRender={prms =>
dataCellRender({
...prms,
handleSelectChange,
sUnit: UNIT_WORKERS,
selectedWorkerRows: costJobsWorkers.selectedRows,
selectedJobSpec: costJobsSpecs.selectedRow
})
}
headCellRender={prms => headCellRender({ ...prms })}
fixedHeader={true}
/>
</Box>
</>
) : null}
</Grid>
</Grid>
</Box>
</Box>
) : null}
{showInclude ? (
<CostJobsSpecsInclude
includePrms={{
NFCJOBSSP: costJobsSpecs.selectedRow.NRN,
SELECTED_WORKERS: costJobsWorkers.selectedRows,
NQUANT_PLAN: costJobsSpecs.selectedRow.NQUANT_PLAN
}}
setShowInclude={setShowInclude}
setCostJobsSpecs={setCostJobsSpecs}
setCostJobsWorkers={setCostJobsWorkers}
includeWorker={includeWorker}
/>
) : null}
</Box>
);
};
//----------------
//Интерфейс модуля
//----------------
export { MechRecCostJobs };

View File

@ -1,103 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
Панель мониторинга: Диалог включения рабочего в сменное задание
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Button, Dialog, DialogTitle, DialogContent, TextField, DialogActions } from "@mui/material"; //Интерфейсные элементы
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы
//-----------
//Тело модуля
//-----------
//Диалог включения рабочего в сменное задание
const CostJobsSpecsInclude = ({ includePrms, setShowInclude, setCostJobsSpecs, setCostJobsWorkers, includeWorker }) => {
//Собственное состояние - Значение приоритета
const [state, setState] = useState(includePrms.NQUANT_PLAN);
//При закрытии включения рабочего
const handlePriorEditClose = () => setShowInclude(false);
//При включении рабочего в строку сменного задания
const costJobsSpecIncludeCostEquipment = () => {
//Делаем асинхронно, чтобы при ошибке ничего не обновлять
const includeAsync = async () => {
//Включаем рабочего в строку сменного задания
try {
await includeWorker({
NFCJOBSSP: includePrms.NFCJOBSSP,
SELECTED_WORKERS: includePrms.SELECTED_WORKERS,
NQUANT_PLAN: state
});
//Необходимо обновить все данные
setCostJobsSpecs(pv => ({ ...pv, selectedRow: {}, pageNumber: 1, reload: true }));
setCostJobsWorkers(pv => ({ ...pv, selectedRows: [], pageNumber: 1, reload: true }));
handlePriorEditClose();
} catch (e) {
throw new Error(e.message);
}
};
//Включаем рабочего асинхронно
includeAsync();
};
return (
<Dialog open onClose={() => handlePriorEditClose()}>
<DialogTitle>Включить в задание</DialogTitle>
<DialogContent>
<Box>
<TextField
name="editInculdeValue"
label="Количество"
variant="standard"
fullWidth
InputProps={{
type: "number",
inputProps: {
max: includePrms.NQUANT_PLAN,
min: 0
}
}}
value={state}
onChange={event => {
var value = parseInt(event.target.value, 10);
if (value > includePrms.NQUANT_PLAN) {
value = includePrms.NQUANT_PLAN;
}
if (value < 0) {
value = 0;
}
setState(value);
}}
/>
<Box></Box>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={costJobsSpecIncludeCostEquipment}>{BUTTONS.OK}</Button>
<Button onClick={() => handlePriorEditClose(null)}>{BUTTONS.CANCEL}</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог включения рабочего в сменное задание
CostJobsSpecsInclude.propTypes = {
includePrms: PropTypes.object.isRequired,
setShowInclude: PropTypes.func.isRequired,
setCostJobsSpecs: PropTypes.func.isRequired,
setCostJobsWorkers: PropTypes.func.isRequired,
includeWorker: PropTypes.func.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { CostJobsSpecsInclude };

View File

@ -48,21 +48,19 @@ const useCostRouteLists = (task, taskType) => {
NPAGE_SIZE: DATA_GRID_PAGE_SIZE, NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
NINCLUDE_DEF: costRouteLists.dataLoaded ? 0 : 1 NINCLUDE_DEF: costRouteLists.dataLoaded ? 0 : 1
}, },
attributeValueProcessor: (name, val) => attributeValueProcessor: (name, val) => (["DEXEC_DATE", "DREL_DATE"].includes(name) ? formatDateRF(val) : val),
["DEXEC_DATE", "DREL_DATE"].includes(name) ? formatDateRF(val) : ["SDOCPREF", "SDOCNUMB"].includes(name) ? undefined : val,
respArg: "COUT" respArg: "COUT"
}); });
setCostRouteLists(pv => ({ setCostRouteLists(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE, morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE,
quantPlanSum: data.XDATA_GRID.rows ? data.XDATA_GRID.rows.reduce((a, b) => a + b["NQUANT_PLAN"], 0) : 0, quantPlanSum: data.XROWS ? data.XROWS.reduce((a, b) => a + b["NQUANT_PLAN"], 0) : 0,
uniqueNomns: data.XDATA_GRID.rows uniqueNomns: data.XROWS
? data.XDATA_GRID.rows.reduce((accumulator, current) => { ? data.XROWS.reduce((accumulator, current) => {
if (!accumulator.find(item => item.SMATRES_PLAN_NOMEN === current.SMATRES_PLAN_NOMEN)) { if (!accumulator.find(item => item.SMATRES_PLAN_NOMEN === current.SMATRES_PLAN_NOMEN)) {
accumulator.push(current); accumulator.push(current);
} }
@ -124,12 +122,11 @@ const useIncomFromDeps = (task, taskType) => {
}); });
setIncomFromDeps(pv => ({ setIncomFromDeps(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE
})); }));
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -175,12 +172,11 @@ const useGoodsParties = mainRowRN => {
}); });
setGoodsParties(pv => ({ setGoodsParties(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE
})); }));
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -227,12 +223,11 @@ const useCostDeliveryLists = mainRowRN => {
}); });
setCostDeliveryLists(pv => ({ setCostDeliveryLists(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE
})); }));
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -43,12 +43,11 @@ import { MessagingСtx } from "../../context/messaging"; //Контекст со
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { P8PGantt, taskLegendDesc } from "../../components/p8p_gantt"; //Диаграмма Ганта import { P8PGantt, taskLegendDesc } from "../../components/p8p_gantt"; //Диаграмма Ганта
import { xml2JSON, formatDateJSONDateOnly, formatDateRF, hasValue } from "../../core/utils"; //Вспомогательные функции import { xml2JSON, formatDateJSONDateOnly, formatDateRF, hasValue } from "../../core/utils"; //Вспомогательные функции
import { useFilteredPlanCtlgs } from "./hooks"; //Вспомогательные хуки import { useFilteredPlanCtlgs } from "./hooks"; //Вспомогательные хуки
import { CostRouteListsDataGrid } from "./datagrids/fcroutlst"; //Таблица "Маршрутные листы" import { CostRouteListsDataGrid } from "./datagrids/fcroutlst";
import { IncomFromDepsDataGrid } from "./datagrids/incomefromdeps"; //Таблица "Приходы из подразделений" import { IncomFromDepsDataGrid } from "./datagrids/incomefromdeps";
//--------- //---------
//Константы //Константы
@ -73,7 +72,7 @@ const STYLES = {
width: "350px", width: "350px",
display: "inline-block", display: "inline-block",
flexShrink: 0, flexShrink: 0,
[`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box", ...APP_STYLES.SCROLL } [`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box" }
}, },
GANTT_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})`, width: "100vw", paddingTop: "24px" }, GANTT_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})`, width: "100vw", paddingTop: "24px" },
GANTT_TITLE: { paddingLeft: "250px", paddingRight: "250px" }, GANTT_TITLE: { paddingLeft: "250px", paddingRight: "250px" },
@ -84,26 +83,7 @@ const STYLES = {
TASK_DIALOG_ACTION_CONTAINER: { border: 1, borderColor: "text.primary", borderRadius: "5px", width: "100%" }, TASK_DIALOG_ACTION_CONTAINER: { border: 1, borderColor: "text.primary", borderRadius: "5px", width: "100%" },
FILTERS: { display: "table", float: "right" }, FILTERS: { display: "table", float: "right" },
FILTERS_DATE: { display: "table-cell", verticalAlign: "middle" }, FILTERS_DATE: { display: "table-cell", verticalAlign: "middle" },
FILTERS_LEVEL: { display: "table-cell", verticalAlign: "middle", paddingLeft: "15px" }, FILTERS_LEVEL: { display: "table-cell", verticalAlign: "middle", paddingLeft: "15px" }
FILTERS_LEVEL_CAPTION: { display: "flex", alignItems: "center" },
FILTERS_LEVEL_LIMIT_ICON: { padding: "0px 8px", color: "#9f9c9c" },
FILTERS_LIMIT_SELECT: nOutOfLimit => {
return nOutOfLimit === 1
? {
".MuiOutlinedInput-notchedOutline": {
borderColor: "#e9863c"
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: "#e9863c",
borderWidth: "0.15rem"
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: "#e9863c",
borderWidth: "0.15rem"
}
}
: {};
}
}; };
//------------------------------------ //------------------------------------
@ -117,7 +97,7 @@ const parseProdPlanSpXML = async xmlDoc => {
attributeValueProcessor: (name, val) => attributeValueProcessor: (name, val) =>
["numb", "title"].includes(name) ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val ["numb", "title"].includes(name) ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val
}); });
return data.XDATA.XGANTT; return data.XDATA;
}; };
//Форматирование для отображения количества документов //Форматирование для отображения количества документов
@ -255,10 +235,10 @@ const MechRecCostProdPlans = () => {
selectedPlanCtlg: null, selectedPlanCtlg: null,
selectedPlanCtlgMaxLevel: null, selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null, selectedPlanCtlgLevel: null,
selectedPlanCtlgOutOfLimit: 0,
selectedPlanCtlgSort: null, selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null, selectedPlanCtlgMenuItems: null,
gantt: {}, selectedPlanCtlgGanttDef: {},
selectedPlanCtlgSpecs: [],
selectedTaskDetail: null, selectedTaskDetail: null,
selectedTaskDetailType: null, selectedTaskDetailType: null,
planSpec: null planSpec: null
@ -278,9 +258,6 @@ const MechRecCostProdPlans = () => {
//Подключение к контексту навигации //Подключение к контексту навигации
const { getNavigationSearch } = useContext(NavigationCtx); const { getNavigationSearch } = useContext(NavigationCtx);
//Подключение к контексту сообщений
const { showMsgInfo } = useContext(MessagingСtx);
//Инициализация каталогов планов //Инициализация каталогов планов
const initPlanCtlgs = useCallback(async () => { const initPlanCtlgs = useCallback(async () => {
if (!state.init) { if (!state.init) {
@ -303,10 +280,10 @@ const MechRecCostProdPlans = () => {
selectedPlanCtlgSpecsLoaded: false, selectedPlanCtlgSpecsLoaded: false,
selectedPlanCtlgMaxLevel: null, selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null, selectedPlanCtlgLevel: null,
selectedPlanCtlgOutOfLimit: 0,
selectedPlanCtlgSort: null, selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null, selectedPlanCtlgMenuItems: null,
gantt: {}, selectedPlanCtlgSpecs: [],
selectedPlanCtlgGanttDef: {},
showPlanList: false, showPlanList: false,
selectedTaskDetail: null, selectedTaskDetail: null,
selectedTaskDetailType: null selectedTaskDetailType: null
@ -321,10 +298,10 @@ const MechRecCostProdPlans = () => {
selectedPlanCtlg: null, selectedPlanCtlg: null,
selectedPlanCtlgMaxLevel: null, selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null, selectedPlanCtlgLevel: null,
selectedPlanCtlgOutOfLimit: 0,
selectedPlanCtlgSort: null, selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null, selectedPlanCtlgMenuItems: null,
gantt: {}, selectedPlanCtlgSpecs: [],
selectedPlanCtlgGanttDef: {},
showPlanList: false, showPlanList: false,
selectedTaskDetail: null, selectedTaskDetail: null,
selectedTaskDetailType: null selectedTaskDetailType: null
@ -342,13 +319,13 @@ const MechRecCostProdPlans = () => {
...pv, ...pv,
selectedPlanCtlgMaxLevel: data.NMAX_LEVEL, selectedPlanCtlgMaxLevel: data.NMAX_LEVEL,
selectedPlanCtlgLevel: level || level === 0 ? level : data.NMAX_LEVEL, selectedPlanCtlgLevel: level || level === 0 ? level : data.NMAX_LEVEL,
selectedPlanCtlgOutOfLimit: data.NOUT_OF_LIMIT,
selectedPlanCtlgSort: sort, selectedPlanCtlgSort: sort,
selectedPlanCtlgMenuItems: state.selectedPlanCtlgMenuItems selectedPlanCtlgMenuItems: state.selectedPlanCtlgMenuItems
? state.selectedPlanCtlgMenuItems ? state.selectedPlanCtlgMenuItems
: [...Array(data.NMAX_LEVEL).keys()].map(el => el + 1), : [...Array(data.NMAX_LEVEL).keys()].map(el => el + 1),
selectedPlanCtlgSpecsLoaded: true, selectedPlanCtlgSpecsLoaded: true,
gantt: { ...doc, tasks: [...(doc?.tasks || [])] } selectedPlanCtlgGanttDef: doc.XGANTT_DEF ? { ...doc.XGANTT_DEF } : {},
selectedPlanCtlgSpecs: [...(doc?.XGANTT_TASKS || [])]
})); }));
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -396,17 +373,6 @@ const MechRecCostProdPlans = () => {
setState(pv => ({ ...pv, selectedTaskDetail: taskRn, selectedTaskDetailType: taskType })); setState(pv => ({ ...pv, selectedTaskDetail: taskRn, selectedTaskDetailType: taskType }));
}; };
//При открытии окна информации об ограничении уровня
const handleLevelLimitInfoOpen = () => {
//Отображаем информацию
showMsgInfo(
`Размер производственной программы превышает предельно допустимый для одновременного отображения в виде диаграммы Ганта.
Доступные для просмотра уровни вложенности ограничены.
Вы можете просматривать производственную программу частями, используя действие "Открытие панели Производственная программа" в спецификации "Выпуск"
раздела "Планы и отчеты производства изделий".`
);
};
//Генерация содержимого //Генерация содержимого
return ( return (
<Box> <Box>
@ -435,7 +401,7 @@ const MechRecCostProdPlans = () => {
<Grid container> <Grid container>
<Grid item xs={12}> <Grid item xs={12}>
{state.selectedPlanCtlgSpecsLoaded ? ( {state.selectedPlanCtlgSpecsLoaded ? (
state.gantt.tasks.length === 0 ? ( state.selectedPlanCtlgSpecs.length === 0 ? (
<Box pt={3}> <Box pt={3}>
<InlineMsgInfo <InlineMsgInfo
okBtn={false} okBtn={false}
@ -471,16 +437,8 @@ const MechRecCostProdPlans = () => {
</Select> </Select>
</Box> </Box>
<Box sx={STYLES.FILTERS_LEVEL}> <Box sx={STYLES.FILTERS_LEVEL}>
<Box sx={STYLES.FILTERS_LEVEL_CAPTION}> <InputLabel id="select-label-level">До уровня</InputLabel>
<InputLabel id="select-label-level">До уровня</InputLabel>
{state.selectedPlanCtlgOutOfLimit === 1 ? (
<IconButton sx={STYLES.FILTERS_LEVEL_LIMIT_ICON} onClick={handleLevelLimitInfoOpen}>
<Icon>info</Icon>
</IconButton>
) : null}
</Box>
<Select <Select
sx={STYLES.FILTERS_LIMIT_SELECT(state.selectedPlanCtlgOutOfLimit)}
labelId="select-label-level" labelId="select-label-level"
id="select-level" id="select-level"
value={state.selectedPlanCtlgLevel} value={state.selectedPlanCtlgLevel}
@ -501,9 +459,10 @@ const MechRecCostProdPlans = () => {
) : null} ) : null}
<P8PGantt <P8PGantt
{...P8P_GANTT_CONFIG_PROPS} {...P8P_GANTT_CONFIG_PROPS}
{...state.gantt} {...state.selectedPlanCtlgGanttDef}
containerStyle={STYLES.GANTT_CONTAINER} containerStyle={STYLES.GANTT_CONTAINER}
titleStyle={STYLES.GANTT_TITLE} titleStyle={STYLES.GANTT_TITLE}
tasks={state.selectedPlanCtlgSpecs}
taskDialogRenderer={prms => taskDialogRenderer({ ...prms, handleTaskDetailOpen })} taskDialogRenderer={prms => taskDialogRenderer({ ...prms, handleTaskDetailOpen })}
/> />
</Box> </Box>

View File

@ -62,12 +62,13 @@ const useMechRecDeptCostJobs = (subdiv, fullDate, workHours) => {
}); });
setCostJobs(pv => ({ setCostJobs(pv => ({
...pv, ...pv,
...data.XDATA_GRID, fixedHeader: data.XDATA_GRID.fixedHeader,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, fixedColumns: data.XDATA_GRID.fixedColumns,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])], columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_LARGE, morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE_LARGE,
date: fullDate date: fullDate
})); }));
}; };
@ -108,12 +109,11 @@ const useInsDepartment = fullDate => {
}); });
setInsDepartments(pv => ({ setInsDepartments(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_SMALL morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE_SMALL
})); }));
}; };
if (insDepartments.reload) { if (insDepartments.reload) {

View File

@ -75,12 +75,13 @@ const useDeptCostProdPlans = () => {
}); });
setState(pv => ({ setState(pv => ({
...pv, ...pv,
...data.XDATA_GRID, fixedHeader: data.XDATA_GRID.fixedHeader,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, fixedColumns: data.XDATA_GRID.fixedColumns,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])], columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_LARGE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE_LARGE
})); }));
}; };
if (state.reload) { if (state.reload) {
@ -143,12 +144,11 @@ const useCostRouteLists = task => {
}); });
setCostRouteLists(pv => ({ setCostRouteLists(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_SMALL morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE_SMALL
})); }));
}; };
if (costRouteLists.reload && task) { if (costRouteLists.reload && task) {
@ -202,12 +202,11 @@ const useCostRouteListsSpecs = mainRowRN => {
}); });
setCostRouteListsSpecs(pv => ({ setCostRouteListsSpecs(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_LARGE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE_LARGE
})); }));
}; };
if (costRouteListsSpecs.reload) { if (costRouteListsSpecs.reload) {
@ -259,12 +258,11 @@ const useIncomFromDeps = task => {
}); });
setIncomFromDeps(pv => ({ setIncomFromDeps(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_LARGE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE_LARGE
})); }));
}; };
if (incomFromDeps.reload) { if (incomFromDeps.reload) {

View File

@ -41,7 +41,7 @@ const STYLES = {
width: "350px", width: "350px",
display: "inline-block", display: "inline-block",
flexShrink: 0, flexShrink: 0,
[`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box", ...APP_STYLES.SCROLL } [`& .MuiDrawer-paper`]: { width: "350px", display: "inline-block", boxSizing: "border-box" }
}, },
CONTAINER: { textAlign: "center" }, CONTAINER: { textAlign: "center" },
TITLE: { height: TITLE_HEIGHT, overflow: "hidden", paddingTop: TITLE_PADDING_TOP, paddingBottom: TITLE_PADDING_BOTTOM, display: "inline-table" }, TITLE: { height: TITLE_HEIGHT, overflow: "hidden", paddingTop: TITLE_PADDING_TOP, paddingBottom: TITLE_PADDING_BOTTOM, display: "inline-table" },

View File

@ -1,52 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Редактор свойств компонента панели
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Typography } from "@mui/material"; //Интерфейсные элементы
import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов
import "./panels_editor.css"; //Стили редактора
//-----------
//Тело модуля
//-----------
//Редактор свойств компонента панели
const ComponentEditor = ({ id, path, settings = {}, valueProviders = {}, onSettingsChange = null } = {}) => {
//Подгрузка модуля редактора компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
const [ComponentEditor, init] = useComponentModule({ path, module: "editor" });
//Расчёт флага наличия компонента
const haveComponent = path ? true : false;
//Формирование представления
return (
<Box className={"component-editor__wrap"}>
{haveComponent && init && (
<ComponentEditor.default id={id} {...settings} valueProviders={valueProviders} onSettingsChange={onSettingsChange} />
)}
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
</Box>
);
};
//Контроль свойств компонента - редактор свойств компонента панели
ComponentEditor.propTypes = {
id: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
settings: PropTypes.object,
valueProviders: PropTypes.object,
onSettingsChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { ComponentEditor };

View File

@ -1,72 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Представление компонента панели
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Typography } from "@mui/material"; //Интерфейсные элементы
import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов
import "./panels_editor.css"; //Стили редактора
//-----------
//Тело модуля
//-----------
//Представление компонента панели
const ComponentView = ({ id, path, settings = {}, values = {}, onValuesChange = null } = {}) => {
//Подгрузка модуля представления компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
const [ComponentView, init] = useComponentModule({ path, module: "view" });
//При смене значений
const handleValuesChange = values => onValuesChange && onValuesChange(id, values);
//Расчёт флага наличия компонента
const haveComponent = path ? true : false;
//Формирование представления
return (
<Box className={"component-view__wrap"}>
{haveComponent && init && <ComponentView.default id={id} {...settings} values={values} onValuesChange={handleValuesChange} />}
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
</Box>
);
};
//Контроль свойств компонента - компонент панели
ComponentView.propTypes = {
id: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
settings: PropTypes.object,
values: PropTypes.object,
onValuesChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { ComponentView };
//--------------------------
//ВАЖНО: Можно на React.lazy
//--------------------------
//ПРИМЕР:
/*
import React, { Suspense, lazy } from "react"; //Классы React
const ComponentView = ({ path = null, props = {} } = {}) => {
const haveComponent = path ? true : false;
const ComponentView = haveComponent ? lazy(() => import(`./components/${path}/view`)) : null;
return (
<Paper sx={STYLES.CONTAINER}>
{haveComponent && (<Suspense fallback={null}><ComponentView {...props}/></Suspense>)}
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
</Paper>
);
};
*/

View File

@ -1,56 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: График (редактор настроек)
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов
import "../../panels_editor.css"; //Стили редактора
//-----------
//Тело модуля
//-----------
//График (редактор настроек)
const ChartEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
//Собственное состояние - текущие настройки
const [settings, setSettings] = useState(null);
//При изменении компонента
useEffect(() => {
settings?.id != id && setSettings({ id, dataSource });
}, [settings, id, dataSource]);
//При сохранении изменений элемента
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
//При сохранении настроек
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
//Формирование представления
return (
<EditorBox title={"Параметры графика"} onSave={handleSave}>
<EditorSubHeader title={"Источник данных"} />
<DataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
</EditorBox>
);
};
//Контроль свойств компонента - График (редактор настроек)
ChartEditor.propTypes = {
id: PropTypes.string.isRequired,
dataSource: DATA_SOURCE_SHAPE,
valueProviders: PropTypes.object,
onSettingsChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export default ChartEditor;

View File

@ -1,79 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: График (представление)
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Paper } from "@mui/material"; //Интерфейсные элементы
import { P8PChart } from "../../../../components/p8p_chart"; //График
import { useComponentDataSource } from "../components_hooks"; //Хуки для данных
import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов
import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений
import "../../panels_editor.css"; //Стили редактора
//---------
//Константы
//---------
//Иконка компонента
const COMPONENT_ICON = "bar_chart";
//Наименование компонента
const COMPONENT_NAME = "График";
//Стили
const STYLES = {
CHART: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", display: "flex" }
};
//-----------
//Тело модуля
//-----------
//График (представление)
const Chart = ({ dataSource = null, values = {} } = {}) => {
//Собственное состояние - данные
const [data, error] = useComponentDataSource({ dataSource, values });
//Флаг настроенности графика
const haveConfing = dataSource?.stored ? true : false;
//Флаг наличия данных
const haveData = data?.init === true && !error ? true : false;
//Данные графика
const chart = data?.XCHART || {};
//Формирование представления
return (
<Paper className={"component-view__container component-view__container__empty"} elevation={6}>
{haveConfing && haveData ? (
<P8PChart style={STYLES.CHART} {...chart} />
) : (
<ComponentInlineMessage
icon={COMPONENT_ICON}
name={COMPONENT_NAME}
message={!haveConfing ? COMPONENT_MESSAGES.NO_SETTINGS : error ? error : COMPONENT_MESSAGES.NO_DATA_FOUND}
type={error ? COMPONENT_MESSAGE_TYPE.ERROR : COMPONENT_MESSAGE_TYPE.COMMON}
/>
)}
</Paper>
);
};
//Контроль свойств компонента - График (представление)
Chart.propTypes = {
dataSource: DATA_SOURCE_SHAPE,
values: PropTypes.object
};
//----------------
//Интерфейс модуля
//----------------
export default Chart;

View File

@ -1,129 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Описание
*/
//---------
//Константы
//---------
const COMPONETNS = [
{
name: "Форма",
path: "form",
settings: {
title: "Параметры формирования",
autoApply: true,
items: [
{
name: "AGENT",
caption: "Контрагент",
unitCode2: "AGNLIST",
unitName: "Контрагенты",
showMethod: "main",
showMethodName: "main",
parameter: "Мнемокод",
inputParameter: "in_AGNABBR",
outputParameter: "out_AGNABBR"
},
{
name: "DOC_TYPE",
caption: "Тип документа",
unitCode2: "DOCTYPES",
unitName: "Типы документов",
showMethod: "main",
showMethodName: "main",
parameter: "Мнемокод",
inputParameter: "in_DOCCODE",
outputParameter: "out_DOCCODE"
}
]
}
},
{
name: "График",
path: "chart",
settings2: {
dataSource: {
type: "USER_PROC",
userProc: рафТоп5ДогКонтрТип",
stored: "UDO_P_P8P_AGNCONTR_CHART",
respArg: "COUT",
arguments: [
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" }
]
}
}
},
{
name: "Таблица",
path: "table",
settings2: {
dataSource: {
type: "USER_PROC",
userProc: "ТаблицаДогКонтрТип",
stored: "UDO_P_P8P_AGNCONTR_TABLE",
respArg: "COUT",
arguments: [
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" }
]
}
}
},
{
name: "Индикатор",
path: "indicator",
settings: {
dataSource: {
type: "USER_PROC",
userProc: "ИндКолДогКонтрТип",
stored: "UDO_P_P8P_AGNCONTR_IND",
respArg: "COUT",
arguments: [
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" },
{
name: "NIND_TYPE",
caption: "Тип индикатора (0 - все, 1 - неутвержденные)",
dataType: "NUMB",
req: true,
value: "0",
valueSource: ""
}
]
}
}
},
{
name: "Индикатор2",
path: "indicator",
settings: {
dataSource: {
type: "USER_PROC",
userProc: "ИндКолДогКонтрТип",
stored: "UDO_P_P8P_AGNCONTR_IND",
respArg: "COUT",
arguments: [
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" },
{
name: "NIND_TYPE",
caption: "Тип индикатора (0 - все, 1 - неутвержденные)",
dataType: "NUMB",
req: true,
value: "1",
valueSource: ""
}
]
}
}
}
];
//----------------
//Интерфейс модуля
//----------------
export { COMPONETNS };

View File

@ -1,174 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Хуки компонентов
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useEffect, useRef } from "react"; //Классы React
import client from "../../../core/client"; //Клиент взаимодействия с сервером приложений
import { formatErrorMessage } from "../../../core/utils"; //Общие вспомогательные функции
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { DATA_SOURCE_TYPE, ARGUMENT_DATA_TYPE } from "./editors_common"; //Общие объекты редакторов
//-----------
//Тело модуля
//-----------
//Загрузка модуля компонента из модуля (можно применять как альтернативу React.lazy)
const useComponentModule = ({ path = null, module = "view" } = {}) => {
//Собственное состояние - импортированный модуль компонента
const [componentModule, setComponentModule] = useState(null);
//Собственное состояние - флаг готовности
const [init, setInit] = useState(false);
//При подмонтировании к странице
useEffect(() => {
//Динамическая загрузка модуля компонента из библиотеки
const importComponentModule = async () => {
setInit(false);
const moduleContent = await import(`./${path}/${module}`);
setComponentModule(moduleContent);
setInit(true);
};
if (path) importComponentModule();
}, [path, module]);
//Возвращаем интерфейс хука
return [componentModule, init];
};
//Описание пользовательской процедуры
const useUserProcDesc = ({ code, refresh }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - данные
const [data, setData] = useState(null);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости обновить данные компонента
useEffect(() => {
//Загрузка данных с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_EDITOR.USERPROCS_DESC",
args: { SCODE: code },
respArg: "COUT",
isArray: name => name === "arguments",
loader: false
});
setData(data?.XUSERPROC || null);
} finally {
setLoading(false);
}
};
//Если надо обновить и есть для чего получать данные
if (refresh > 0)
if (code) loadData();
else setData(null);
}, [refresh, code, executeStored]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//Получение данных компонента из источника
const useComponentDataSource = ({ dataSource, values }) => {
//Контроллер для прерывания запросов
const abortController = useRef(null);
//Собственное состояние - параметры исполнения
const [state, setState] = useState({ stored: null, storedArgs: [], respArg: null, reqSet: false });
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - данные
const [data, setData] = useState({ init: false });
//Собственное состояние - ошибка получения данных
const [error, setError] = useState(null);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости обновить данные
useEffect(() => {
//Загрузка данных с сервера
const loadData = async () => {
try {
setLoading(true);
abortController.current?.abort?.();
abortController.current = new AbortController();
const data = await executeStored({
stored: state.stored,
args: { ...(state.storedArgs ? state.storedArgs : {}) },
respArg: state.respArg,
loader: false,
signal: abortController.current.signal,
showErrorMessage: false
});
setError(null);
setData({ ...data, init: true });
} catch (e) {
if (e.message !== client.ERR_ABORTED) {
setError(formatErrorMessage(e.message).text);
setData({ init: false });
}
} finally {
setLoading(false);
}
};
if (state.reqSet) {
if (state.stored) loadData();
} else setData({ init: false });
return () => abortController.current?.abort?.();
}, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored]);
//При изменении свойств
useEffect(() => {
setState(pv => {
if (dataSource?.type == DATA_SOURCE_TYPE.USER_PROC) {
const { stored, respArg } = dataSource;
let reqSet = true;
const storedArgs = {};
dataSource.arguments.forEach(argument => {
let v = argument.valueSource ? values[argument.valueSource] : argument.value;
storedArgs[argument.name] =
argument.dataType == ARGUMENT_DATA_TYPE.NUMB
? isNaN(parseFloat(v))
? null
: parseFloat(v)
: argument.dataType == ARGUMENT_DATA_TYPE.DATE
? new Date(v)
: String(v === undefined ? "" : v);
if (argument.req === true && [undefined, null, ""].includes(storedArgs[argument.name])) reqSet = false;
});
if (pv.stored != stored || pv.respArg != respArg || JSON.stringify(pv.storedArgs) != JSON.stringify(storedArgs)) {
if (!reqSet) {
setError("Не заданы обязательные параметры источника данных");
setData({ init: false });
}
return { stored, respArg, storedArgs, reqSet };
} else return pv;
} else return pv;
});
}, [dataSource, values]);
//Возвращаем интерфейс хука
return [data, error, isLoading];
};
//----------------
//Интерфейс модуля
//----------------
export { useComponentModule, useUserProcDesc, useComponentDataSource };

View File

@ -1,434 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Общие компоненты редакторов свойств
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Box,
Stack,
IconButton,
Icon,
Typography,
Divider,
Chip,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
TextField,
InputAdornment,
MenuItem,
Menu,
Card,
CardContent,
CardActions,
CardActionArea
} from "@mui/material"; //Интерфейсные элементы
import client from "../../../core/client"; //Клиент БД
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
import { BUTTONS } from "../../../../app.text"; //Общие текстовые ресурсы
import { useUserProcDesc } from "./components_hooks"; //Общие хуки компонентов
import "../panels_editor.css"; //Стили редактора
//---------
//Константы
//---------
//Стили
const STYLES = {
CHIP: (fullWidth = false, multiLine = false) => ({
...(multiLine ? { height: "auto" } : {}),
"& .MuiChip-label": {
...(multiLine
? {
display: "block",
whiteSpace: "normal"
}
: {}),
...(fullWidth ? { width: "100%" } : {})
}
})
};
//Типы даных аргументов
const ARGUMENT_DATA_TYPE = {
STR: client.SERV_DATA_TYPE_STR,
NUMB: client.SERV_DATA_TYPE_NUMB,
DATE: client.SERV_DATA_TYPE_DATE
};
//Типы источников данных
const DATA_SOURCE_TYPE = {
USER_PROC: "USER_PROC",
QUERY: "QUERY"
};
//Типы источников данных (наименования)
const DATA_SOURCE_TYPE_NAME = {
[DATA_SOURCE_TYPE.USER_PROC]: "Пользовательская процедура",
[DATA_SOURCE_TYPE.QUERY]: "Запрос"
};
//Структура аргумента источника данных
const DATA_SOURCE_ARGUMENT_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
caption: PropTypes.string.isRequired,
dataType: PropTypes.oneOf(Object.values(ARGUMENT_DATA_TYPE)),
req: PropTypes.bool.isRequired,
value: PropTypes.any,
valueSource: PropTypes.string
});
//Начальное состояние аргумента источника данных
const DATA_SOURCE_ARGUMENT_INITIAL = {
name: "",
caption: "",
dataType: "",
req: false,
value: "",
valueSource: ""
};
//Структура источника данных
const DATA_SOURCE_SHAPE = PropTypes.shape({
type: PropTypes.oneOf([...Object.values(DATA_SOURCE_TYPE), ""]),
userProc: PropTypes.string,
stored: PropTypes.string,
respArg: PropTypes.string,
arguments: PropTypes.arrayOf(DATA_SOURCE_ARGUMENT_SHAPE)
});
//Начальное состояние истоника данных
const DATA_SOURCE_INITIAL = {
type: "",
userProc: "",
stored: "",
respArg: "",
arguments: []
};
//-----------
//Тело модуля
//-----------
//Контейнер редактора
const EditorBox = ({ title, children, onSave }) => {
//При нажатии на "Сохранить"
const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor);
//Формирование представления
return (
<Box className={"component-editor__container"}>
<Divider>{title}</Divider>
<Stack direction={"column"} spacing={1}>
{children}
</Stack>
<Stack direction={"row"} justifyContent={"right"} p={1}>
<IconButton onClick={() => handleSaveClick(false)} title={BUTTONS.APPLY}>
<Icon>done</Icon>
</IconButton>
<IconButton onClick={() => handleSaveClick(true)} title={BUTTONS.SAVE}>
<Icon>done_all</Icon>
</IconButton>
</Stack>
</Box>
);
};
//Контроль свойств компонента - контейнер редактора
EditorBox.propTypes = {
title: PropTypes.string.isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
onSave: PropTypes.func
};
//Заголовок раздела редактора
const EditorSubHeader = ({ title }) => {
//Формирование представления
return (
<Divider className={"component-editor__divider"}>
<Chip label={title} size={"small"} />
</Divider>
);
};
//Контроль свойств компонента - заголовок раздела редактора
EditorSubHeader.propTypes = {
title: PropTypes.string.isRequired
};
//Диалог настройки
const ConfigDialog = ({ title, children, onOk, onCancel }) => {
//Формирование представления
return (
<Dialog onClose={onCancel} open>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={() => onOk && onOk()}>{BUTTONS.OK}</Button>
<Button onClick={() => onCancel && onCancel()}>{BUTTONS.CANCEL}</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств компонента - диалог настройки
ConfigDialog.propTypes = {
title: PropTypes.string.isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//Диалог настройки источника данных
const ConfigDataSourceDialog = ({ dataSource = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => {
//Собственное состояние - параметры элемента формы
const [state, setState] = useState({ ...DATA_SOURCE_INITIAL, ...dataSource });
//Собственное состояние - флаги обновление данных
const [refresh, setRefresh] = useState({ userProcDesc: 0 });
//Собственное состояние - элемент привязки меню выбора источника
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState(null);
//Описание выбранной пользовательской процедуры
const [userProcDesc] = useUserProcDesc({ code: state.userProc, refresh: refresh.userProcDesc });
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Установка значения/привязки аргумента
const setArgumentValueSource = (index, value, valueSource) =>
setState(pv => ({
...pv,
arguments: pv.arguments.map((argument, i) => ({ ...argument, ...(i == index ? { value, valueSource } : {}) }))
}));
//Открытие/сокрытие меню выбора источника
const toggleValueProvidersMenu = target => setValueProvidersMenuAnchorEl(target instanceof Element ? target : null);
//При нажатии на очистку наименования пользовательской процедуры
const handleUserProcClearClick = () => setState({ ...DATA_SOURCE_INITIAL });
//При нажатии на выбор пользовательской процедуры в качестве источника данных
const handleUserProcSelectClick = () => {
pOnlineShowDictionary({
unitCode: "UserProcedures",
showMethod: "main",
inputParameters: [{ name: "in_CODE", value: state.userProc }],
callBack: res => {
if (res.success) {
setState(pv => ({ ...pv, type: DATA_SOURCE_TYPE.USER_PROC, userProc: res.outParameters.out_CODE }));
setRefresh(pv => ({ ...pv, userProcDesc: pv.userProcDesc + 1 }));
}
}
});
};
//При закрытии дилога с сохранением
const handleOk = () => onOk && onOk({ ...state });
//При закртии диалога отменой
const handleCancel = () => onCancel && onCancel();
//При очистке значения/связывания аргумента
const handleArgumentClearClick = index => setArgumentValueSource(index, "", "");
//При отображении меню связывания аргумента с поставщиком данных
const handleArgumentLinkMenuClick = e => setValueProvidersMenuAnchorEl(e.currentTarget);
//При выборе элемента меню связывания аргумента с поставщиком данных
const handleArgumentLinkClick = valueSource => {
setArgumentValueSource(valueProvidersMenuAnchorEl.id, "", valueSource);
toggleValueProvidersMenu();
};
//При вводе значения аргумента
const handleArgumentChange = (index, value) => setArgumentValueSource(index, value, "");
//При изменении описания пользовательской процедуры
useEffect(() => {
if (userProcDesc)
setState(pv => ({
...pv,
stored: userProcDesc?.stored?.name,
respArg: userProcDesc?.stored?.respArg,
arguments: (userProcDesc?.arguments || []).map(argument => ({ ...DATA_SOURCE_ARGUMENT_INITIAL, ...argument }))
}));
}, [userProcDesc]);
//Список значений
const values = Object.keys(valueProviders).reduce((res, key) => [...res, ...Object.keys(valueProviders[key])], []);
//Наличие значений
const isValues = values && values.length > 0 ? true : false;
//Меню привязки к поставщикам значений
const valueProvidersMenu = isValues && (
<Menu anchorEl={valueProvidersMenuAnchorEl} open={Boolean(valueProvidersMenuAnchorEl)} onClose={toggleValueProvidersMenu}>
{values.map((value, i) => (
<MenuItem key={i} onClick={() => handleArgumentLinkClick(value)}>
{value}
</MenuItem>
))}
</Menu>
);
//Формирование представления
return (
<ConfigDialog title="Настройка источника данных" onOk={handleOk} onCancel={handleCancel}>
<Stack direction={"column"} spacing={1}>
{valueProvidersMenu}
<TextField
type={"text"}
variant={"standard"}
value={state.userProc}
label={"Пользовательская процедура"}
InputLabelProps={{ shrink: true }}
InputProps={{
readOnly: true,
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleUserProcClearClick}>
<Icon>clear</Icon>
</IconButton>
<IconButton onClick={handleUserProcSelectClick}>
<Icon>list</Icon>
</IconButton>
</InputAdornment>
)
}}
/>
{Array.isArray(state?.arguments) &&
state.arguments.map((argument, i) => (
<TextField
key={i}
type={"text"}
variant={"standard"}
value={argument.value || argument.valueSource}
label={argument.caption}
onChange={e => handleArgumentChange(i, e.target.value)}
InputLabelProps={{ shrink: true }}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => handleArgumentClearClick(i)}>
<Icon>clear</Icon>
</IconButton>
{isValues && (
<IconButton id={i} onClick={handleArgumentLinkMenuClick}>
<Icon>settings_ethernet</Icon>
</IconButton>
)}
</InputAdornment>
)
}}
/>
))}
</Stack>
</ConfigDialog>
);
};
//Контроль свойств компонента - Диалог настройки источника данных
ConfigDataSourceDialog.propTypes = {
dataSource: DATA_SOURCE_SHAPE,
valueProviders: PropTypes.object,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//Источник данных
const DataSource = ({ dataSource = null, valueProviders = {}, onChange = null } = {}) => {
//Собственное состояние - отображение диалога настройки
const [configDlg, setConfigDlg] = useState(false);
//Уведомление родителя о смене настроек источника данных
const notifyChange = settings => onChange && onChange(settings);
//При нажатии на настройку источника данных
const handleSetup = () => setConfigDlg(true);
//При нажатии на настройку источника данных
const handleSetupOk = dataSource => {
setConfigDlg(false);
notifyChange(dataSource);
};
//При нажатии на настройку источника данных
const handleSetupCancel = () => setConfigDlg(false);
//При удалении настроек источника данных
const handleDelete = () => notifyChange({ ...DATA_SOURCE_INITIAL });
//Расчет флага "настроенности"
const configured = dataSource?.type ? true : false;
//Список аргументов
const args =
configured &&
dataSource.arguments.map((argument, i) => (
<Chip
key={i}
label={`:${argument.name} = ${argument.valueSource || argument.value || "NULL"}`}
variant={"outlined"}
sx={STYLES.CHIP(true)}
/>
));
//Формирование представления
return (
<>
{configDlg && (
<ConfigDataSourceDialog dataSource={dataSource} valueProviders={valueProviders} onOk={handleSetupOk} onCancel={handleSetupCancel} />
)}
{configured && (
<Card variant={"outlined"}>
<CardActionArea onClick={handleSetup}>
<CardContent>
<Typography variant={"subtitle1"} noWrap={true}>
{dataSource.type === DATA_SOURCE_TYPE.USER_PROC ? dataSource.userProc : "Источник без наименования"}
</Typography>
<Typography variant={"caption"} color={"text.secondary"} noWrap={true}>
{DATA_SOURCE_TYPE_NAME[dataSource.type] || "Неизвестный тип источника"}
</Typography>
<Stack direction={"column"} spacing={1} pt={2}>
{args}
</Stack>
</CardContent>
</CardActionArea>
<CardActions>
<IconButton onClick={handleDelete}>
<Icon>delete</Icon>
</IconButton>
</CardActions>
</Card>
)}
{!configured && (
<Button startIcon={<Icon>build</Icon>} onClick={handleSetup}>
Настроить
</Button>
)}
</>
);
};
//Контроль свойств компонента - Источник данных
DataSource.propTypes = {
dataSource: DATA_SOURCE_SHAPE,
valueProviders: PropTypes.object,
onChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { STYLES, ARGUMENT_DATA_TYPE, DATA_SOURCE_TYPE, DATA_SOURCE_SHAPE, DATA_SOURCE_INITIAL, EditorBox, EditorSubHeader, ConfigDialog, DataSource };

View File

@ -1,49 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Форма (общие константы)
*/
//---------------------
//Подключение библиотек
//---------------------
import PropTypes from "prop-types"; //Контроль свойств компонента
//----------------
//Интерфейс модуля
//----------------
//Структура элемента формы
export const ITEM_SHAPE = PropTypes.shape({
name: PropTypes.string.isRequired,
caption: PropTypes.string.isRequired,
unitCode: PropTypes.string,
unitName: PropTypes.string,
showMethod: PropTypes.string,
showMethodName: PropTypes.string,
parameter: PropTypes.string,
inputParameter: PropTypes.string,
outputParameter: PropTypes.string
});
//Начальное состояние элемента формы
export const ITEM_INITIAL = {
name: "",
caption: "",
unitCode: "",
unitName: "",
showMethod: "",
showMethodName: "",
parameter: "",
inputParameter: "",
outputParameter: ""
};
//Начальное состояние элементов формы
export const ITEMS_INITIAL = [];
//Ориентация элементов формы
export const ORIENTATION = {
H: "H",
V: "v"
};

View File

@ -1,306 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Форма (редактор настроек)
*/
//TODO: Контроль уникальности имени элемента формы
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
TextField,
Button,
Icon,
Select,
MenuItem,
FormControl,
InputLabel,
FormControlLabel,
Switch,
Chip,
Stack,
InputAdornment,
IconButton
} from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения
import { STYLES as COMMON_STYLES, EditorBox, EditorSubHeader, ConfigDialog } from "../editors_common"; //Общие компоненты редакторов
import { ITEM_SHAPE, ITEM_INITIAL, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
//---------
//Константы
//---------
//Стили
const STYLES = {
CHIP_ITEM: { ...COMMON_STYLES.CHIP(true, false) }
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Редактор элемента
const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => {
//Собственное состояние - параметры элемента формы
const [state, setState] = useState({ ...ITEM_INITIAL, ...item });
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//При закрытии редактора с сохранением
const handleOk = () => onOk && onOk({ ...state });
//При закрытии редактора с отменой
const handleCancel = () => onCancel && onCancel();
//При изменении параметра элемента
const handleChange = e => setState(pv => ({ ...pv, [e.target.id]: e.target.value }));
//При нажатии на очистку раздела
const handleClearUnitClick = () =>
setState(pv => ({
...pv,
unitCode: "",
unitName: "",
showMethod: "",
showMethodName: "",
parameter: "",
inputParameter: "",
outputParameter: ""
}));
//При нажатии на выбор раздела
const handleSelectUnitClick = () => {
pOnlineShowDictionary({
unitCode: "Units",
showMethod: "methods",
inputParameters: [
{ name: "pos_unit_name", value: state.unitName },
{ name: "pos_method_name", value: state.showMethodName }
],
callBack: res =>
res.success &&
setState(pv => ({
...pv,
unitCode: res.outParameters.unit_code,
unitName: res.outParameters.unit_name,
showMethod: res.outParameters.method_code,
showMethodName: res.outParameters.method_name,
parameter: "",
inputParameter: "",
outputParameter: ""
}))
});
};
//При нажатии на выбор параметра метода вызова
const handleSelectUnitParameterClick = () => {
state.unitCode &&
state.showMethod &&
pOnlineShowDictionary({
unitCode: "UnitParams",
showMethod: "main",
inputParameters: [
{ name: "in_UNITCODE", value: state.unitCode },
{ name: "in_PARENT_METHOD_CODE", value: state.showMethod },
{ name: "in_PARAMNAME", value: state.parameter }
],
callBack: res =>
res.success &&
setState(pv => ({
...pv,
parameter: res.outParameters.out_PARAMNAME,
inputParameter: res.outParameters.out_IN_CODE,
outputParameter: res.outParameters.out_OUT_CODE
}))
});
};
//Формирование представления
return (
<ConfigDialog title={`${item ? "Изменение" : "Добавление"} элемента`} onOk={handleOk} onCancel={handleCancel}>
<Stack direction={"column"} spacing={1}>
<TextField type={"text"} variant={"standard"} value={state.name} label={"Имя"} id={"name"} onChange={handleChange} />
<TextField type={"text"} variant={"standard"} value={state.caption} label={"Приглашение"} id={"caption"} onChange={handleChange} />
<TextField
type={"text"}
variant={"standard"}
value={state.unitName}
label={"Раздел"}
InputLabelProps={{ shrink: state.unitName ? true : false }}
InputProps={{
readOnly: true,
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleClearUnitClick}>
<Icon>clear</Icon>
</IconButton>
<IconButton onClick={handleSelectUnitClick}>
<Icon>list</Icon>
</IconButton>
</InputAdornment>
)
}}
/>
<TextField
type={"text"}
variant={"standard"}
value={state.showMethodName}
label={"Метод вызова"}
InputLabelProps={{ shrink: state.showMethodName ? true : false }}
InputProps={{ readOnly: true }}
/>
<TextField
type={"text"}
variant={"standard"}
value={state.parameter}
label={"Параметр"}
InputLabelProps={{ shrink: state.parameter ? true : false }}
InputProps={{
readOnly: true,
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleSelectUnitParameterClick}>
<Icon>list</Icon>
</IconButton>
</InputAdornment>
)
}}
/>
</Stack>
</ConfigDialog>
);
};
//Контроль свойств - редактор элемента
ItemEditor.propTypes = {
item: ITEM_SHAPE,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//-----------
//Тело модуля
//-----------
//Форма (редактор настроек)
const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = false, items = ITEMS_INITIAL, onSettingsChange = null } = {}) => {
//Собственное состояние - текущие настройки
const [settings, setSettings] = useState(null);
//Собственное состояние - предоставляемые в панель значения
const [providedValues, setProvidedValues] = useState([]);
//Собственное состояние - редактор элементов формы
const [itemEditor, setItemEditor] = useState({ display: false, index: null });
//При изменении значения настройки
const handleChange = e => setSettings({ ...settings, [e.target.name]: e.target.type === "checkbox" ? e.target.checked : e.target.value });
//При добавлении нового элемента
const handleItemAdd = () => setItemEditor({ display: true, index: null });
//При нажатии на элемент
const handleItemClick = i => setItemEditor({ display: true, index: i });
//При удалении элемента
const handleItemDelete = i => {
const items = [...settings.items];
items.splice(i, 1);
setSettings(pv => ({ ...pv, items }));
};
//При сохранении изменений элемента
const handleItemSave = item => {
const items = [...settings.items];
itemEditor.index == null ? items.push({ ...item }) : (items[itemEditor.index] = { ...item });
setSettings(pv => ({ ...pv, items }));
setItemEditor({ display: false, index: null });
};
//При отмене сохранения изменений элемента
const handleItemCancel = () => setItemEditor({ display: false, index: null });
//При сохранении настроек
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, providedValues, closeEditor });
//При изменении компонента
useEffect(() => {
settings?.id != id && setSettings({ id, title, orientation, autoApply, items });
}, [settings, id, title, orientation, autoApply, items]);
//При изменении состава элементов формы
useEffect(() => {
Array.isArray(settings?.items) && setProvidedValues(settings.items.map(item => item.name));
}, [settings?.items]);
//Формирование представления
return (
settings && (
<EditorBox title={"Параметры формы"} onSave={handleSave}>
{itemEditor.display && (
<ItemEditor
item={itemEditor.index !== null ? { ...settings.items[itemEditor.index] } : null}
onCancel={handleItemCancel}
onOk={handleItemSave}
/>
)}
<EditorSubHeader title={"Общие"} />
<TextField type={"text"} variant={"standard"} value={settings.title} label={"Заголовок"} name={"title"} onChange={handleChange} />
<FormControl variant={"standard"}>
<InputLabel id={"orientation-label"}>Ориентация</InputLabel>
<Select
name={"orientation"}
value={settings.orientation}
labelId={"orientation-label"}
label={"Ориентация"}
onChange={handleChange}
>
<MenuItem value={ORIENTATION.V}>Вертикально</MenuItem>
<MenuItem value={ORIENTATION.H}>Горизонтально</MenuItem>
</Select>
</FormControl>
<FormControlLabel
control={<Switch name={"autoApply"} checked={settings.autoApply} onChange={handleChange} />}
label={"Автоподтверждение"}
/>
<EditorSubHeader title={"Элементы"} />
{Array.isArray(settings?.items) &&
settings.items.length > 0 &&
settings.items.map((item, i) => (
<Chip
key={i}
label={item.caption}
variant={"outlined"}
onClick={() => handleItemClick(i)}
onDelete={() => handleItemDelete(i)}
sx={STYLES.CHIP_ITEM}
/>
))}
<Button startIcon={<Icon>add</Icon>} onClick={handleItemAdd}>
Добавить элемент
</Button>
</EditorBox>
)
);
};
//Контроль свойств компонента - Форма (редактор настроек)
FormEditor.propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string,
orientation: PropTypes.oneOf(Object.values(ORIENTATION)),
autoApply: PropTypes.bool,
items: PropTypes.arrayOf(ITEM_SHAPE),
onSettingsChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export default FormEditor;

View File

@ -1,168 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Форма (представление)
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Paper, Stack, Typography, Icon, TextField, IconButton, InputAdornment } from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения
import { COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений
import { ITEM_SHAPE, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
import "../../panels_editor.css"; //Стили редактора
//---------
//Константы
//---------
//Иконка компонента
const COMPONENT_ICON = "fact_check";
//Наименование компонента
const COMPONENT_NAME = "Форма";
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Элемент формы
const FormItem = ({ item = null, fullWidth = false, value = "", onChange = null } = {}) => {
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//При изменении значения элемента
const handleChange = e => onChange && onChange(e.target.id, e.target.value);
//При очистке значения элемента
const handleClear = () => onChange(item.name, "");
//При выборе значения из словаря
const handleDictionary = () =>
item.unitCode &&
item.showMethod &&
pOnlineShowDictionary({
unitCode: item.unitCode,
showMethod: item.showMethod,
inputParameters: [{ name: item.inputParameter, value }],
callBack: res => res.success && onChange && onChange(item.name, res.outParameters[item.outputParameter])
});
//Формирование представления
return (
item && (
<TextField
fullWidth={fullWidth}
type={"text"}
variant={"standard"}
value={value}
label={item.caption}
id={item.name}
onChange={handleChange}
{...(item.unitCode && {
InputLabelProps: { shrink: true },
InputProps: {
readOnly: true,
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleClear}>
<Icon>clear</Icon>
</IconButton>
<IconButton onClick={handleDictionary}>
<Icon>list</Icon>
</IconButton>
</InputAdornment>
)
}
})}
/>
)
);
};
//Контроль свойств - элемент формы
FormItem.propTypes = {
item: ITEM_SHAPE,
fullWidth: PropTypes.bool,
value: PropTypes.any,
onChange: PropTypes.func
};
//-----------
//Тело модуля
//-----------
//Форма (представление)
const Form = ({ title = null, orientation = ORIENTATION.V, autoApply = false, items = ITEMS_INITIAL, values = {}, onValuesChange = null } = {}) => {
//Собственное состояние - значения элементов
const [selfValues, setSelfValues] = useState({});
//При изменении состава элементов или значений
useEffect(() => setSelfValues(items.reduce((sV, item) => ({ ...sV, [item.name]: values[item.name] }), {})), [items, values]);
//При изменении значения элемента формы
const handleItemChange = (name, value) => {
setSelfValues(pv => ({ ...pv, [name]: value }));
autoApply && onValuesChange && onValuesChange({ ...selfValues, [name]: value });
};
//При подтверждении изменений формы
const handleOkClick = () => onValuesChange && onValuesChange({ ...selfValues });
//Флаг настроенности формы
const haveConfing = items && Array.isArray(items) && items.length > 0;
//Формирование представления
return (
<Paper className={`component-view__container ${!haveConfing && "component-view__container__empty"}`} elevation={6}>
{haveConfing ? (
<Stack direction={"column"}>
<Stack direction={"row"} justifyContent={"space-between"} alignItems={"center"}>
{title && (
<Typography align={"left"} color={"text.primary"} variant={"subtitle2"} noWrap={true}>
{title}
</Typography>
)}
{!autoApply && (
<IconButton onClick={handleOkClick}>
<Icon>done</Icon>
</IconButton>
)}
</Stack>
<Stack direction={orientation == ORIENTATION.V ? "column" : "row"} spacing={1} pt={1} pb={1}>
{items.map((item, i) => (
<FormItem
key={i}
item={item}
value={selfValues?.[item.name] || ""}
onChange={handleItemChange}
fullWidth={orientation == ORIENTATION.V}
/>
))}
</Stack>
</Stack>
) : (
<ComponentInlineMessage icon={COMPONENT_ICON} name={COMPONENT_NAME} message={COMPONENT_MESSAGES.NO_SETTINGS} />
)}
</Paper>
);
};
//Контроль свойств компонента - Форма (представление)
Form.propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string,
orientation: PropTypes.oneOf(Object.values(ORIENTATION)),
autoApply: PropTypes.bool,
items: PropTypes.arrayOf(ITEM_SHAPE),
values: PropTypes.object,
onValuesChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export default Form;

View File

@ -1,56 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Индикатор (редактор настроек)
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов
import "../../panels_editor.css"; //Стили редактора
//-----------
//Тело модуля
//-----------
//Индикатор (редактор настроек)
const IndicatorEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
//Собственное состояние - текущие настройки
const [settings, setSettings] = useState(null);
//При изменении компонента
useEffect(() => {
settings?.id != id && setSettings({ id, dataSource });
}, [settings, id, dataSource]);
//При сохранении изменений элемента
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
//При сохранении настроек
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
//Формирование представления
return (
<EditorBox title={"Параметры индикатора"} onSave={handleSave}>
<EditorSubHeader title={"Источник данных"} />
<DataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
</EditorBox>
);
};
//Контроль свойств компонента - Индикатор (редактор настроек)
IndicatorEditor.propTypes = {
id: PropTypes.string.isRequired,
dataSource: DATA_SOURCE_SHAPE,
valueProviders: PropTypes.object,
onSettingsChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export default IndicatorEditor;

View File

@ -1,84 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Индикатор (представление)
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Paper } from "@mui/material"; //Интерфейсные элементы
import { P8PIndicator } from "../../../../components/p8p_indicator"; //Компонент индикатора
import { useComponentDataSource } from "../components_hooks"; //Хуки для данных
import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов
import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений
import "../../panels_editor.css"; //Стили редактора
//---------
//Константы
//---------
//Иконка компонента
const COMPONENT_ICON = "speed";
//Наименование компонента
const COMPONENT_NAME = "Индикатор";
//Стили
const STYLES = {
CONTAINER: { height: "100%" }
};
//-----------
//Тело модуля
//-----------
//Индикатор (представление)
const Indicator = ({ dataSource = null, values = {} } = {}) => {
//Собственное состояние - данные
const [data, error] = useComponentDataSource({ dataSource, values });
//Флаг настроенности индикатора
const haveConfing = dataSource?.stored ? true : false;
//Флаг наличия данных
const haveData = data?.init === true && !error ? true : false;
//Данные индикатора
const indicator = data?.XINDICATOR || {};
//Формирование представления
return (
<Paper
{...(haveConfing && haveData
? { sx: { ...STYLES.CONTAINER } }
: { className: "component-view__container component-view__container__empty" })}
elevation={6}
>
{haveConfing && haveData ? (
<P8PIndicator {...indicator} elevation={0} />
) : (
<ComponentInlineMessage
icon={COMPONENT_ICON}
name={COMPONENT_NAME}
message={!haveConfing ? COMPONENT_MESSAGES.NO_SETTINGS : error ? error : COMPONENT_MESSAGES.NO_DATA_FOUND}
type={error ? COMPONENT_MESSAGE_TYPE.ERROR : COMPONENT_MESSAGE_TYPE.COMMON}
/>
)}
</Paper>
);
};
//Контроль свойств компонента - Индикатор (представление)
Indicator.propTypes = {
dataSource: DATA_SOURCE_SHAPE,
values: PropTypes.object
};
//----------------
//Интерфейс модуля
//----------------
export default Indicator;

View File

@ -1,56 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Таблица (редактор настроек)
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов
import "../../panels_editor.css"; //Стили редактора
//-----------
//Тело модуля
//-----------
//Таблица (редактор настроек)
const TableEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
//Собственное состояние - текущие настройки
const [settings, setSettings] = useState(null);
//При изменении компонента
useEffect(() => {
settings?.id != id && setSettings({ id, dataSource });
}, [settings, id, dataSource]);
//При сохранении изменений элемента
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
//При сохранении настроек
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
//Формирование представления
return (
<EditorBox title={"Параметры таблицы"} onSave={handleSave}>
<EditorSubHeader title={"Источник данных"} />
<DataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
</EditorBox>
);
};
//Контроль свойств компонента - Таблица (редактор настроек)
TableEditor.propTypes = {
id: PropTypes.string.isRequired,
dataSource: DATA_SOURCE_SHAPE,
valueProviders: PropTypes.object,
onSettingsChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export default TableEditor;

View File

@ -1,96 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Таблица (представление)
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Paper } from "@mui/material"; //Интерфейсные элементы
import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили
import { P8PDataGrid } from "../../../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useComponentDataSource } from "../components_hooks"; //Хуки для данных
import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов
import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений
import "../../panels_editor.css"; //Стили редактора
//---------
//Константы
//---------
//Иконка компонента
const COMPONENT_ICON = "table_view";
//Наименование компонента
const COMPONENT_NAME = "Таблица";
//Стили
const STYLES = {
CONTAINER: { display: "flex", height: "100%", overflow: "hidden" },
DATA_GRID: { width: "100%" },
DATA_GRID_CONTAINER: {
height: `calc(100%)`,
...APP_STYLES.SCROLL
}
};
//-----------
//Тело модуля
//-----------
//Таблица (представление)
const Table = ({ dataSource = null, values = {} } = {}) => {
//Собственное состояние - данные
const [data, error] = useComponentDataSource({ dataSource, values });
//Флаг настроенности таблицы
const haveConfing = dataSource?.stored ? true : false;
//Флаг наличия данных
const haveData = data?.init === true && !error ? true : false;
//Данные таблицы
const dataGrid = data?.XDATA_GRID || {};
//Формирование представления
return (
<Paper
{...(haveConfing && haveData
? { sx: { ...STYLES.CONTAINER } }
: { className: "component-view__container component-view__container__empty" })}
elevation={6}
>
{haveConfing && haveData ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...dataGrid}
style={STYLES.DATA_GRID}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
/>
) : (
<ComponentInlineMessage
icon={COMPONENT_ICON}
name={COMPONENT_NAME}
message={!haveConfing ? COMPONENT_MESSAGES.NO_SETTINGS : error ? error : COMPONENT_MESSAGES.NO_DATA_FOUND}
type={error ? COMPONENT_MESSAGE_TYPE.ERROR : COMPONENT_MESSAGE_TYPE.COMMON}
/>
)}
</Paper>
);
};
//Контроль свойств компонента - Таблица (представление)
Table.propTypes = {
dataSource: DATA_SOURCE_SHAPE,
values: PropTypes.object
};
//----------------
//Интерфейс модуля
//----------------
export default Table;

View File

@ -1,67 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Общие компоненты представлений элементов панели
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Stack, Icon, Typography } from "@mui/material"; //Интерфейсные элементы
import { TEXTS } from "../../../../app.text"; //Общие текстовые ресурсы
//---------
//Константы
//---------
//Типы сообщений
const COMPONENT_MESSAGE_TYPE = {
COMMON: "COMMON",
ERROR: "ERROR"
};
//Типовые сообщения
const COMPONENT_MESSAGES = {
NO_DATA_FOUND: TEXTS.NO_DATA_FOUND,
NO_SETTINGS: "Настройте компонент"
};
//-----------
//Тело модуля
//-----------
//Информационное сообщение внутри компонента
const ComponentInlineMessage = ({ icon, name, message, type = COMPONENT_MESSAGE_TYPE.COMMON }) => {
//Формирование представления
return (
<Stack direction={"column"}>
<Stack direction={"row"} justifyContent={"center"} alignItems={"center"}>
{icon && <Icon color={"disabled"}>{icon}</Icon>}
{name && (
<Typography align={"center"} color={"text.secondary"} variant={"button"}>
{name}
</Typography>
)}
</Stack>
<Typography align={"center"} color={type != COMPONENT_MESSAGE_TYPE.ERROR ? "text.secondary" : "error.dark"} variant={"caption"}>
{message}
</Typography>
</Stack>
);
};
//Контроль свойств - Информационное сообщение внутри компонента
ComponentInlineMessage.propTypes = {
icon: PropTypes.string,
name: PropTypes.string,
message: PropTypes.string.isRequired,
type: PropTypes.oneOf(Object.values(COMPONENT_MESSAGE_TYPE))
};
//----------------
//Интерфейс модуля
//----------------
export { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage };

View File

@ -1,16 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Редактор панелей: точка входа
*/
//---------------------
//Подключение библиотек
//---------------------
import { PanelsEditor } from "./panels_editor"; //Корневая панель редактора
//----------------
//Интерфейс модуля
//----------------
export const RootClass = PanelsEditor;

View File

@ -1,87 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Элемент макета
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные элементы
import "./panels_editor.css"; //Кастомные стили
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: selected => ({ zIndex: 1100, ...(selected ? { border: "2px dotted green" } : {}) }),
STACK_TOOLS: { position: "absolute", zIndex: 1200, height: "100%", backgroundColor: "#c0c0c07f" }
};
//-----------
//Тело модуля
//-----------
//Элемент макета
// eslint-disable-next-line react/display-name
const LayoutItem = React.forwardRef(
(
{ style, className, onMouseDown, onMouseUp, onTouchEnd, children, onSettingsClick, onDeleteClick, item, editMode = false, selected = false },
ref
) => {
//При нажатии на настройки
const handleSettingsClick = () => onSettingsClick && onSettingsClick(item.i);
//При нажатии на удаление
const handleDeleteClick = () => onDeleteClick && onDeleteClick(item.i);
//Формирование представления
return (
<div
style={{ ...style, ...STYLES.CONTAINER(selected) }}
className={`${className} layout-item__container`}
ref={ref}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onTouchEnd={onTouchEnd}
>
{editMode && (
<Stack direction={"column"} sx={STYLES.STACK_TOOLS}>
<IconButton onClick={handleSettingsClick}>
<Icon>settings</Icon>
</IconButton>
<IconButton onClick={handleDeleteClick}>
<Icon>delete</Icon>
</IconButton>
</Stack>
)}
{children}
</div>
);
}
);
//Контроль свойств компонента - элемент макета
LayoutItem.propTypes = {
style: PropTypes.object,
className: PropTypes.string,
onMouseDown: PropTypes.func,
onMouseUp: PropTypes.func,
onTouchEnd: PropTypes.func,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
onSettingsClick: PropTypes.func,
onDeleteClick: PropTypes.func,
item: PropTypes.object.isRequired,
editMode: PropTypes.bool,
selected: PropTypes.bool
};
//----------------
//Интерфейс модуля
//----------------
export { LayoutItem };

View File

@ -1,40 +0,0 @@
:root {
--border-color: #dee2e6;
--layout-bg: #ffffff;
}
.layout {
background-color: var(--layout-bg);
}
.layout-item__container {
border: 1px solid var(--border-color);
border-radius: 4px;
}
.component-editor__wrap {
}
.component-editor__container {
padding: 10px;
}
.component-editor__divider {
padding-top: 20px;
}
.component-view__wrap {
height: 100%;
}
.component-view__container {
height: 100%;
overflow: auto;
padding: 10px;
}
.component-view__container__empty {
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -1,249 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Корневой компонент
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState, useContext } from "react"; //Классы React
import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
import { Box, Grid, Stack, Menu, MenuItem, IconButton, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
import { genGUID } from "../../core/utils"; //Общие вспомогательные функции
import { LayoutItem } from "./layout_item"; //Элемент макета
import { ComponentView } from "./component_view"; //Представление компонента панели
import { ComponentEditor } from "./component_editor"; //Редактор свойств компонента панели
import { COMPONETNS } from "./components/components"; //Описание доступных компонентов
import "react-grid-layout/css/styles.css"; //Стили для адаптивного макета
import "react-resizable/css/styles.css"; //Стили для адаптивного макета
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { display: "flex" },
GRID_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})` },
GRID_ITEM_INSPECTOR: { backgroundColor: "#e9ecef" },
FAB_EDIT: { position: "absolute", top: 12, right: 12, zIndex: 2000 }
};
//Заголовоки по умолчанию
const PANEL_CAPTION_EDIT_MODE = "Редактор панелей";
const PANEL_CAPTION_EXECUTE_MODE = "Исполнение панели";
//Начальное состояние размера макета
const INITIAL_BREAKPOINT = "lg";
//Начальное состояние макета
const INITIAL_LAYOUTS = {
[INITIAL_BREAKPOINT]: []
};
//-----------
//Тело модуля
//-----------
//Обёрдка для динамического макета
const ResponsiveGridLayout = WidthProvider(Responsive);
//Корневой компонент редактора панелей
const PanelsEditor = () => {
//Собственное состояние
const [components, setComponents] = useState({});
const [valueProviders, setValueProviders] = useState({});
const [layouts, setLayouts] = useState(INITIAL_LAYOUTS);
const [breakpoint, setBreakpoint] = useState(INITIAL_BREAKPOINT);
const [editMode, setEditMode] = useState(true);
const [editComponent, setEditComponent] = useState(null);
const [addMenuAnchorEl, setAddMenuAnchorEl] = useState(null);
//Подключение к контексту приложения
const { setAppBarTitle } = useContext(ApplicationСtx);
//Добвление компонента в макет
const addComponent = component => {
const id = genGUID();
setLayouts(pv => ({ ...pv, [breakpoint]: [...pv[breakpoint], { i: id, x: 0, y: 0, w: 4, h: 10 }] }));
setComponents(pv => ({ ...pv, [id]: { ...component } }));
};
//Удаление компонента из макета
const deleteComponent = id => {
setLayouts(pv => ({ ...pv, [breakpoint]: layouts[breakpoint].filter(item => item.i !== id) }));
setComponents(pv => ({ ...pv, [id]: { ...pv[id], deleted: true } }));
if (valueProviders[id]) {
const vPTmp = { ...valueProviders };
delete vPTmp[id];
setValueProviders(vPTmp);
}
editComponent === id && closeComponentSettingsEditor();
};
//Включение/выключение режима редиктирования
const toggleEditMode = () => {
if (!editMode) setAppBarTitle(PANEL_CAPTION_EDIT_MODE);
else setAppBarTitle(PANEL_CAPTION_EXECUTE_MODE);
setEditMode(!editMode);
};
//Открытие редактора настроек компонента
const openComponentSettingsEditor = id => setEditComponent(id);
//Закрытие реактора настроек компонента
const closeComponentSettingsEditor = () => setEditComponent(null);
//Открытие/сокрытие меню добавления
const toggleAddMenu = target => setAddMenuAnchorEl(target instanceof Element ? target : null);
//При изменении размера холста
const handleBreakpointChange = breakpoint => setBreakpoint(breakpoint);
//При изменении состояния макета
const handleLayoutChange = (currentLayout, layouts) => setLayouts(layouts);
//При нажатии на кнопку добалвения
const handleAddClick = e => toggleAddMenu(e.currentTarget);
//При выборе элемента меню добавления
const handleAddMenuItemClick = component => {
toggleAddMenu();
addComponent(component);
};
//При изменении значений в компоненте
const handleComponentValuesChange = (id, values) => setValueProviders(pv => ({ ...pv, [id]: { ...values } }));
//При нажатии на настройки компонента
const handleComponentSettingsClick = id => (editComponent === id ? closeComponentSettingsEditor() : openComponentSettingsEditor(id));
//При изменении настроек компонента
const handleComponentSettingsChange = ({ id = null, settings = {}, providedValues = [], closeEditor = false } = {}) => {
if (id && components[id]) {
const providedValuesInit = providedValues.reduce((res, providedValue) => ({ ...res, [providedValue]: undefined }), {});
if (valueProviders[id]) {
const vPTmp = { ...valueProviders[id] };
Object.keys(valueProviders[id]).forEach(key => !providedValues.includes(key) && delete vPTmp[key]);
setValueProviders(pv => ({ ...pv, [id]: { ...providedValuesInit, ...vPTmp } }));
} else setValueProviders(pv => ({ ...pv, [id]: providedValuesInit }));
setComponents(pv => ({ ...pv, [editComponent]: { ...pv[editComponent], settings: { ...settings } } }));
if (closeEditor === true) closeComponentSettingsEditor();
}
};
//При удалении компоненета
const handleComponentDeleteClick = id => deleteComponent(id);
//При подключении к странице
useEffect(() => {
addComponent(COMPONETNS[0]);
addComponent(COMPONETNS[3]);
addComponent(COMPONETNS[4]);
//addComponent(COMPONETNS[1]);
//addComponent(COMPONETNS[2]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Текущие значения панели
const values = Object.keys(valueProviders).reduce((res, key) => ({ ...res, ...valueProviders[key] }), {});
//Меню добавления
const addMenu = (
<Menu anchorEl={addMenuAnchorEl} open={Boolean(addMenuAnchorEl)} onClose={toggleAddMenu}>
{COMPONETNS.map((comp, i) => (
<MenuItem key={i} onClick={() => handleAddMenuItemClick(comp)}>
{comp.name}
</MenuItem>
))}
</Menu>
);
//Кнопка редактирования
const editButton = !editMode && (
<Fab sx={STYLES.FAB_EDIT} size={"small"} color={"grey.700"} title={"Редактировать"} onClick={toggleEditMode}>
<Icon>edit</Icon>
</Fab>
);
//Панель инструмментов
const toolBar = (
<Stack direction={"row"} p={1}>
<IconButton onClick={toggleEditMode} title={"Запустить"}>
<Icon>play_arrow</Icon>
</IconButton>
<IconButton onClick={handleAddClick} title={"Добавить элемент"}>
<Icon>add</Icon>
</IconButton>
</Stack>
);
//Генерация содержимого
return (
<Box sx={STYLES.CONTAINER}>
{editButton}
{addMenu}
<Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
<Grid item xs={editMode ? 20 : 25}>
<ResponsiveGridLayout
rowHeight={5}
className={"layout"}
layouts={layouts}
breakpoints={{ lg: 1200 }}
cols={{ lg: 12 }}
onBreakpointChange={handleBreakpointChange}
onLayoutChange={handleLayoutChange}
useCSSTransforms={true}
compactType={"vertical"}
isDraggable={editMode}
isResizable={editMode}
>
{layouts[breakpoint].map(item => (
<LayoutItem
key={item.i}
onSettingsClick={handleComponentSettingsClick}
onDeleteClick={handleComponentDeleteClick}
item={item}
editMode={editMode}
selected={editMode && editComponent === item.i}
>
<ComponentView
id={item.i}
path={components[item.i]?.path}
settings={components[item.i]?.settings}
values={values}
onValuesChange={handleComponentValuesChange}
/>
</LayoutItem>
))}
</ResponsiveGridLayout>
</Grid>
{editMode && (
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
{toolBar}
{editComponent && (
<>
<ComponentEditor
id={editComponent}
path={components[editComponent].path}
settings={components[editComponent].settings}
valueProviders={valueProviders}
onSettingsChange={handleComponentSettingsChange}
/>
</>
)}
</Grid>
)}
</Grid>
</Box>
);
};
//----------------
//Интерфейс модуля
//----------------
export { PanelsEditor };

View File

@ -23,13 +23,6 @@ export const PANEL_UNITS = {
PROJECT_STAGE_ARTS: "PROJECT_STAGE_ARTS" PROJECT_STAGE_ARTS: "PROJECT_STAGE_ARTS"
}; };
//Общие стили
export const COMMON_PROJECTS_STYLES = {
FULL_SCREEN_DIALOG_CONTENT: {
padding: 0
}
};
//----------- //-----------
//Тело модуля //Тело модуля
//----------- //-----------
@ -257,7 +250,6 @@ export const rowExpandRender = ({
columnsDef, columnsDef,
row, row,
pOnlineShowDocument, pOnlineShowDocument,
pOnlineShowUnit,
showStages, showStages,
showPayNotes, showPayNotes,
showCostNotes, showCostNotes,
@ -283,55 +275,42 @@ export const rowExpandRender = ({
const linkButtons = () => const linkButtons = () =>
panelUnit === PANEL_UNITS.PROJECTS ? ( panelUnit === PANEL_UNITS.PROJECTS ? (
<> <>
<Button variant="outlined" onClick={() => showStages({ sender: row })}> <Button fullWidth variant="contained" onClick={() => showStages({ sender: row })}>
Этапы Этапы
</Button> </Button>
<Button variant="outlined" onClick={() => pOnlineShowDocument({ unitCode: "Projects", document: row.NRN, modal: false })}> <Button fullWidth variant="contained" onClick={() => pOnlineShowDocument({ unitCode: "Projects", document: row.NRN })}>
К проекту В раздел
</Button> </Button>
</> </>
) : panelUnit === PANEL_UNITS.PROJECT_STAGES ? ( ) : panelUnit === PANEL_UNITS.PROJECT_STAGES ? (
<> <>
<Button variant="outlined" onClick={() => showStageArts({ sender: row })}> <Button fullWidth variant="contained" onClick={() => showStageArts({ sender: row })}>
Статьи Статьи
</Button> </Button>
<Button variant="outlined" onClick={() => showContracts({ sender: row })}> <Button fullWidth variant="contained" onClick={() => showContracts({ sender: row })}>
Сисполнители Сисполнители
</Button> </Button>
<Button <Button fullWidth variant="contained" onClick={() => pOnlineShowDocument({ unitCode: "ProjectsStages", document: row.NRN })}>
variant="outlined" В раздел
onClick={() =>
pOnlineShowUnit({
unitCode: "Projects",
inputParameters: [
{ name: "in_RN", value: row.NPROJECT },
{ name: "in_STAGE_RN", value: row.NRN }
],
modal: false
})
}
>
К этапу
</Button> </Button>
</> </>
) : panelUnit === PANEL_UNITS.PROJECT_STAGE_CONTRACTS ? ( ) : panelUnit === PANEL_UNITS.PROJECT_STAGE_CONTRACTS ? (
<Button <Button
variant="outlined" fullWidth
onClick={() => pOnlineShowDocument({ unitCode: row.SLNK_UNIT_SDOC_PREF, document: row.NLNK_DOCUMENT_SDOC_PREF, modal: false })} variant="contained"
onClick={() => pOnlineShowDocument({ unitCode: row.SLNK_UNIT_SDOC_PREF, document: row.NLNK_DOCUMENT_SDOC_PREF })}
> >
К договору В раздел
</Button> </Button>
) : null; ) : null;
//Сборка содержимого //Сборка содержимого
return ( return (
<Box p={2}> <Box p={2}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12} md={12}> <Grid item xs={12} md={1}>
<Stack spacing={2} direction="row"> <Stack spacing={2}>{linkButtons()}</Stack>
{linkButtons()}
</Stack>
</Grid> </Grid>
<Grid item xs={12} md={12}> <Grid item xs={12} md={11}>
<Paper elevation={5}> <Paper elevation={5}>
<Table sx={{ width: "100%" }} size="small"> <Table sx={{ width: "100%" }} size="small">
<TableBody> <TableBody>

View File

@ -11,34 +11,23 @@ import React, { useState, useCallback, useEffect, useContext } from "react"; //
import { Box, Grid, Paper, Fab, Icon } from "@mui/material"; //Интерфейсные компоненты import { Box, Grid, Paper, Fab, Icon } from "@mui/material"; //Интерфейсные компоненты
import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_MORE_HEIGHT, P8P_DATA_GRID_FILTERS_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог import { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог
import { P8PChart } from "../../components/p8p_chart"; //График import { P8PChart } from "../../components/p8p_chart"; //График
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { COMMON_PROJECTS_STYLES, PANEL_UNITS, headCellRender, dataCellRender, valueFormatter, rowExpandRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов import { PANEL_UNITS, headCellRender, dataCellRender, valueFormatter, rowExpandRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
import { Stages } from "./stages"; //Список этапов проекта import { Stages } from "./stages"; //Список этапов проекта
//--------- //---------
//Константы //Константы
//--------- //---------
//Высота графиков
const CHART_HEIGHT = "300px";
//Стили //Стили
const STYLES = { const STYLES = {
TABLE_PROJECTS: (showCharts, morePages, filters) => ({ CHART: { maxHeight: "300px", display: "flex", justifyContent: "center" },
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${showCharts ? CHART_HEIGHT : "0px"} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - ${
filters ? P8P_DATA_GRID_FILTERS_HEIGHT : "0px"
} - 25px)`,
...APP_STYLES.SCROLL
}),
CHART: { maxHeight: CHART_HEIGHT, display: "flex", justifyContent: "center" },
CHART_PAPER: { height: "100%" }, CHART_PAPER: { height: "100%" },
CHART_FAB: { position: "absolute", top: 80, left: 16 } CHART_FAB: { position: "absolute", top: 80, left: 16 }
}; };
@ -95,12 +84,11 @@ const Projects = () => {
}); });
setProjectsDataGrid(pv => ({ setProjectsDataGrid(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize morePages: (data.XROWS || []).length >= configSystemPageSize
})); }));
} }
}, [ }, [
@ -231,16 +219,12 @@ const Projects = () => {
{projectsDataGrid.dataLoaded ? ( {projectsDataGrid.dataLoaded ? (
<P8PDataGrid <P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS} {...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{
sx: STYLES.TABLE_PROJECTS(showCharts, projectsDataGrid.morePages, (projectsDataGrid.filters || []).length > 0)
}}
columnsDef={projectsDataGrid.columnsDef} columnsDef={projectsDataGrid.columnsDef}
rows={projectsDataGrid.rows} rows={projectsDataGrid.rows}
size={P8P_DATA_GRID_SIZE.SMALL} size={P8P_DATA_GRID_SIZE.SMALL}
filtersInitial={projectsDataGrid.filters} filtersInitial={projectsDataGrid.filters}
morePages={projectsDataGrid.morePages} morePages={projectsDataGrid.morePages}
reloading={projectsDataGrid.reload} reloading={projectsDataGrid.reload}
fixedHeader={true}
expandable={true} expandable={true}
headCellRender={headCellRender} headCellRender={headCellRender}
dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECTS, showStages })} dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECTS, showStages })}
@ -260,11 +244,7 @@ const Projects = () => {
/> />
) : null} ) : null}
{projectsDataGrid.selectedProject ? ( {projectsDataGrid.selectedProject ? (
<P8PFullScreenDialog <P8PFullScreenDialog title={`Этапы проекта "${projectsDataGrid.selectedProject.SNAME_USL}"`} onClose={handleStagesClose}>
title={`Этапы проекта "${projectsDataGrid.selectedProject.SNAME_USL}"`}
onClose={handleStagesClose}
contentProps={{ sx: COMMON_PROJECTS_STYLES.FULL_SCREEN_DIALOG_CONTENT }}
>
<Stages <Stages
project={projectsDataGrid.selectedProject.NRN} project={projectsDataGrid.selectedProject.NRN}
projectName={projectsDataGrid.selectedProject.SNAME_USL} projectName={projectsDataGrid.selectedProject.SNAME_USL}

View File

@ -12,27 +12,13 @@ import PropTypes from "prop-types"; //Контроль свойств компо
import { Box } from "@mui/material"; //Интерфейсные компоненты import { Box } from "@mui/material"; //Интерфейсные компоненты
import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE } from "../../components/p8p_data_grid"; //Таблица данных
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE, P8P_DATA_GRID_FILTERS_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { PANEL_UNITS, dataCellRender, valueFormatter } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов import { PANEL_UNITS, dataCellRender, valueFormatter } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
//---------
//Константы
//---------
//Стили
const STYLES = {
TABLE_ARTS: filters => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${filters ? P8P_DATA_GRID_FILTERS_HEIGHT : "0px"} - 16px)`,
...APP_STYLES.SCROLL
})
};
//----------- //-----------
//Тело модуля //Тело модуля
//----------- //-----------
@ -71,9 +57,8 @@ const StageArts = ({ stage, filters }) => {
}); });
setStageArtsDataGrid(pv => ({ setStageArtsDataGrid(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: [...(data.XROWS || [])],
rows: [...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false reload: false
})); }));
@ -114,12 +99,10 @@ const StageArts = ({ stage, filters }) => {
{stageArtsDataGrid.dataLoaded ? ( {stageArtsDataGrid.dataLoaded ? (
<P8PDataGrid <P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS} {...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STYLES.TABLE_ARTS((stageArtsDataGrid.filters || []).length > 0), elevation: 0 }}
columnsDef={stageArtsDataGrid.columnsDef} columnsDef={stageArtsDataGrid.columnsDef}
filtersInitial={filters} filtersInitial={filters}
rows={stageArtsDataGrid.rows} rows={stageArtsDataGrid.rows}
size={P8P_DATA_GRID_SIZE.SMALL} size={P8P_DATA_GRID_SIZE.SMALL}
fixedHeader={true}
morePages={false} morePages={false}
reloading={stageArtsDataGrid.reload} reloading={stageArtsDataGrid.reload}
dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECT_STAGE_ARTS, showCostNotes, showContracts })} dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECT_STAGE_ARTS, showCostNotes, showContracts })}

View File

@ -12,35 +12,13 @@ import PropTypes from "prop-types"; //Контроль свойств компо
import { Box } from "@mui/material"; //Интерфейсные компоненты import { Box } from "@mui/material"; //Интерфейсные компоненты
import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE } from "../../components/p8p_data_grid"; //Таблица данных
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import {
P8PDataGrid,
P8P_DATA_GRID_SIZE,
P8P_DATA_GRID_FILTER_SHAPE,
P8P_DATA_GRID_MORE_HEIGHT,
P8P_DATA_GRID_FILTERS_HEIGHT
} from "../../components/p8p_data_grid"; //Таблица данных
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { PANEL_UNITS, dataCellRender, valueFormatter, rowExpandRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов import { PANEL_UNITS, dataCellRender, valueFormatter, rowExpandRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
//---------
//Константы
//---------
//Стили
const STYLES = {
TABLE_CONTRACTS: (morePages, filters) => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - ${
filters ? P8P_DATA_GRID_FILTERS_HEIGHT : "0px"
} - 16px)`,
...APP_STYLES.SCROLL
})
};
//----------- //-----------
//Тело модуля //Тело модуля
//----------- //-----------
@ -89,12 +67,11 @@ const StageContracts = ({ stage, filters }) => {
}); });
setStageContractsDataGrid(pv => ({ setStageContractsDataGrid(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize morePages: (data.XROWS || []).length >= configSystemPageSize
})); }));
} }
}, [ }, [
@ -159,17 +136,12 @@ const StageContracts = ({ stage, filters }) => {
{stageContractsDataGrid.dataLoaded ? ( {stageContractsDataGrid.dataLoaded ? (
<P8PDataGrid <P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS} {...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{
sx: STYLES.TABLE_CONTRACTS(stageContractsDataGrid.morePages, (stageContractsDataGrid.filters || []).length > 0),
elevation: 0
}}
columnsDef={stageContractsDataGrid.columnsDef} columnsDef={stageContractsDataGrid.columnsDef}
filtersInitial={filters} filtersInitial={filters}
rows={stageContractsDataGrid.rows} rows={stageContractsDataGrid.rows}
size={P8P_DATA_GRID_SIZE.SMALL} size={P8P_DATA_GRID_SIZE.SMALL}
morePages={stageContractsDataGrid.morePages} morePages={stageContractsDataGrid.morePages}
reloading={stageContractsDataGrid.reload} reloading={stageContractsDataGrid.reload}
fixedHeader={true}
expandable={true} expandable={true}
dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECT_STAGE_CONTRACTS, pOnlineShowDocument })} dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECT_STAGE_CONTRACTS, pOnlineShowDocument })}
rowExpandRender={prms => rowExpandRender={prms =>

View File

@ -12,15 +12,7 @@ import PropTypes from "prop-types"; //Контроль свойств компо
import { Box } from "@mui/material"; //Интерфейсные компоненты import { Box } from "@mui/material"; //Интерфейсные компоненты
import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции import { object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_FILTER_SHAPE } from "../../components/p8p_data_grid"; //Таблица данных
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import {
P8PDataGrid,
P8P_DATA_GRID_SIZE,
P8P_DATA_GRID_FILTER_SHAPE,
P8P_DATA_GRID_MORE_HEIGHT,
P8P_DATA_GRID_FILTERS_HEIGHT
} from "../../components/p8p_data_grid"; //Таблица данных
import { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог import { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог
import { StageArts } from "./stage_arts"; //Калькуляция этапа проекта import { StageArts } from "./stage_arts"; //Калькуляция этапа проекта
import { StageContracts } from "./stage_contracts"; //Договоры с соисполнителями этапа проекта import { StageContracts } from "./stage_contracts"; //Договоры с соисполнителями этапа проекта
@ -28,21 +20,7 @@ import { BackEndСtx } from "../../context/backend"; //Контекст взаи
import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { COMMON_PROJECTS_STYLES, PANEL_UNITS, headCellRender, dataCellRender, valueFormatter, rowExpandRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов import { PANEL_UNITS, headCellRender, dataCellRender, valueFormatter, rowExpandRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
//---------
//Константы
//---------
//Стили
const STYLES = {
TABLE_STAGES: (morePages, filters) => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - ${
filters ? P8P_DATA_GRID_FILTERS_HEIGHT : "0px"
} - 16px)`,
...APP_STYLES.SCROLL
})
};
//----------- //-----------
//Тело модуля //Тело модуля
@ -93,12 +71,11 @@ const Stages = ({ project, projectName, filters }) => {
}); });
setStagesDataGrid(pv => ({ setStagesDataGrid(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize morePages: (data.XROWS || []).length >= configSystemPageSize
})); }));
} }
}, [ }, [
@ -177,17 +154,12 @@ const Stages = ({ project, projectName, filters }) => {
{stagesDataGrid.dataLoaded ? ( {stagesDataGrid.dataLoaded ? (
<P8PDataGrid <P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS} {...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{
sx: STYLES.TABLE_STAGES(stagesDataGrid.morePages, (stagesDataGrid.filters || []).length > 0),
elevation: 0
}}
columnsDef={stagesDataGrid.columnsDef} columnsDef={stagesDataGrid.columnsDef}
filtersInitial={filters} filtersInitial={filters}
rows={stagesDataGrid.rows} rows={stagesDataGrid.rows}
size={P8P_DATA_GRID_SIZE.SMALL} size={P8P_DATA_GRID_SIZE.SMALL}
morePages={stagesDataGrid.morePages} morePages={stagesDataGrid.morePages}
reloading={stagesDataGrid.reload} reloading={stagesDataGrid.reload}
fixedHeader={true}
expandable={true} expandable={true}
headCellRender={headCellRender} headCellRender={headCellRender}
dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECT_STAGES, showStageArts, showContracts })} dataCellRender={prms => dataCellRender({ ...prms, panelUnit: PANEL_UNITS.PROJECT_STAGES, showStageArts, showContracts })}
@ -196,7 +168,6 @@ const Stages = ({ project, projectName, filters }) => {
...prms, ...prms,
panelUnit: PANEL_UNITS.PROJECT_STAGES, panelUnit: PANEL_UNITS.PROJECT_STAGES,
pOnlineShowDocument, pOnlineShowDocument,
pOnlineShowUnit,
showStageArts, showStageArts,
showContracts, showContracts,
showPayNotes, showPayNotes,
@ -214,7 +185,6 @@ const Stages = ({ project, projectName, filters }) => {
<P8PFullScreenDialog <P8PFullScreenDialog
title={`Договоры этапа "${stagesDataGrid.selectedStageNumb}" проекта "${projectName}"`} title={`Договоры этапа "${stagesDataGrid.selectedStageNumb}" проекта "${projectName}"`}
onClose={handleStageContractsClose} onClose={handleStageContractsClose}
contentProps={{ sx: COMMON_PROJECTS_STYLES.FULL_SCREEN_DIALOG_CONTENT }}
> >
<StageContracts stage={stagesDataGrid.showStageContracts} filters={stagesDataGrid.stageContractsFilters} /> <StageContracts stage={stagesDataGrid.showStageContracts} filters={stagesDataGrid.stageContractsFilters} />
</P8PFullScreenDialog> </P8PFullScreenDialog>
@ -223,7 +193,6 @@ const Stages = ({ project, projectName, filters }) => {
<P8PFullScreenDialog <P8PFullScreenDialog
title={`Калькуляция этапа "${stagesDataGrid.selectedStageNumb}" проекта "${projectName}"`} title={`Калькуляция этапа "${stagesDataGrid.selectedStageNumb}" проекта "${projectName}"`}
onClose={handleStageArtsClose} onClose={handleStageArtsClose}
contentProps={{ sx: COMMON_PROJECTS_STYLES.FULL_SCREEN_DIALOG_CONTENT }}
> >
<StageArts stage={stagesDataGrid.showStageArts} filters={stagesDataGrid.stageArtsFilters} /> <StageArts stage={stagesDataGrid.showStageArts} filters={stagesDataGrid.stageArtsFilters} />
</P8PFullScreenDialog> </P8PFullScreenDialog>

View File

@ -55,10 +55,11 @@ const PrjGraph = () => {
const data = await executeStored({ stored: "PKG_P8PANELS_PROJECTS.GRAPH", args: {}, respArg: "COUT" }); const data = await executeStored({ stored: "PKG_P8PANELS_PROJECTS.GRAPH", args: {}, respArg: "COUT" });
setdataGrid(pv => ({ setdataGrid(pv => ({
...pv, ...pv,
...data.XDATA_GRID, fixedHeader: data.XDATA_GRID.fixedHeader,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, fixedColumns: data.XDATA_GRID.fixedColumns,
rows: [...(data.XDATA_GRID.rows || [])], columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
groups: [...(data.XDATA_GRID.groups || [])], rows: [...(data.XROWS || [])],
groups: [...(data.XGROUPS || [])],
dataLoaded: true, dataLoaded: true,
reload: false reload: false
})); }));

View File

@ -1,170 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Фильтр
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Grid, Chip, Stack, Input, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
import { FILTER_INITIAL, FILTER_ITEMS, PRICE_STRUCT_STATUS, PROJECT_STATE, FilterDialog } from "./filter_dialog"; //Компонент "Диалог фильтра"
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { paddingTop: "10px" },
FILTER: { maxWidth: "99vw" },
SEARCH_GRID_ITEM: { paddingRight: "15px" }
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Элемент фильтра
const FilterItem = ({ caption, value, defaultValue, onClick, onDelete }) => {
//При нажатии на элемент
const handleClick = () => (onClick ? onClick() : null);
//При нажатии на удаление элемента
const handleDelete = () => (onDelete ? onDelete() : null);
//Генерация содержимого
return (
<Chip
label={
<Stack direction={"row"} alignItems={"center"}>
<strong>{caption}</strong>:&nbsp;{value || defaultValue}
</Stack>
}
variant="outlined"
onClick={handleClick}
onDelete={onDelete ? handleDelete : null}
/>
);
};
//Контроль свойств компонента - Элемент фильтра
FilterItem.propTypes = {
caption: PropTypes.string.isRequired,
value: PropTypes.any,
defaultValue: PropTypes.string.isRequired,
onClick: PropTypes.func,
onDelete: PropTypes.func
};
//-----------
//Тело модуля
//-----------
//Фильтр
const Filter = ({ values, onChange }) => {
//Собственное состояние - отображение диалога ввода значений фильтра
const [isOpen, setIsOpen] = useState(false);
//Собственное состояние - строка поиска
const [search, setSearch] = useState(values.search);
//Передача сообщения об измении фильтра родителю
const notifyChange = values => (onChange ? onChange(values) : null);
//При закрытии диалога с сохранением значений
const handleFilterDialogOk = values => {
setIsOpen(false);
notifyChange(values);
};
//При закрытии диалога без сохранения значений
const handleFilterDialogCancel = () => setIsOpen(false);
//При нажатии на фильтр
const handleClick = () => setIsOpen(true);
//При выполнении поиска
const handleDoSearch = (clear = false) => {
if (clear === true) setSearch("");
notifyChange({ ...values, search: clear === true ? "" : search });
};
//При изменении значения в строке поиска
const handleSearchChange = e => setSearch(e.target.value);
//При нажатии клавиши в строке поиска
const handleSearchKeyPress = e => ([13, 27].includes(e.keyCode) ? handleDoSearch(e.keyCode == 27) : null);
//Формирование функции обработки очистки элемента фильтар
const buildFilterItemClearHandler = сode =>
values[сode] != FILTER_INITIAL[сode] ? () => notifyChange({ ...values, [сode]: FILTER_INITIAL[сode] }) : null;
//Генерация содержимого
return (
<Grid container sx={STYLES.CONTAINER}>
<Grid xs={10} item>
{isOpen ? <FilterDialog valuesInitial={values} onOk={handleFilterDialogOk} onCancel={handleFilterDialogCancel} /> : null}
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.FILTER} onClick={handleClick}>
<FilterItem
caption={"Тип заказа"}
value={values.prjType}
defaultValue={"Любой"}
onDelete={buildFilterItemClearHandler("prjType")}
/>
<FilterItem
caption={"Подразделение-ответственный"}
defaultValue={"Любое"}
value={values.insDep}
onDelete={buildFilterItemClearHandler("insDep")}
/>
<FilterItem
caption={"Статус структуры цены"}
defaultValue={"Неподдерживаемое значение"}
value={PRICE_STRUCT_STATUS.find(item => item.value == values.priceStructStatus)?.name}
onDelete={buildFilterItemClearHandler("priceStructStatus")}
/>
<FilterItem
caption={"Состояние проекта"}
defaultValue={"Неподдерживаемое значение"}
value={PROJECT_STATE.find(item => item.value == values.prjState)?.name}
onDelete={buildFilterItemClearHandler("prjState")}
/>
</Stack>
</Grid>
<Grid xs={2} item sx={STYLES.SEARCH_GRID_ITEM}>
<Input
fullWidth
placeholder="Поиск..."
value={search}
endAdornment={
<InputAdornment position="end">
<IconButton onClick={() => handleDoSearch(true)}>
<Icon>clear</Icon>
</IconButton>
<IconButton onClick={handleDoSearch}>
<Icon>search</Icon>
</IconButton>
</InputAdornment>
}
onKeyDown={handleSearchKeyPress}
onChange={handleSearchChange}
/>
</Grid>
</Grid>
);
};
//Контроль свойств компонента - Фильтр
Filter.propTypes = {
values: FILTER_ITEMS.isRequired,
onChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { FILTER_INITIAL, Filter };

View File

@ -1,147 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Диалог фильтра
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Button, Dialog, DialogTitle, DialogContent, DialogActions } from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { BUTTONS } from "../../../app.text"; //Типовые тексты
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { FormField } from "./layouts"; //Общие компоненты панели
//---------
//Константы
//---------
//Стили
const STYLES = {
DIALOG_CONTENT: { overflowY: "auto", ...APP_STYLES.SCROLL }
};
//Структура фильтра
const FILTER_ITEMS = PropTypes.shape({
prjType: PropTypes.string,
insDep: PropTypes.string,
priceStructStatus: PropTypes.number.isRequired,
prjState: PropTypes.number.isRequired,
search: PropTypes.string
});
//Начальное состояние фильтра
const FILTER_INITIAL = { prjType: "", insDep: "", priceStructStatus: 0, prjState: 0, search: "" };
//Статусы структуры цены
const PRICE_STRUCT_STATUS = [
{ value: 0, name: "Все" },
{ value: 1, name: "Есть статьи с расходом больше 90%" },
{ value: 2, name: "Есть статьи с перерасходом" }
];
//Состояния проекта
const PROJECT_STATE = [
{ value: 0, name: "Все" },
{ value: 1, name: "Открытые" },
{ value: 2, name: "Неоткрытые" }
];
//-----------
//Тело модуля
//-----------
//Диалог фильтра
const FilterDialog = ({ valuesInitial, onOk, onCancel }) => {
//Собственное состояние элементов фильтра
const [values, setValues] = useState({ ...valuesInitial });
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Изменение элемента формы фильтра
const handleValueChanged = (name, value) => setValues(pv => ({ ...pv, [name]: value }));
//Сброс настроек фильтра
const handleResetClick = () => setValues({ ...FILTER_INITIAL });
//Сохранение фильтра
const handleOkClick = () => (onOk ? onOk(values) : null);
//Отмена фильтра
const handleCancelClick = () => (onCancel ? onCancel() : null);
//Выбор значения элемента формы из словаря
const selectFromDictionary = (unitCode, name, applyValue) => {
pOnlineShowDictionary({
unitCode,
showMethod: "main",
inputParameters: [{ name: "in_CODE", value: values[name] }],
callBack: res => applyValue(res.success ? [{ name, value: res.outParameters.out_CODE }] : null)
});
};
//Генерация содержимого
return (
<Dialog open onClose={handleCancelClick} fullWidth maxWidth={"md"}>
<DialogTitle>Фильтр отбора</DialogTitle>
<DialogContent sx={STYLES.DIALOG_CONTENT}>
<FormField
elementCode={"prjType"}
elementValue={values.prjType}
labelText={"Тип заказа"}
onChange={handleValueChanged}
dictionary={applyValue => selectFromDictionary("ProjectTypes", "prjType", applyValue)}
/>
<FormField
elementCode={"insDep"}
elementValue={values.insDep}
labelText={"Подразделение-ответственный"}
onChange={handleValueChanged}
dictionary={applyValue => selectFromDictionary("INS_DEPARTMENT", "insDep", applyValue)}
/>
<FormField
elementCode={"priceStructStatus"}
elementValue={values.priceStructStatus}
labelText={"Статус структуры цены"}
onChange={handleValueChanged}
list={PRICE_STRUCT_STATUS}
/>
<FormField
elementCode={"prjState"}
elementValue={values.prjState}
labelText={"Состояние проекта"}
onChange={handleValueChanged}
list={PROJECT_STATE}
/>
</DialogContent>
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button variant="text" onClick={handleOkClick}>
{BUTTONS.OK}
</Button>
<Button variant="text" onClick={handleResetClick}>
{BUTTONS.CLEAR}
</Button>
<Button variant="text" onClick={handleCancelClick}>
{BUTTONS.CANCEL}
</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств компонента - Диалог фильтра
FilterDialog.propTypes = {
valuesInitial: FILTER_ITEMS.isRequired,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { FILTER_ITEMS, FILTER_INITIAL, PRICE_STRUCT_STATUS, PROJECT_STATE, FilterDialog };

View File

@ -1,16 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Панель мониторинга: точка входа
*/
//---------------------
//Подключение библиотек
//---------------------
import { PrjInfo } from "./prj_info"; //Корневая панель информации о проектах
//----------------
//Интерфейс модуля
//----------------
export const RootClass = PrjInfo;

View File

@ -1,168 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Общие дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Icon, Input, InputAdornment, FormControl, Select, InputLabel, MenuItem, IconButton, Typography, Switch, Stack } from "@mui/material"; //Интерфейсные компоненты
//---------
//Константы
//---------
//Стили
const STYLES = {
STATE: value => ({ color: value === 1 ? "green" : "black" }),
COST_STATUS: color => ({ color, verticalAlign: "middle" }),
COST_READY: value => ({ color: value <= 30 ? "red" : value >= 80 ? "green" : "#e2af00" }),
TOGGLE_COLOR: checked => ({ color: checked ? "#006dd9" : "lightgrey" })
};
//-----------
//Тело модуля
//-----------
//Поле ввода формы
const FormField = ({ elementCode, elementValue, labelText, onChange, dictionary, list, type, ...other }) => {
//Значение элемента
const [value, setValue] = useState(elementValue);
//При получении нового значения из вне
useEffect(() => {
setValue(elementValue);
}, [elementValue]);
//Выбор значения из словаря
const handleDictionaryClick = () =>
dictionary ? dictionary(res => (res ? res.map(i => handleChange({ target: { name: i.name, value: i.value } })) : null)) : null;
//Изменение значения элемента (по событию)
const handleChange = e => {
setValue(e.target.value);
if (onChange) onChange(e.target.name, e.target.value);
};
//Генерация содержимого
return (
<Box p={1}>
<FormControl variant="standard" fullWidth {...other}>
{list ? (
<>
<InputLabel id={`${elementCode}Lable`} shrink>
{labelText}
</InputLabel>
<Select
labelId={`${elementCode}Lable`}
id={elementCode}
name={elementCode}
label={labelText}
value={value || value == 0 ? value : ""}
onChange={handleChange}
displayEmpty
>
{list.map((item, i) => (
<MenuItem key={i} value={item.value || item.value == 0 ? item.value : ""}>
{item.name}
</MenuItem>
))}
</Select>
</>
) : (
<>
<InputLabel {...(type == "date" ? { shrink: true } : {})} htmlFor={elementCode}>
{labelText}
</InputLabel>
<Input
id={elementCode}
name={elementCode}
value={value || value == 0 ? value : ""}
endAdornment={
dictionary ? (
<InputAdornment position="end">
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
<Icon>list</Icon>
</IconButton>
</InputAdornment>
) : null
}
{...(type ? { type } : {})}
onChange={handleChange}
/>
</>
)}
</FormControl>
</Box>
);
};
//Контроль свойств - Поле ввода формы
FormField.propTypes = {
elementCode: PropTypes.string.isRequired,
elementValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.instanceOf(Date)]),
labelText: PropTypes.string.isRequired,
onChange: PropTypes.func,
dictionary: PropTypes.func,
list: PropTypes.array,
type: PropTypes.string
};
//Переключатель
const Toggle = ({ labels, checked, onChange }) => {
//Обработка переключения
const handleChange = event => (onChange ? onChange(event.target.checked) : null);
//Генерация содержимого
return (
<Stack direction={"row"} spacing={1} alignItems={"center"} justifyContent={"center"}>
<Typography sx={STYLES.TOGGLE_COLOR(!checked)}>{labels[0]}</Typography>
<Switch checked={checked} size="small" onChange={handleChange} />
<Typography sx={STYLES.TOGGLE_COLOR(checked)}>{labels[1]}</Typography>
</Stack>
);
};
//Контроль свойств компонента - Переключатель
Toggle.propTypes = {
labels: PropTypes.arrayOf(PropTypes.string).isRequired,
checked: PropTypes.bool.isRequired,
onChange: PropTypes.func
};
//Формирование значения для колонки "Статус структуры цены"
const formatCostStatusValue = ({ value, onClick, type = 1 }) => {
const [text, color] =
value == 0
? ["Без отклонений", "lightgreen"]
: value == 1
? [type == 1 ? "Есть статьи с расходом более 90%" : "Расход более 90%", "#ffdf71"]
: value == 2
? [type == 1 ? "Есть статьи с перерасходом" : "Перерасход", "#eb6b6b"]
: ["Не определено", "lightgray"];
return onClick ? (
<IconButton onClick={onClick}>
<Icon sx={STYLES.COST_STATUS(color)} title={`${text}\nНажмите для детальной информации`}>
circle
</Icon>
</IconButton>
) : (
<Icon sx={STYLES.COST_STATUS(color)} title={text}>
circle
</Icon>
);
};
//Формирование значения для колонки "Готов (%, зтраты)"
const formatCostReadyValue = value => {
return <span style={STYLES.COST_READY(value)}>{value}</span>;
};
//----------------
//Интерфейс модуля
//----------------
export { STYLES as COMMON_STYLES, FormField, Toggle, formatCostStatusValue, formatCostReadyValue };

View File

@ -1,27 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Корневой компонент панели
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Projects } from "./projects"; //Список проектов
//-----------
//Тело модуля
//-----------
//Корневой компонент панели "Информация о проектах"
const PrjInfo = () => {
//Генерация содержимого
return <Projects />;
};
//----------------
//Интерфейс модуля
//----------------
export { PrjInfo };

View File

@ -1,70 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список проектов
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import { P8PDataGrid } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { useProjectsDataGrid } from "./projects_hooks"; //Хуки списка проектов
import { FILTER_INITIAL, Filter } from "./filter"; //Компонент "Фильтр"
import { PROJECTS_STYLES, projectDataCellRender, projectRowExpandRender } from "./projects_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
//-----------
//Тело модуля
//-----------
//Список проектов
const Projects = () => {
//Собственное состояние
const [projects, setProjects] = useState({ pageNumber: 1, orders: [], filter: { ...FILTER_INITIAL } });
//Состояние таблицы проектов
const [projectsDataGrid] = useProjectsDataGrid({ ...projects.filter, pageNumber: projects.pageNumber, orders: projects.orders });
//Подключение к контексту приложения
const { pOnlineShowDocument } = useContext(ApplicationСtx);
//Отображение записи проекта в штатном разделе
const showProject = async rn => pOnlineShowDocument({ unitCode: "Projects", document: rn, modal: false });
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setProjects(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setProjects(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));
//При изменении фильтра
const handleFilterChanged = values => setProjects(pv => ({ ...pv, filter: { ...values }, pageNumber: 1 }));
//Генерация содержимого
return (
<>
<Filter values={projects.filter} onChange={handleFilterChanged} />
{projectsDataGrid.init ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...projectsDataGrid}
containerComponentProps={{ sx: PROJECTS_STYLES.DATA_GRID_CONTAINER(projectsDataGrid.morePages), elevation: 0 }}
expandable={true}
fixedHeader={true}
onPagesCountChanged={handlePagesCountChanged}
onOrderChanged={handleOrderChanged}
dataCellRender={prms => projectDataCellRender({ ...prms, showProject })}
rowExpandRender={projectRowExpandRender}
/>
) : null}
</>
);
};
//----------------
//Интерфейс модуля
//----------------
export { Projects };

View File

@ -1,82 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список проектов: пользовательские хуки для взаимодействия с сервером
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
import config from "../../../app.config"; //Настройки приложения
//---------
//Константы
//---------
//Размер страницы данных
const DATA_GRID_PAGE_SIZE = config.SYSTEM.PAGE_SIZE;
//-----------
//Тело модуля
//-----------
//Получение данных проектов с сервера
const useProjectsDataGrid = ({ prjType, insDep, priceStructStatus, prjState, search, pageNumber, orders }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({ init: false, morePages: true });
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_PROJECTS_DG",
args: {
SPRJ_TYPE: prjType,
SINS_DEPARTMENT: insDep,
NCOST_STATUS: priceStructStatus,
NSTATE: prjState,
SSEARCH: search,
CORDERS: { VALUE: object2Base64XML(orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
NINCLUDE_DEF: pageNumber == 1 ? 1 : 0
},
respArg: "COUT",
loader: true,
attributeValueProcessor: (name, val) => (["DBEGPLAN", "DENDPLAN"].includes(name) ? formatDateRF(val) : val)
});
setData(pv => ({
...pv,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [],
rows: pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])],
morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE,
init: true
}));
} finally {
setLoading(false);
}
};
loadData();
}, [prjType, insDep, priceStructStatus, prjState, search, pageNumber, orders, executeStored, SERV_DATA_TYPE_CLOB]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//----------------
//Интерфейс модуля
//----------------
export { useProjectsDataGrid };

View File

@ -1,96 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список проектов: дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Icon, Stack, Paper, Link } from "@mui/material"; //Интерфейсные элементы
import { P8P_DATA_GRID_MORE_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { COMMON_STYLES, formatCostStatusValue, formatCostReadyValue } from "./layouts"; //Общие стили и разметка панели
import { Stages } from "./stages"; //Компонент "Этапы проекта"
//---------
//Константы
//---------
//Высота фильтра (пиксели)
const FILTER_HEIGHT = "60px";
//Стили
const STYLES = {
DATA_GRID_CONTAINER: morePages => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${FILTER_HEIGHT} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - 8px)`,
...APP_STYLES.SCROLL
})
};
//-----------
//Тело модуля
//-----------
//Формирование значения для колонки "Состояние" проекта
const formatPrjStateValue = value => {
const [text, icon] =
value == 0
? ["Зарегистрирован", "app_registration"]
: value == 1
? ["Открыт", "lock_open"]
: value == 2
? ["Остановлен", "do_not_disturb_on"]
: value == 3
? ["Закрыт", "lock_outline"]
: value == 4
? ["Согласован", "thumb_up_alt"]
: ["Исполнение прекращено", "block"];
return (
<Stack direction="row" gap={0.5} alignItems="center" justifyContent="center">
<Icon title={text} sx={COMMON_STYLES.STATE(value)}>
{icon}
</Icon>
</Stack>
);
};
//Форматирование ячеек таблицы "Проекты"
const projectDataCellRender = ({ row, columnDef, showProject }) => {
//Формирование представлений
switch (columnDef.name) {
case "NCOST_STATUS":
return { cellProps: { align: "center" }, data: formatCostStatusValue({ value: row[columnDef.name] }) };
case "NCOST_READY":
return { cellProps: { align: "center" }, data: formatCostReadyValue(row[columnDef.name]) };
case "NSTATE":
return { cellProps: { align: "center" }, data: formatPrjStateValue(row[columnDef.name]) };
case "SCODE":
return {
data: (
<Link component="button" align="left" underline="hover" onClick={() => showProject(row["NRN"])}>
{row[columnDef.name]}
</Link>
)
};
default:
return { data: row[columnDef.name] };
}
};
//Генерация представления расширения строки таблицы "Проектов"
const projectRowExpandRender = ({ row }) => {
return (
<Paper elevation={6}>
<Stages projectRn={row.NRN} projectCode={row.SCODE} />
</Paper>
);
};
//----------------
//Интерфейс модуля
//----------------
export { STYLES as PROJECTS_STYLES, projectDataCellRender, projectRowExpandRender };

View File

@ -1,173 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Детальная информация об этапе проекта
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Grid, Box, Typography, Paper, Drawer, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8PChart } from "../../components/p8p_chart"; //График
import { P8PAppInlineError } from "../../components/p8p_app_message"; //Встраиваемое сообщение об ошибке
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useStageDetailInfoDataGrid, useStageDetailArtsDataGrid, useStageDetailArtsChart } from "./stage_detail_hooks"; //Хуки детализации этапов проекта
import { Toggle } from "./layouts"; //Общая разметка и компоненты панели
import {
STAGE_DETAIL_STYLES,
stageDetailInfoHeadCellRender,
stageDetailInfoDataCellRender,
stageDetailArtsHeadCellRender,
stageDetailArtsDataCellRender
} from "./stage_detail_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Данные этапа
const StageDetailData = ({ stageRn }) => {
//Собственное состояние
const [state, setState] = useState({ artsDisplayType: 0, artsChartType: 0 });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Подключение к контексту приложения
const { pOnlineShowUnit } = useContext(ApplicationСtx);
//Подключение к контексту сообщений
const { showMsgErr } = useContext(MessagingСtx);
//Отображение журнала затрат (фактического, по рег. номеру ЛС и статьи затрат)
const showCostNotesFact = async ({ faceAccRn, artclRn }) => {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_FCCOSTNOTES_FACT_SELECT",
args: { NFACEACC: faceAccRn, NFPDARTCL: artclRn }
});
if (data.NIDENT) pOnlineShowUnit({ unitCode: "CostNotes", inputParameters: [{ name: "in_IDENT", value: data.NIDENT }] });
else showMsgErr(TEXTS.NO_DATA_FOUND);
};
//Состояние таблицы с информацией об этапе
const [stageDeatilInfoDataGrid] = useStageDetailInfoDataGrid({ stageRn });
//Состояние таблицы с данными структуры цены
const [stageDeatilArtsDataGrid] = useStageDetailArtsDataGrid({ stageRn });
//Состояние графика с данными структуры цены
const [stageDeatilArtsChart] = useStageDetailArtsChart({ stageRn, display: state.artsDisplayType == 1, type: state.artsChartType });
//При изменении способа отображения структуры цены
const handleArtsDisplayTypeChange = checked => setState(pv => ({ ...pv, artsDisplayType: checked ? 1 : 0 }));
//При изменении типа данных графика структуры цены
const handleArtsChartTypeChange = checked => setState(pv => ({ ...pv, artsChartType: checked ? 1 : 0 }));
//Отработка нажатия на график
const handleChartClick = ({ item }) =>
state.artsChartType === 1 && item.NFACEACC && item.NFPDARTCL
? showCostNotesFact({ faceAccRn: item.NFACEACC, artclRn: item.NFPDARTCL })
: null;
//Генерация содержимого
return (
<Grid container spacing={2} sx={STAGE_DETAIL_STYLES.DATA_AREA_CONTAINER}>
<Grid item xs={5}>
<Typography variant={"h6"} sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER}>
Сведения
</Typography>
{stageDeatilInfoDataGrid.init ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STAGE_DETAIL_STYLES.DATA_AREA, elevation: 6 }}
{...stageDeatilInfoDataGrid}
size={P8P_DATA_GRID_SIZE.SMALL}
fixedHeader={true}
headCellRender={stageDetailInfoHeadCellRender}
dataCellRender={stageDetailInfoDataCellRender}
/>
) : null}
</Grid>
<Grid item xs={7}>
<Box sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER_CONTAINER}>
<Typography variant={"h6"} sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER}>
Структура цены
</Typography>
<Toggle labels={["Таблица", "График"]} checked={state.artsDisplayType === 1} onChange={handleArtsDisplayTypeChange} />
</Box>
{state.artsDisplayType === 0 ? (
stageDeatilArtsDataGrid.init ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STAGE_DETAIL_STYLES.DATA_AREA, elevation: 6 }}
{...stageDeatilArtsDataGrid}
size={P8P_DATA_GRID_SIZE.SMALL}
fixedHeader={true}
headCellRender={stageDetailArtsHeadCellRender}
dataCellRender={prms => stageDetailArtsDataCellRender({ ...prms, showCostNotesFact })}
/>
) : null
) : (
<Paper elevation={6} sx={STAGE_DETAIL_STYLES.DATA_AREA}>
<Box sx={STAGE_DETAIL_STYLES.CHART_CONTAINER}>
<Toggle labels={["План", "Факт"]} checked={state.artsChartType === 1} onChange={handleArtsChartTypeChange} />
{stageDeatilArtsDataGrid?.rows?.length > 0 ? (
stageDeatilArtsChart.init ? (
<P8PChart style={STAGE_DETAIL_STYLES.CHART} {...stageDeatilArtsChart} onClick={handleChartClick} />
) : null
) : (
<P8PAppInlineError text={TEXTS.NO_DATA_FOUND} />
)}
</Box>
</Paper>
)}
</Grid>
</Grid>
);
};
//Контроль свойств компонента - Данные этапа
StageDetailData.propTypes = {
stageRn: PropTypes.number
};
//-----------
//Тело модуля
//-----------
//Детальная информация об этапе проекта
const StageDetail = ({ stageRn, stageName, isOpen, onClose }) => {
return (
<Drawer anchor={"right"} open={isOpen} onClose={onClose} sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_DRAWER}>
<Box sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_HEADER}>
<IconButton sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_CLOSE_BUTTON} size={"small"} onClick={onClose}>
<Icon>close</Icon>
</IconButton>
<Typography variant={"h6"} color={"white"} pl={2}>{`Этап: ${stageName}`}</Typography>
</Box>
<StageDetailData stageRn={stageRn} />
</Drawer>
);
};
//Контроль свойств компонента - Детальная информация об этапе проекта
StageDetail.propTypes = {
stageRn: PropTypes.number,
stageName: PropTypes.string,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { StageDetail };

View File

@ -1,126 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Детальная информация об этапе проекта: пользовательские хуки для взаимодействия с сервером
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
//-----------
//Тело модуля
//-----------
//Детали этапа проекта - информация об этапе
const useStageDetailInfoDataGrid = ({ stageRn }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({ init: false });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_DTL_DG",
args: { NPROJECTSTAGE: stageRn },
respArg: "COUT",
loader: true
});
setData(pv => ({ ...pv, ...data.XDATA_GRID, init: true }));
} finally {
setLoading(false);
}
};
if (stageRn) loadData();
}, [stageRn, executeStored]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//Детали этапа проекта - структура цены - таблица данных
const useStageDetailArtsDataGrid = ({ stageRn }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({ init: false });
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const artsData = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_ARTS_DG",
args: { NPROJECTSTAGE: stageRn },
respArg: "COUT",
loader: true
});
setData(pv => ({ ...pv, ...artsData.XDATA_GRID, init: true }));
} finally {
setLoading(false);
}
};
if (stageRn) loadData();
}, [stageRn, executeStored, SERV_DATA_TYPE_CLOB]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//Детали этапа проекта - структура цены - график
const useStageDetailArtsChart = ({ stageRn, display, type }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - график
const [data, setData] = useState({ init: false, currentType: null });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_ARTS_CHART",
args: { NPROJECTSTAGE: stageRn, NTYPE: type },
respArg: "COUT",
loader: true
});
setData(pv => ({ ...pv, ...data.XCHART, currentType: type, init: true }));
} finally {
setLoading(false);
}
};
if (stageRn && display && data.currentType != type) loadData();
}, [stageRn, display, type, data.currentType, executeStored]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//----------------
//Интерфейс модуля
//----------------
export { useStageDetailInfoDataGrid, useStageDetailArtsDataGrid, useStageDetailArtsChart };

View File

@ -1,148 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Детальная информация об этапе проекта: дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Link } from "@mui/material"; //Интерфейсные элементы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { formatNumberRFCurrency } from "../../core/utils"; //Вспомогательные функции
import { formatCostStatusValue } from "./layouts"; //Общие стили и разметка панели
import { formatStageStatusValue } from "./stages_layouts"; //Cтили и разметка списка этапов проекта
//---------
//Константы
//---------
//Высота заголовка информационного блока
const DATA_AREA_HEADER_HEIGHT = "52px";
//Стили
const STYLES = {
STAGE_DETAIL_DRAWER: { flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "70%", boxSizing: "border-box", ...APP_STYLES.SCROLL } },
STAGE_DETAIL_HEADER: {
height: APP_BAR_HEIGHT,
paddingLeft: "24px",
backgroundColor: "#1976d2",
display: "flex",
alignItems: "center",
justifyContent: "flex-start"
},
STAGE_DETAIL_CLOSE_BUTTON: { color: "white", marginBottom: "3px" },
DATA_AREA_CONTAINER: { paddingLeft: "10px", paddingRight: "10px" },
DATA_AREA: { height: `calc(100vh - ${APP_BAR_HEIGHT} - ${DATA_AREA_HEADER_HEIGHT} - 10px)`, overflowY: "auto", ...APP_STYLES.SCROLL },
DATA_AREA_HEADER_CONTAINER: { display: "flex", justifyContent: "space-between" },
DATA_AREA_HEADER: { paddingTop: "10px", paddingBottom: "10px" },
DATA_GRID_HEADER: { fontSize: "10pt", padding: "6px 10px" },
DATA_GRID_CELL: value => ({ fontSize: "9pt", padding: "6px 10px", ...(value ? { color: value > 0 ? "green" : "red" } : {}) }),
CHART_CONTAINER: { paddingTop: "20px" },
CHART: { maxHeight: "60vh", display: "flex", justifyContent: "center" }
};
//-----------
//Тело модуля
//-----------
//Форматирование заголовков колонок таблицы "Сведения"
const stageDetailInfoHeadCellRender = ({ columnDef }) => {
//Инициализируем общий стиль ячеек
let cellStyle = STYLES.DATA_GRID_HEADER;
//Формирование представлений
switch (columnDef.name) {
case "SATTR":
return { cellStyle, stackProps: { justifyContent: "left" } };
case "SVALUE":
return { cellStyle, stackProps: { justifyContent: "right" } };
default:
return { cellStyle: cellStyle };
}
};
//Форматирование ячеек строк таблицы "Сведения"
const stageDetailInfoDataCellRender = ({ row, columnDef }) => {
//Инициализируем общий стиль ячеек
let cellStyle = STYLES.DATA_GRID_CELL();
//Формирование представлений
switch (columnDef.name) {
case "SATTR":
return { cellStyle: { ...cellStyle, color: "#1976d2" }, cellProps: { align: "left" } };
case "SVALUE": {
const res = { cellStyle, cellProps: { align: "right" } };
if (["NCOST_SUM", "NSTAGE_COST_SUM"].includes(row["SCODE"]))
res.data = row["SVALUE"] || row["SVALUE"] === 0 ? formatNumberRFCurrency(row["SVALUE"]) : "-";
if (row["SCODE"] == "NSTATE")
res.data = formatStageStatusValue({ value: parseInt(row["SVALUE"]), addText: true, justifyContent: "right" });
return res;
}
default:
return { cellStyle };
}
};
//Форматирование заголовков колонок таблицы "Структура затрат"
const stageDetailArtsHeadCellRender = ({ columnDef }) => {
//Инициализируем общий стиль ячеек
let cellStyle = STYLES.DATA_GRID_HEADER;
//Формирование представлений
switch (columnDef.name) {
case "NSTATE":
return { cellStyle: { ...cellStyle, justifyContent: "center" }, stackStyle: { justifyContent: "center" } };
default:
return { cellStyle: cellStyle };
}
};
//Форматирование ячеек строк таблицы "Структура затрат"
const stageDetailArtsDataCellRender = ({ row, columnDef, showCostNotesFact }) => {
//Инициализируем общий стиль ячеек
let cellStyle = STYLES.DATA_GRID_CELL;
//Формирование представлений
switch (columnDef.name) {
case "NCOST_STATUS":
return {
cellProps: { align: "center" },
data: formatCostStatusValue({ value: row[columnDef.name], type: 0 })
};
case "NPLAN_SUM":
case "NPLAN_FACT_SUM":
return {
cellStyle: cellStyle(columnDef.name == "NPLAN_FACT_SUM" ? row[columnDef.name] : null),
data: row[columnDef.name] || row[columnDef.name] === 0 ? formatNumberRFCurrency(row[columnDef.name]) : "-"
};
case "NFACT_SUM":
return {
cellStyle: cellStyle(),
data:
row[columnDef.name] || row[columnDef.name] === 0 ? (
row[columnDef.name] > 0 ? (
<Link component="button" onClick={() => showCostNotesFact({ faceAccRn: row["NFACEACC"], artclRn: row["NFPDARTCL"] })}>
{formatNumberRFCurrency(row[columnDef.name])}
</Link>
) : (
formatNumberRFCurrency(row[columnDef.name])
)
) : (
"-"
)
};
default:
return { cellStyle: cellStyle(), data: row[columnDef.name] };
}
};
//----------------
//Интерфейс модуля
//----------------
export {
STYLES as STAGE_DETAIL_STYLES,
stageDetailInfoHeadCellRender,
stageDetailInfoDataCellRender,
stageDetailArtsHeadCellRender,
stageDetailArtsDataCellRender
};

View File

@ -1,95 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список этапов проекта
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Typography } from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { P8PDataGrid } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useStagesDataGrid } from "./stages_hooks"; //Хуки списка этапов проекта
import { STAGES_STYLES, projectStageDataCellRender } from "./stages_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
import { StageDetail } from "./stage_detail"; //Компонент "Информация об этапе проекта"
//-----------
//Тело модуля
//-----------
//Список этапов проекта
const Stages = ({ projectRn, projectCode }) => {
//Собственное состояние
const [stages, setStages] = useState({ pageNumber: 1, orders: [] });
//Состояние таблицы этапов
const [stagesDataGrid] = useStagesDataGrid({ ...stages, projectRn });
//Состояние информации о этапе
const [stageInfo, setStageInfo] = useState({ showInfo: false, stage: null, sFaceAcc: null });
//Подключение к контексту приложения
const { pOnlineShowUnit } = useContext(ApplicationСtx);
//Отображение записи этапа проекта в штатном разделе
const showProjectStage = (prn, rn) => {
pOnlineShowUnit({
unitCode: "Projects",
inputParameters: [
{ name: "in_RN", value: prn },
{ name: "in_STAGE_RN", value: rn }
],
modal: false
});
};
//Отображение деталей этапа
const showStageDetails = stage => setStageInfo(pv => ({ ...pv, showInfo: true, stage: stage["NRN"], sFaceAcc: stage["SFACEACC"] }));
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setStages(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setStages(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));
//Генерация содержимого
return stagesDataGrid.init ? (
<>
<div style={STAGES_STYLES.CONTAINER}>
<Typography variant={"subtitle2"} sx={STAGES_STYLES.TITLE}>
{`Этапы проекта "${projectCode}"`}
</Typography>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...stagesDataGrid}
containerComponentProps={{ sx: STAGES_STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
onPagesCountChanged={handlePagesCountChanged}
onOrderChanged={handleOrderChanged}
dataCellRender={prms => projectStageDataCellRender({ ...prms, showProjectStage, showStageDetails })}
/>
</div>
<StageDetail
stageRn={stageInfo.stage}
stageName={stageInfo.sFaceAcc}
isOpen={stageInfo.showInfo}
onClose={() => setStageInfo(pv => ({ ...pv, showInfo: false, stage: null, sFaceAcc: null }))}
/>
</>
) : null;
};
//Контроль свойств компонента - Список этапов проекта
Stages.propTypes = {
projectRn: PropTypes.number.isRequired,
projectCode: PropTypes.string.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { Stages };

View File

@ -1,78 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список этапов проекта: пользовательские хуки для взаимодействия с сервером
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
import config from "../../../app.config"; //Настройки приложения
//---------
//Константы
//---------
//Размер страницы данных
const DATA_GRID_PAGE_SIZE = config.SYSTEM.PAGE_SIZE;
//-----------
//Тело модуля
//-----------
//Этапы проекта
const useStagesDataGrid = ({ projectRn, pageNumber, orders }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({ init: false, morePages: true });
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGES_DG",
args: {
NPROJECT: projectRn,
CORDERS: { VALUE: object2Base64XML(orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
NINCLUDE_DEF: pageNumber == 1 ? 1 : 0
},
respArg: "COUT",
loader: true,
attributeValueProcessor: (name, val) => (["DBEGPLAN", "DENDPLAN"].includes(name) ? formatDateRF(val) : val)
});
setData(pv => ({
...pv,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [],
rows: pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])],
morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE,
init: true
}));
} finally {
setLoading(false);
}
};
if (projectRn) loadData();
}, [projectRn, orders, pageNumber, executeStored, SERV_DATA_TYPE_CLOB]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//----------------
//Интерфейс модуля
//----------------
export { useStagesDataGrid };

View File

@ -1,86 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список этапов проекта: дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Icon, Stack, Link } from "@mui/material"; //Интерфейсные элементы
import { formatNumberRFCurrency } from "../../core/utils"; //Спомогательные функции
import { COMMON_STYLES, formatCostStatusValue, formatCostReadyValue } from "./layouts"; //Общие стили и разметка панели
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { textAlign: "center", paddingTop: "10px", backgroundColor: "lightcyan" },
TITLE: { fontSize: "13pt", paddingBottom: "10px" },
DATA_GRID_CONTAINER: { backgroundColor: "lightcyan" }
};
//-----------
//Тело модуля
//-----------
//Формирование значения для колонки "Состояние" этапа
const formatStageStatusValue = ({ value, addText = false, justifyContent = "center" }) => {
const [text, icon] =
value == 0
? ["Зарегистрирован", "app_registration"]
: value == 1
? ["Открыт", "lock_open"]
: value == 2
? ["Закрыт", "lock_outline"]
: value == 3
? ["Согласован", "thumb_up_alt"]
: value == 4
? ["Исполнение прекращено", "block"]
: ["Остановлен", "do_not_disturb_on"];
return (
<Stack direction="row" gap={0.5} alignItems={"center"} justifyContent={justifyContent || "center"}>
<Icon title={text} sx={COMMON_STYLES.STATE(value)}>
{icon}
</Icon>
{addText == true ? text : null}
</Stack>
);
};
//Форматирование ячеек таблицы "Этапы проекта"
const projectStageDataCellRender = ({ row, columnDef, showProjectStage, showStageDetails }) => {
//Формирование представлений
switch (columnDef.name) {
case "NCOST_STATUS":
return {
cellProps: { align: "center" },
data: formatCostStatusValue({ value: row[columnDef.name], onClick: () => showStageDetails(row) })
};
case "NCOST_READY":
return { cellProps: { align: "center" }, data: formatCostReadyValue(row[columnDef.name]) };
case "NSTATE":
return { cellProps: { align: "center" }, data: formatStageStatusValue({ value: row[columnDef.name] }) };
case "SFACEACC":
return {
data: (
<Link component="button" align="left" underline="hover" onClick={() => showProjectStage(row["NPRN"], row["NRN"])}>
{row[columnDef.name]}
</Link>
)
};
case "NCOST_SUM":
return { data: formatNumberRFCurrency(row[columnDef.name]) };
default:
return { data: row[columnDef.name] };
}
};
//----------------
//Интерфейс модуля
//----------------
export { STYLES as STAGES_STYLES, projectStageDataCellRender, formatStageStatusValue };

View File

@ -57,12 +57,11 @@ const LabFactRptDtl = ({ periodId, title, onHide }) => {
}); });
setFactRptDtl(pv => ({ setFactRptDtl(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize morePages: (data.XROWS || []).length >= configSystemPageSize
})); }));
} }
}, [ }, [

View File

@ -56,12 +56,11 @@ const LabPlanFOTDtl = ({ periodId, title, onHide }) => {
}); });
setPlanFOTDtl(pv => ({ setPlanFOTDtl(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize morePages: (data.XROWS || []).length >= configSystemPageSize
})); }));
} }
}, [ }, [

View File

@ -61,12 +61,11 @@ const LabPlanJobsDtl = ({ periodId, title, onHide, onProjectClick }) => {
}); });
setPlanJobsDtl(pv => ({ setPlanJobsDtl(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize morePages: (data.XROWS || []).length >= configSystemPageSize
})); }));
} }
}, [ }, [

View File

@ -265,7 +265,8 @@ const PrjJobs = () => {
selectedProjectJobsLoaded: false, selectedProjectJobsLoaded: false,
selectedProject: null, selectedProject: null,
selectedProjectDocRn: null, selectedProjectDocRn: null,
gantt: {}, selectedProjectGanttDef: {},
selectedProjectTasks: [],
showInitDialog: false showInitDialog: false
}); });
@ -307,9 +308,8 @@ const PrjJobs = () => {
setState(pv => ({ setState(pv => ({
...pv, ...pv,
selectedProjectJobsLoaded: true, selectedProjectJobsLoaded: true,
gantt: { selectedProjectGanttDef: tasksOnly === true ? { ...pv.selectedProjectGanttDef } : data.XGANTT_DEF ? { ...data.XGANTT_DEF } : {},
...(tasksOnly === true ? { ...pv.gantt, tasks: [...data.XGANTT.tasks] } : data.XGANTT ? { ...data.XGANTT } : {}) selectedProjectTasks: [...data.XGANTT_TASKS]
}
})); }));
}, },
[executeStored, state.ident, state.selectedProject] [executeStored, state.ident, state.selectedProject]
@ -394,7 +394,8 @@ const PrjJobs = () => {
selectedProject: project, selectedProject: project,
selectedProjectDocRn: projectDocRn, selectedProjectDocRn: projectDocRn,
selectedProjectJobsLoaded: false, selectedProjectJobsLoaded: false,
gantt: {}, selectedProjectTasks: [],
selectedProjectGanttDef: {},
showProjectsList: false showProjectsList: false
})); }));
}; };
@ -406,7 +407,8 @@ const PrjJobs = () => {
selectedProjectJobsLoaded: false, selectedProjectJobsLoaded: false,
selectedProject: null, selectedProject: null,
selectedProjectDocRn: null, selectedProjectDocRn: null,
gantt: {}, selectedProjectTasks: [],
selectedProjectGanttDef: {},
showProjectsList: false showProjectsList: false
})); }));
@ -513,10 +515,11 @@ const PrjJobs = () => {
{state.selectedProjectJobsLoaded ? ( {state.selectedProjectJobsLoaded ? (
<P8PGantt <P8PGantt
{...P8P_GANTT_CONFIG_PROPS} {...P8P_GANTT_CONFIG_PROPS}
{...state.gantt} {...state.selectedProjectGanttDef}
containerStyle={STYLES.GANTT_CONTAINER} containerStyle={STYLES.GANTT_CONTAINER}
titleStyle={STYLES.GANTT_TITLE} titleStyle={STYLES.GANTT_TITLE}
onTitleClick={handleTitleClick} onTitleClick={handleTitleClick}
tasks={state.selectedProjectTasks}
onTaskDatesChange={handleTaskDatesChange} onTaskDatesChange={handleTaskDatesChange}
taskAttributeRenderer={taskAttributeRenderer} taskAttributeRenderer={taskAttributeRenderer}
/> />

View File

@ -79,12 +79,11 @@ const ResMon = ({ ident, onPlanJobsDtlProjectClick }) => {
}); });
setPeriods(pv => ({ setPeriods(pv => ({
...pv, ...pv,
...data.XDATA_GRID, columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef, rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true, dataLoaded: true,
reload: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= configSystemPageSize morePages: (data.XROWS || []).length >= configSystemPageSize
})); }));
} }
}, [ident, peridos.reload, peridos.orders, peridos.dataLoaded, peridos.pageNumber, executeStored, configSystemPageSize, SERV_DATA_TYPE_CLOB]); }, [ident, peridos.reload, peridos.orders, peridos.dataLoaded, peridos.pageNumber, executeStored, configSystemPageSize, SERV_DATA_TYPE_CLOB]);

View File

@ -9,23 +9,7 @@
import React, { useState } from "react"; //Классы React import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { import { Box, IconButton, Icon, Dialog, DialogTitle, DialogContent, Typography, List, ListItem } from "@mui/material"; //Интерфейсные элементы
Box,
IconButton,
Icon,
Dialog,
DialogTitle,
DialogContent,
Button,
Typography,
List,
ListItem,
Select,
FormControl,
InputLabel,
MenuItem,
DialogActions
} from "@mui/material"; //Интерфейсные элементы
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../../components/p8p_data_grid"; //Таблица данных import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { SectionTabPanel } from "./section_tab_panel"; //Компонент вкладки раздела import { SectionTabPanel } from "./section_tab_panel"; //Компонент вкладки раздела
@ -83,8 +67,7 @@ const STYLES = {
}, },
HELP_LIST_ITEM_DESC: { HELP_LIST_ITEM_DESC: {
fontSize: "inherit" fontSize: "inherit"
}, }
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" }
}; };
//------------------------------------ //------------------------------------
@ -153,89 +136,6 @@ HelpDialog.propTypes = {
handleOpenHelpChange: PropTypes.func.isRequired handleOpenHelpChange: PropTypes.func.isRequired
}; };
//Диалог сортировки
const SortDialog = ({ init, handleOpenSortChange, onOrderChange }) => {
//Собственное состояние сортировки
const [order, setOrder] = useState({ row: init.rowOrder ? init.rowOrder : 0, column: init.columnOrder ? init.columnOrder : 0 });
//Изменеие сортировки
const handleOrderChange = e => setOrder(pv => ({ ...pv, [e.target.name]: e.target.value }));
//Нажатие на кнопку Ok
const handleOk = () => {
onOrderChange({ rowOrder: order.row, columnOrder: order.column });
handleOpenSortChange();
};
//Кнопка "Очистить", значения по умолчанию
const handleClear = () => {
setOrder({ row: 0, column: 0 });
};
//Кнопка "Отмена"
const handleCancel = () => {
handleOpenSortChange();
};
//Генерация содержимого
return (
<Dialog open onClose={handleOpenSortChange}>
<DialogTitle>
<Box display="flex" alignItems="center">
<Box flexGrow={1} textAlign="center">
Сортировка
</Box>
<Box>
<IconButton aria-label="close" onClick={handleCancel}>
<Icon>close</Icon>
</IconButton>
</Box>
</Box>
</DialogTitle>
<DialogContent>
<Box component="section" p={1}>
<FormControl variant="standard" fullWidth>
<InputLabel id="row-label">Строки</InputLabel>
<Select labelId="row-label" id="row" name="row" value={order.row} label="Строки" onChange={handleOrderChange}>
<MenuItem value={0}>Номер</MenuItem>
<MenuItem value={1}>Код</MenuItem>
<MenuItem value={2}>Мнемокод</MenuItem>
</Select>
</FormControl>
</Box>
<Box component="section" p={1}>
<FormControl variant="standard" fullWidth>
<InputLabel id="column-label">Графы</InputLabel>
<Select labelId="column-label" id="column" name="column" value={order.column} label="Графы" onChange={handleOrderChange}>
<MenuItem value={0}>Номер</MenuItem>
<MenuItem value={1}>Код</MenuItem>
<MenuItem value={2}>Мнемокод</MenuItem>
</Select>
</FormControl>
</Box>
</DialogContent>
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button variant="text" onClick={handleOk}>
ОК
</Button>
<Button variant="text" onClick={handleClear}>
Очистить
</Button>
<Button variant="text" onClick={handleCancel}>
Отмена
</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог сортировки
SortDialog.propTypes = {
init: PropTypes.object.isRequired,
handleOpenSortChange: PropTypes.func.isRequired,
onOrderChange: PropTypes.func.isRequired
};
//----------- //-----------
//Тело модуля //Тело модуля
//----------- //-----------
@ -245,8 +145,6 @@ const SectionTab = ({
section, section,
tabValue, tabValue,
index, index,
order,
onOrderChange,
containerProps, containerProps,
handleMarkAdd, handleMarkAdd,
handleReload, handleReload,
@ -263,14 +161,6 @@ const SectionTab = ({
setOpenHelp(!openHelp); setOpenHelp(!openHelp);
}; };
//Состояние - диалог сортировки
const [openSort, setOpenSort] = useState(false);
//Изменение состояния диалога сортировки
const handleOpenSortChange = () => {
setOpenSort(!openSort);
};
//Генерация содержимого //Генерация содержимого
return ( return (
<> <>
@ -285,9 +175,6 @@ const SectionTab = ({
</IconButton> </IconButton>
</Box> </Box>
<Box> <Box>
<IconButton onClick={() => handleOpenSortChange()}>
<Icon>sort</Icon>
</IconButton>
<IconButton onClick={() => handleOpenHelpChange()}> <IconButton onClick={() => handleOpenHelpChange()}>
<Icon>help</Icon> <Icon>help</Icon>
</IconButton> </IconButton>
@ -330,7 +217,6 @@ const SectionTab = ({
</Box> </Box>
) : null} ) : null}
</SectionTabPanel> </SectionTabPanel>
{openSort ? <SortDialog init={order} handleOpenSortChange={handleOpenSortChange} onOrderChange={onOrderChange} /> : null}
{openHelp ? <HelpDialog handleOpenHelpChange={handleOpenHelpChange} /> : null} {openHelp ? <HelpDialog handleOpenHelpChange={handleOpenHelpChange} /> : null}
</> </>
); );
@ -341,8 +227,6 @@ SectionTab.propTypes = {
section: PropTypes.object.isRequired, section: PropTypes.object.isRequired,
tabValue: PropTypes.number, tabValue: PropTypes.number,
index: PropTypes.number, index: PropTypes.number,
order: PropTypes.object.isRequired,
onOrderChange: PropTypes.func.isRequired,
containerProps: PropTypes.object, containerProps: PropTypes.object,
handleMarkAdd: PropTypes.func, handleMarkAdd: PropTypes.func,
handleReload: PropTypes.func, handleReload: PropTypes.func,

View File

@ -39,7 +39,7 @@ const useWindowResize = () => {
}; };
//Хук для настройки регламентированного отчета //Хук для настройки регламентированного отчета
const useConf = (currentTab, handleSectionChange, order) => { const useConf = (currentTab, handleSectionChange) => {
//Собственное состояние - таблица данных //Собственное состояние - таблица данных
const dataGrid = { const dataGrid = {
rn: 0, rn: 0,
@ -58,7 +58,6 @@ const useConf = (currentTab, handleSectionChange, order) => {
const [rrpConf, setRrpConf] = useState({ const [rrpConf, setRrpConf] = useState({
docLoaded: false, docLoaded: false,
sections: [], sections: [],
orderChanged: false,
reload: true reload: true
}); });
@ -77,116 +76,104 @@ const useConf = (currentTab, handleSectionChange, order) => {
}, []); }, []);
//Загрузка данных разделов регламентированного отчёта //Загрузка данных разделов регламентированного отчёта
const loadData = useCallback( const loadData = useCallback(async () => {
async () => { if (rrpConf.reload) {
if (rrpConf.reload) { //Переменная номера раздела с фокусом
//Переменная номера раздела с фокусом let tabFocus = currentTab ? currentTab : 0;
let tabFocus = currentTab ? currentTab : 0; const data = await executeStored({
const data = await executeStored({ stored: "PKG_P8PANELS_RRPCONFED.RRPCONF_GET_SECTIONS",
stored: "PKG_P8PANELS_RRPCONFED.RRPCONF_GET_SECTIONS", args: {
args: { NRN_RRPCONF: Number(getNavigationSearch().NRN)
NRN_RRPCONF: Number(getNavigationSearch().NRN), },
NROW_ORDER: Number(order.rowOrder), respArg: "COUT"
NCOL_ORDER: Number(order.columnOrder) });
}, //Флаг первой загрузки данных
respArg: "COUT" let firstLoad = dataGrids.length == 0 ? true : false;
//Копирование массива уже загруженных разделов
let cloneDGs = dataGrids.slice();
//Массив из нескольких разделов и из одного
const sections = data.SECTIONS ? (data.SECTIONS.length ? data.SECTIONS : [data.SECTIONS]) : [];
//Заполнение очередного раздела по шаблону
sections.map(s => {
let dg = {};
Object.assign(dg, dataGrid, {
rn: s.NRN,
code: s.SCODE,
name: s.SNAME,
delete_allow: s.NDELETE_ALLOW,
dataLoaded: true,
columnsDef: [...(s.XDATA.XCOLUMNS_DEF || [])],
groups: [...(s.XDATA.XGROUPS || [])],
rows: [...(s.XDATA.XROWS || [])],
fixedHeader: s.XDATA.XDATA_GRID.fixedHeader,
fixedColumns: s.XDATA.XDATA_GRID.fixedColumns,
reload: false
}); });
//Флаг первой загрузки данных //Если раздел имеет составы показателей
let firstLoad = dataGrids.length == 0 ? true : false; if (s.MARK_CNS.MARK_CN) {
//Копирование массива уже загруженных разделов //Обходим строки раздела
let cloneDGs = dataGrids.slice(); dg.rows.map(row => {
//Массив из нескольких разделов и из одного //Цикл по ключам строки
const sections = data.SECTIONS ? (data.SECTIONS.length ? data.SECTIONS : [data.SECTIONS]) : []; for (let key in row) {
//Заполнение очередного раздела по шаблону //Если это ключ для группы составов показателей
sections if (key.match(/MARK_CNS_.*/)) {
.sort((a, b) => a.SCODE - b.SCODE) //Считываем рег. номер показателя
.map(s => { let markRn = key.substring(9);
let dg = {}; //Переносим из раздела
Object.assign(dg, dataGrid, { row[key] = Array.isArray(s.MARK_CNS.MARK_CN)
...s.XDATA.XDATA_GRID, ? [...s.MARK_CNS.MARK_CN].filter(el => el.NPRN === row[`NMARK_RN_${markRn}`])
rn: s.NRN, : s.MARK_CNS.MARK_CN.NPRN === row[`NMARK_RN_${markRn}`]
code: s.SCODE, ? [s.MARK_CNS.MARK_CN]
name: s.SNAME, : null;
delete_allow: s.NDELETE_ALLOW,
dataLoaded: true,
columnsDef: [...(s.XDATA.XDATA_GRID.columnsDef || [])],
groups: [...(s.XDATA.XDATA_GRID.groups || [])],
rows: [...(s.XDATA.XDATA_GRID.rows || [])],
reload: false
});
//Если раздел имеет составы показателей
if (s.MARK_CNS.MARK_CN) {
//Обходим строки раздела
dg.rows.map(row => {
//Цикл по ключам строки
for (let key in row) {
//Если это ключ для группы составов показателей
if (key.match(/MARK_CNS_.*/)) {
//Считываем рег. номер показателя
let markRn = key.substring(9);
//Переносим из раздела
row[key] = Array.isArray(s.MARK_CNS.MARK_CN)
? [...s.MARK_CNS.MARK_CN].filter(el => el.NPRN === row[`NMARK_RN_${markRn}`])
: s.MARK_CNS.MARK_CN.NPRN === row[`NMARK_RN_${markRn}`]
? [s.MARK_CNS.MARK_CN]
: null;
}
}
});
}
//Ищем загружен ли уже раздел с таким же ид.
const dgItem = dataGrids.find(x => x.rn === dg.rn);
//Его индекс, если нет соответствия, то -1
let index = dataGrids.indexOf(dgItem);
//Если было соответствие
if (dgItem) {
//Если в нём не найдено изменений
if (JSON.stringify(dgItem, null, 4) === JSON.stringify(dg, null, 4)) {
//То из копированного массива его удаляем
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dgItem.rn)), 1);
} else {
//Иначе обновляем раздел в массиве
dataGrids[index] = dg;
//Удаляем из копированного массива
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dg.rn)), 1);
//Устанавливаем фокус на обновлённый раздел, если был добавлен
tabFocus = rrpConf.orderChanged ? 0 : index;
} }
} else {
//Если раздел новый, то добавляем его в массив данных
dataGrids.push(dg);
//И устанавливаем на него фокус, если флаг первой загрузки = false
tabFocus = !firstLoad ? dataGrids.length - 1 : 0;
} }
}); });
//Обходим разделы, что остались в копированном массиве (на удаление) }
cloneDGs.map(s => { //Ищем загружен ли уже раздел с таким же ид.
let curIndex = dataGrids.indexOf(dataGrids.find(x => x.rn === s.rn)); const dgItem = dataGrids.find(x => x.rn === dg.rn);
//Устаревший раздел удаляем из массива данных //Его индекс, если нет соответствия, то -1
dataGrids.splice(curIndex, 1); let index = dataGrids.indexOf(dgItem);
//Фокус на предшествующий раздел //Если было соответствие
if (curIndex > 0) tabFocus = curIndex - 1; if (dgItem) {
//Иначе фокус на следующий, если был удалён первый раздел //Если в нём не найдено изменений
else tabFocus = curIndex; if (JSON.stringify(dgItem, null, 4) === JSON.stringify(dg, null, 4)) {
}); //То из копированного массива его удаляем
setRrpConf(pv => ({ cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dgItem.rn)), 1);
...pv, } else {
docLoaded: true, //Иначе обновляем раздел в массиве
orderChanged: false, dataGrids[index] = dg;
reload: false, //Удаляем из копированного массива
sections: dataGrids cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dg.rn)), 1);
})); //Устанавливаем фокус на обновлённый раздел
handleSectionChange(tabFocus); tabFocus = index;
} }
}, } else {
//Если раздел новый, то добавляем его в массив данных
dataGrids.push(dg);
//И устанавливаем на него фокус, если флаг первой загрузки = false
tabFocus = !firstLoad ? dataGrids.length - 1 : 0;
}
});
//Обходим разделы, что остались в копированном массиве (на удаление)
cloneDGs.map(s => {
let curIndex = dataGrids.indexOf(dataGrids.find(x => x.rn === s.rn));
//Устаревший раздел удаляем из массива данных
dataGrids.splice(curIndex, 1);
//Фокус на предшествующий раздел
if (curIndex > 0) tabFocus = curIndex - 1;
//Иначе фокус на следующий, если был удалён первый раздел
else tabFocus = curIndex;
});
setRrpConf(pv => ({
...pv,
docLoaded: true,
reload: false,
sections: dataGrids
}));
handleSectionChange(tabFocus);
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[rrpConf.reload, rrpConf.docLoaded, dataGrid.reload, dataGrid.docLoaded, executeStored] }, [rrpConf.reload, rrpConf.docLoaded, dataGrid.reload, dataGrid.docLoaded, executeStored]);
);
//При изменении сортировок
useEffect(() => {
setRrpConf(pv => ({ ...pv, orderChanged: true, reload: true }));
}, [order]);
//При необходимости обновить данные таблицы //При необходимости обновить данные таблицы
useEffect(() => { useEffect(() => {

View File

@ -44,14 +44,8 @@ const RrpConfEditor = () => {
//Состояние вкладки //Состояние вкладки
const [tabValue, handleSectionChange] = useTab(""); const [tabValue, handleSectionChange] = useTab("");
//Состояние сортировки строк и граф
const [order, setOrder] = useState({ rowOrder: 0, columnOrder: 0 });
//Изменение состояния сортировки строк и граф
const handleOrder = newOrder => setOrder(newOrder);
//Состояние настройки //Состояние настройки
const [rrpConf, handleReload] = useConf(tabValue, handleSectionChange, order); const [rrpConf, handleReload] = useConf(tabValue, handleSectionChange);
//Функции открытия разделов //Функции открытия разделов
const [handleMarkOpen, handleMarkCnOpen, handleMarkCnInsert] = useRecOpen(handleReload); const [handleMarkOpen, handleMarkCnOpen, handleMarkCnInsert] = useRecOpen(handleReload);
@ -142,8 +136,6 @@ const RrpConfEditor = () => {
section={s} section={s}
tabValue={tabValue} tabValue={tabValue}
index={i} index={i}
order={order}
onOrderChange={handleOrder}
containerProps={{ height, pxOuterMenuH, pxPanelHeaderH, pxTabsH }} containerProps={{ height, pxOuterMenuH, pxPanelHeaderH, pxTabsH }}
handleReload={handleReload} handleReload={handleReload}
handleMarkOpen={handleMarkOpen} handleMarkOpen={handleMarkOpen}

View File

@ -33,7 +33,7 @@ const STYLES = {
//Пример: Графики "P8PChart" //Пример: Графики "P8PChart"
const Chart = ({ title }) => { const Chart = ({ title }) => {
//Собственное состояние - график //Собственное состояние - график
const [chart, setChart] = useState({ loaded: false }); const [chart, setChart] = useState({ loaded: false, labels: [], datasets: [] });
//Подключение к контексту взаимодействия с сервером //Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx); const { executeStored } = useContext(BackEndСtx);

View File

@ -1,301 +0,0 @@
/*
Парус 8 - Панели мониторинга - Примеры для разработчиков
Пример: Циклограмма "P8PCyclogram"
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Typography,
Grid,
Button,
Box,
DialogContent,
List,
ListItem,
ListItemText,
Divider,
TextField,
DialogActions,
Stack,
Icon
} from "@mui/material"; //Интерфейсные элементы
import { formatDateJSONDateOnly, formatDateRF } from "../../core/utils"; //Вспомогательные функции
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { P8PCyclogram } from "../../components/p8p_cyclogram"; //Циклограмма
import { P8P_CYCLOGRAM_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
//---------
//Константы
//---------
//Отступ контейнера страницы от заголовка
const CONTAINER_PADDING_TOP = "20px";
//Высота заголовка страницы
const TITLE_HEIGHT = "47px";
//Высота строк
const LINE_HEIGHT = 30;
//Стили
const STYLES = {
CONTAINER: { textAlign: "center", paddingTop: CONTAINER_PADDING_TOP },
TITLE: { paddingBottom: "15px", height: TITLE_HEIGHT },
CYCLOGRAM_CONTAINER: {
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_HEIGHT} - ${CONTAINER_PADDING_TOP})`,
width: "100vw",
paddingTop: "5px"
},
TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" },
GROUP_HEADER: height => ({
border: "1px solid",
backgroundColor: "#ecf8fb",
height: height,
borderRadius: "10px",
display: "flex",
alignItems: "center",
justifyContent: "space-around"
})
};
//---------------------------------------------
//Вспомогательные функции форматирования данных
//---------------------------------------------
//Диалог открытия задачи
const CustomTaskDialog = ({ task, ident, handleReload, close }) => {
//Собственное состояние
const [taskDates, setTaskDates] = useState({ start: task.ddate_start, end: task.ddate_end });
//Тип проекта
const textType = task.type === 0 ? "Задачи проекта" : task.type === 1 ? "Этап проекта" : "Работа проекта";
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Изменение дат задачи
const changeDates = useCallback(async () => {
//Изменяем даты задачи
await executeStored({
stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_TASK_MODIFY",
args: {
NIDENT: ident,
NRN: task.rn,
SDATE_FROM: formatDateRF(taskDates.start),
SDATE_TO: formatDateRF(taskDates.end)
}
});
handleReload();
close();
}, [close, executeStored, handleReload, ident, task.rn, taskDates.end, taskDates.start]);
//При нажатии OK
const handleOk = () => {
//Изменяем даты задачи
changeDates();
};
return (
<>
<DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
<List sx={STYLES.TASK_EDITOR_LIST}>
<ListItem alignItems="flex-start">
<ListItemText primary={"Наименование"} secondary={task.fullName} />
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
secondaryTypographyProps={{ component: "span" }}
primary={"Начало"}
secondary={
<TextField
error={!taskDates.start}
disabled={task.type !== 2}
name="start"
fullWidth
required
InputLabelProps={{ shrink: true }}
type={"date"}
value={taskDates.start}
onChange={e => setTaskDates(pv => ({ ...pv, start: e.target.value }))}
variant="standard"
size="small"
margin="normal"
></TextField>
}
/>
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
secondaryTypographyProps={{ component: "span" }}
primary={"Окончание"}
secondary={
<TextField
error={!taskDates.end}
disabled={task.type !== 2}
name="end"
fullWidth
required
InputLabelProps={{ shrink: true }}
type={"date"}
value={taskDates.end}
onChange={e => setTaskDates(pv => ({ ...pv, end: e.target.value }))}
variant="standard"
size="small"
margin="normal"
></TextField>
}
/>
</ListItem>
<Divider component="li" />
<ListItem alignItems="flex-start">
<ListItemText
primary={"Тип"}
secondaryTypographyProps={{ component: "span" }}
secondary={
<Stack direction="row" gap={0.5}>
<Icon title={textType}>{task.type === 0 ? "description" : task.type === 1 ? "check" : "work_outline"}</Icon>
{textType}
</Stack>
}
/>
</ListItem>
</List>
</DialogContent>
<DialogActions>
<Button onClick={handleOk}>ОК</Button>
<Button onClick={close}>Отмена</Button>
</DialogActions>
</>
);
};
//Контроль свойств - Диалог открытия задачи
CustomTaskDialog.propTypes = {
task: PropTypes.object.isRequired,
ident: PropTypes.number.isRequired,
handleReload: PropTypes.func.isRequired,
close: PropTypes.func.isRequired
};
//Заголовок группы
const CustomGroupHeader = ({ group }) => {
return (
<Box sx={STYLES.GROUP_HEADER(group.height)}>
<Typography variant="body2">{group.name}</Typography>
</Box>
);
};
//Контроль свойств - Заголовок группы
CustomGroupHeader.propTypes = {
group: PropTypes.object.isRequired
};
//Отображение задачи
const taskRenderer = ({ task }) => {
//Если это задачи проекта
if (task.type === 0) {
return {
taskStyle: { border: "3px solid #ebe058" }
};
}
};
//-----------
//Тело модуля
//-----------
//Пример: Циклограмма "P8PCyclogram"
const Cyclogram = ({ title }) => {
//Собственное состояние
const [state, setState] = useState({
init: false,
dataLoaded: false,
reload: true,
ident: null
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости перезагрузки
const handleReload = () => {
setState(pv => ({ ...pv, reload: true }));
};
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных циклограммы с сервера
const loadData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM",
args: { NIDENT: state.ident },
attributeValueProcessor: (name, val) => (["ddate_start", "ddate_end"].includes(name) ? formatDateJSONDateOnly(val) : val),
respArg: "COUT"
});
setState(pv => ({ ...pv, dataLoaded: true, ...data.XCYCLOGRAM, reload: false }));
};
//Если указан идентификатор и требуется перезагрузить
if (state.ident && state.reload) loadData();
}, [state.ident, state.reload, executeStored]);
//При подключении компонента к странице
useEffect(() => {
//Инициализация данных циклограммы
const initData = async () => {
const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.CYCLOGRAM_INIT", args: { NIDENT: state.ident } });
setState(pv => ({ ...pv, init: true, ident: data.NIDENT, reload: true }));
};
//Если требуется проинициализировать
if (!state.init) {
initData();
}
}, [executeStored, state.ident, state.init]);
return (
<Box>
<div style={STYLES.CONTAINER}>
<Typography sx={STYLES.TITLE} variant={"h6"}>
{title}
</Typography>
<Grid container direction="column" alignItems="center">
<Grid item xs={12}>
{state.dataLoaded ? (
<P8PCyclogram
{...P8P_CYCLOGRAM_CONFIG_PROPS}
{...state}
containerStyle={STYLES.CYCLOGRAM_CONTAINER}
lineHeight={LINE_HEIGHT}
taskDialogRenderer={prms => (
<CustomTaskDialog task={prms.task} ident={state.ident} handleReload={handleReload} close={prms.close} />
)}
taskRenderer={prms => taskRenderer(prms)}
groupHeaderRenderer={prms => <CustomGroupHeader group={prms.group} />}
/>
) : null}
</Grid>
</Grid>
</div>
</Box>
);
};
//Контроль свойств - Пример: Циклограмма "P8PCyclogram"
Cyclogram.propTypes = {
title: PropTypes.string.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { Cyclogram };

View File

@ -15,7 +15,6 @@ import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
//--------- //---------
//Константы //Константы
@ -28,7 +27,7 @@ const DATA_GRID_PAGE_SIZE = 5;
const STYLES = { const STYLES = {
CONTAINER: { textAlign: "center", paddingTop: "20px" }, CONTAINER: { textAlign: "center", paddingTop: "20px" },
TITLE: { paddingBottom: "15px" }, TITLE: { paddingBottom: "15px" },
DATA_GRID_CONTAINER: { maxWidth: 700, maxHeight: 500, minHeight: 500, ...APP_STYLES.SCROLL } DATA_GRID_CONTAINER: { maxWidth: 700, maxHeight: 500, minHeight: 500 }
}; };
//--------------------------------------------- //---------------------------------------------
@ -87,14 +86,18 @@ export const groupCellRender = () => ({ cellStyle: { padding: "2px" } });
//Пример: Таблица данных "P8PDataGrid" //Пример: Таблица данных "P8PDataGrid"
const DataGrid = ({ title }) => { const DataGrid = ({ title }) => {
//Собственное состояние - таблица данных //Собственное состояние - таблица данных
const [dataGrid, setDataGrid] = useState({ const [dataGrid, setdataGrid] = useState({
dataLoaded: false, dataLoaded: false,
columnsDef: [],
filters: null, filters: null,
orders: null, orders: null,
groups: [],
rows: [],
reload: true,
pageNumber: 1, pageNumber: 1,
morePages: true, morePages: true,
expandable: true, fixedHeader: false,
reloading: true fixedColumns: 0
}); });
//Подключение к контексту взаимодействия с сервером //Подключение к контексту взаимодействия с сервером
@ -105,7 +108,7 @@ const DataGrid = ({ title }) => {
//Загрузка данных таблицы с сервера //Загрузка данных таблицы с сервера
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
if (dataGrid.reloading) { if (dataGrid.reload) {
const data = await executeStored({ const data = await executeStored({
stored: "PKG_P8PANELS_SAMPLES.DATA_GRID", stored: "PKG_P8PANELS_SAMPLES.DATA_GRID",
args: { args: {
@ -117,31 +120,32 @@ const DataGrid = ({ title }) => {
}, },
respArg: "COUT" respArg: "COUT"
}); });
setDataGrid(pv => ({ setdataGrid(pv => ({
...pv, ...pv,
...data.XDATA_GRID, fixedHeader: data.XDATA_GRID.fixedHeader,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [], fixedColumns: data.XDATA_GRID.fixedColumns,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])], columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef,
groups: data.XDATA_GRID.groups rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])],
groups: data.XGROUPS
? pv.pageNumber == 1 ? pv.pageNumber == 1
? [...data.XDATA_GRID.groups] ? [...data.XGROUPS]
: [...(pv.groups || []), ...data.XDATA_GRID.groups.filter(g => !pv.groups.find(pg => pg.name == g.name))] : [...pv.groups, ...data.XGROUPS.filter(g => !pv.groups.find(pg => pg.name == g.name))]
: [...(pv.groups || [])], : [...pv.groups],
dataLoaded: true, dataLoaded: true,
reloading: false, reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE
})); }));
} }
}, [dataGrid.reloading, dataGrid.filters, dataGrid.orders, dataGrid.dataLoaded, dataGrid.pageNumber, executeStored, SERV_DATA_TYPE_CLOB]); }, [dataGrid.reload, dataGrid.filters, dataGrid.orders, dataGrid.dataLoaded, dataGrid.pageNumber, executeStored, SERV_DATA_TYPE_CLOB]);
//При изменении состояния фильтра //При изменении состояния фильтра
const handleFilterChanged = ({ filters }) => setDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1, reloading: true })); const handleFilterChanged = ({ filters }) => setdataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1, reload: true }));
//При изменении состояния сортировки //При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reloading: true })); const handleOrderChanged = ({ orders }) => setdataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
//При изменении количества отображаемых страниц //При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reloading: true })); const handlePagesCountChanged = () => setdataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
//При нажатии на копку контрагента //При нажатии на копку контрагента
const handleAgnButtonClicked = agnCode => pOnlineShowDocument({ unitCode: "AGNLIST", document: agnCode, inRnParameter: "in_AGNABBR" }); const handleAgnButtonClicked = agnCode => pOnlineShowDocument({ unitCode: "AGNLIST", document: agnCode, inRnParameter: "in_AGNABBR" });
@ -149,7 +153,7 @@ const DataGrid = ({ title }) => {
//При необходимости обновить данные таблицы //При необходимости обновить данные таблицы
useEffect(() => { useEffect(() => {
loadData(); loadData();
}, [dataGrid.reloading, loadData]); }, [dataGrid.reload, loadData]);
//Генерация содержимого //Генерация содержимого
return ( return (
@ -163,9 +167,16 @@ const DataGrid = ({ title }) => {
{dataGrid.dataLoaded ? ( {dataGrid.dataLoaded ? (
<P8PDataGrid <P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS} {...P8P_DATA_GRID_CONFIG_PROPS}
{...dataGrid} containerComponentProps={{ elevation: 6, style: STYLES.DATA_GRID_CONTAINER }}
columnsDef={dataGrid.columnsDef}
groups={dataGrid.groups}
rows={dataGrid.rows}
size={P8P_DATA_GRID_SIZE.LARGE} size={P8P_DATA_GRID_SIZE.LARGE}
containerComponentProps={{ elevation: 6, sx: STYLES.DATA_GRID_CONTAINER }} fixedHeader={dataGrid.fixedHeader}
fixedColumns={dataGrid.fixedColumns}
filtersInitial={dataGrid.filters}
morePages={dataGrid.morePages}
reloading={dataGrid.reload}
valueFormatter={valueFormatter} valueFormatter={valueFormatter}
headCellRender={headCellRender} headCellRender={headCellRender}
dataCellRender={dataCellRender} dataCellRender={dataCellRender}
@ -173,6 +184,7 @@ const DataGrid = ({ title }) => {
onOrderChanged={handleOrderChanged} onOrderChanged={handleOrderChanged}
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
onPagesCountChanged={handlePagesCountChanged} onPagesCountChanged={handlePagesCountChanged}
expandable={true}
rowExpandRender={({ row }) => ( rowExpandRender={({ row }) => (
<Button onClick={() => handleAgnButtonClicked(row.SAGNABBR)}>Показать в разделе</Button> <Button onClick={() => handleAgnButtonClicked(row.SAGNABBR)}>Показать в разделе</Button>
)} )}

View File

@ -97,10 +97,12 @@ const taskDialogRenderer = ({ task, close }) => {
//Пример: Диаграмма Ганта "P8Gantt" //Пример: Диаграмма Ганта "P8Gantt"
const Gantt = ({ title }) => { const Gantt = ({ title }) => {
//Собственное состояние //Собственное состояние
const [gantt, setGantt] = useState({ const [state, setState] = useState({
init: false, init: false,
dataLoaded: false, dataLoaded: false,
ident: null, ident: null,
ganttDef: {},
ganttTasks: [],
useCustomTaskDialog: false useCustomTaskDialog: false
}); });
@ -111,21 +113,21 @@ const Gantt = ({ title }) => {
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
const data = await executeStored({ const data = await executeStored({
stored: "PKG_P8PANELS_SAMPLES.GANTT", stored: "PKG_P8PANELS_SAMPLES.GANTT",
args: { NIDENT: gantt.ident }, args: { NIDENT: state.ident },
attributeValueProcessor: (name, val) => attributeValueProcessor: (name, val) =>
name == "numb" ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val, name == "numb" ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val,
respArg: "COUT" respArg: "COUT"
}); });
setGantt(pv => ({ ...pv, dataLoaded: true, ...data.XGANTT })); setState(pv => ({ ...pv, dataLoaded: true, ganttDef: { ...data.XGANTT_DEF }, ganttTasks: [...data.XGANTT_TASKS] }));
}, [gantt.ident, executeStored]); }, [state.ident, executeStored]);
//Инициализация данных диаграммы //Инициализация данных диаграммы
const initData = useCallback(async () => { const initData = useCallback(async () => {
if (!gantt.init) { if (!state.init) {
const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_INIT", args: { NIDENT: gantt.ident } }); const data = await executeStored({ stored: "PKG_P8PANELS_SAMPLES.GANTT_INIT", args: { NIDENT: state.ident } });
setGantt(pv => ({ ...pv, init: true, ident: data.NIDENT })); setState(pv => ({ ...pv, init: true, ident: data.NIDENT }));
} }
}, [gantt.init, gantt.ident, executeStored]); }, [state.init, state.ident, executeStored]);
//Изменение данных диаграммы //Изменение данных диаграммы
const modifyData = useCallback( const modifyData = useCallback(
@ -133,13 +135,13 @@ const Gantt = ({ title }) => {
try { try {
await executeStored({ await executeStored({
stored: "PKG_P8PANELS_SAMPLES.GANTT_MODIFY", stored: "PKG_P8PANELS_SAMPLES.GANTT_MODIFY",
args: { NIDENT: gantt.ident, NRN: rn, DDATE_FROM: new Date(start), DDATE_TO: new Date(end) } args: { NIDENT: state.ident, NRN: rn, DDATE_FROM: new Date(start), DDATE_TO: new Date(end) }
}); });
} finally { } finally {
loadData(); loadData();
} }
}, },
[gantt.ident, executeStored, loadData] [state.ident, executeStored, loadData]
); );
//Обработка измненения сроков задачи в диаграмме Гантта //Обработка измненения сроков задачи в диаграмме Гантта
@ -149,8 +151,8 @@ const Gantt = ({ title }) => {
//При необходимости обновить данные таблицы //При необходимости обновить данные таблицы
useEffect(() => { useEffect(() => {
if (gantt.ident) loadData(); if (state.ident) loadData();
}, [gantt.ident, loadData]); }, [state.ident, loadData]);
//При подключении компонента к странице //При подключении компонента к странице
useEffect(() => { useEffect(() => {
@ -166,19 +168,20 @@ const Gantt = ({ title }) => {
</Typography> </Typography>
<FormControlLabel <FormControlLabel
sx={STYLES.CONTROL} sx={STYLES.CONTROL}
control={<Checkbox onChange={() => setGantt(pv => ({ ...pv, useCustomTaskDialog: !pv.useCustomTaskDialog }))} />} control={<Checkbox onChange={() => setState(pv => ({ ...pv, useCustomTaskDialog: !pv.useCustomTaskDialog }))} />}
label="Отображать пользовательский диалог задачи" label="Отображать пользовательский диалог задачи"
/> />
<Grid container direction="column" alignItems="center"> <Grid container direction="column" alignItems="center">
<Grid item xs={12}> <Grid item xs={12}>
{gantt.dataLoaded ? ( {state.dataLoaded ? (
<P8PGantt <P8PGantt
{...P8P_GANTT_CONFIG_PROPS} {...P8P_GANTT_CONFIG_PROPS}
{...gantt} {...state.ganttDef}
containerStyle={STYLES.GANTT_CONTAINER} containerStyle={STYLES.GANTT_CONTAINER}
tasks={state.ganttTasks}
onTaskDatesChange={handleTaskDatesChange} onTaskDatesChange={handleTaskDatesChange}
taskAttributeRenderer={taskAttributeRenderer} taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={gantt.useCustomTaskDialog ? taskDialogRenderer : null} taskDialogRenderer={state.useCustomTaskDialog ? taskDialogRenderer : null}
/> />
) : null} ) : null}
</Grid> </Grid>

View File

@ -1,156 +0,0 @@
/*
Парус 8 - Панели мониторинга - Примеры для разработчиков
Пример: Индикатор "P8PIndicator"
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Typography, Stack, Divider } from "@mui/material"; //Интерфейсные элементы
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator } from "../../components/p8p_indicator"; //Индикатор
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { textAlign: "center", paddingTop: "20px" },
TITLE: { paddingBottom: "15px" },
DIVIDER: { margin: "15px" }
};
//-----------
//Тело модуля
//-----------
//Пример: Индикатор "P8PIndicator"
const Indicator = ({ title }) => {
//Подключение к контексту сообщений
const { showMsgInfo } = useContext(MessagingСtx);
//Генерация содержимого
return (
<div style={STYLES.CONTAINER}>
<Typography sx={STYLES.TITLE} variant={"h6"}>
{title}
</Typography>
<Divider>Иконка</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{/* Индикатор (без иконки) */}
<P8PIndicator caption={"Без иконки"} value={10} />
{/* Индикатор (с иконкой 1) */}
<P8PIndicator caption={"С иконкой - Back Hand"} value={20} icon={"back_hand"} />
{/* Индикатор (с иконкой 2) */}
<P8PIndicator caption={"С иконкой - Scoreboard"} value={30} icon={"scoreboard"} />
</Stack>
<Divider>Состояние</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{/* Индикатор (нейтральный) */}
<P8PIndicator caption={"Нейтральное состояние"} value={10} icon={"sentiment_neutral"} />
{/* Индикатор (позитивный) */}
<P8PIndicator caption={"Позитивное состояние"} value={20} state={P8P_INDICATOR_STATE.OK} icon={"check_circle"} />
{/* Индикатор (пограничный) */}
<P8PIndicator caption={"Пограничное состояние"} value={30} state={P8P_INDICATOR_STATE.WARN} icon={"warning"} />
{/* Индикатор (негативный) */}
<P8PIndicator caption={"Негативное состояния"} value={40} state={P8P_INDICATOR_STATE.ERR} icon={"dangerous"} />
</Stack>
<Divider>Скругление</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{/* Индикатор (скругленный) */}
<P8PIndicator caption={"Скругленный"} />
{/* Индикатор (квадратный) */}
<P8PIndicator caption={"Квадрадтный"} square={true} />
</Stack>
<Divider>Парение</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{/* Индикатор (парение - 0) */}
<P8PIndicator caption={"Парение"} value={0} state={P8P_INDICATOR_STATE.OK} elevation={0} />
{/* Индикатор (парение - 3) */}
<P8PIndicator caption={"Парение (по умолчанию)"} value={3} state={P8P_INDICATOR_STATE.WARN} elevation={3} />
{/* Индикатор (парение - 6) */}
<P8PIndicator caption={"Парение"} value={6} state={P8P_INDICATOR_STATE.OK} elevation={6} />
{/* Индикатор (парение - 12) */}
<P8PIndicator caption={"Парение"} value={12} state={P8P_INDICATOR_STATE.OK} elevation={12} />
{/* Индикатор (парение - 18) */}
<P8PIndicator caption={"Парение"} value={18} state={P8P_INDICATOR_STATE.OK} elevation={18} />
</Stack>
<Divider>Исполнение</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{/* Индикатор (парение) */}
<P8PIndicator caption={"Парящий (по умолчанию)"} value={123} />
{/* Индикатор (рамка) */}
<P8PIndicator caption={"Рамка"} value={321} variant={P8P_INDICATOR_VARIANT.OUTLINED} />
</Stack>
<Divider>Подсказка</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{/* Индикатор (подсказка без форматирования) */}
<P8PIndicator
caption={"Подсказка (без форматирования)"}
value={42}
icon={"desktop_windows"}
hint={"Ответ на главный вопрос жизни, вселенной и всего такого..."}
/>
{/* Индикатор (подсказка с форматирование) */}
<P8PIndicator
caption={"Подсказка (с форматированием)"}
value={3.14}
icon={"radio_button_unchecked"}
hint={`Математическая <b>постоянная</b>, равная <b style='color:red'>отношению</b> <b style='color:green'>длины окружности</b>
к её <b style='color:blue'>диаметру</b>:
<p style='text-align: center'>&#960; = <span style='color:green'>L</span>/<span style='color:blue'>d</span></p>`}
/>
</Stack>
<Divider>Активность</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{[P8P_INDICATOR_STATE.UNDEFINED, P8P_INDICATOR_STATE.OK, P8P_INDICATOR_STATE.WARN, P8P_INDICATOR_STATE.ERR].map(
(indicatorState, i) => (
<P8PIndicator
key={i}
caption={`Нажми на меня #${i + 1}`}
value={i + 1}
state={indicatorState}
icon={"chat"}
onClick={() => showMsgInfo(`Нажатие на индикатор #${i + 1}`)}
hint={`Подсказка индикатора #${i + 1}`}
/>
)
)}
</Stack>
<Divider>Пользовательские цвета</Divider>
<Stack direction={"row"} spacing={2} p={5}>
{[
["yellow", "black"],
["darkred", "yellow"],
["orange", "darkblue"],
["magenta", "darkmagenta"]
].map((userColor, i) => (
<P8PIndicator
key={i}
caption={`Текст: ${userColor[0]}, Заливка: ${userColor[1]}`}
value={i + 1}
state={P8P_INDICATOR_STATE.WARN}
icon={"palette"}
color={userColor[0]}
backgroundColor={userColor[1]}
/>
))}
</Stack>
</div>
);
};
//Контроль свойств - Пример: Индикатор "P8PIndicator"
Indicator.propTypes = {
title: PropTypes.string.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { Indicator };

View File

@ -46,20 +46,6 @@ const Messages = ({ title }) => {
Ошибка Ошибка
</Button> </Button>
<Divider sx={STYLES.DIVIDER} /> <Divider sx={STYLES.DIVIDER} />
{/* Сообщение об ошибке (диалог с подробностями) */}
<Button
variant="contained"
onClick={() =>
showMsgErr(
"Что-то пошло не так :( ...но мы точно знаем что ;)",
null,
"Здесь подробная информация об ошибке (стек вызова СУБД, например)"
)
}
>
Ошибка с подробностями
</Button>
<Divider sx={STYLES.DIVIDER} />
{/* Предупреждение (диалог) */} {/* Предупреждение (диалог) */}
<Button <Button
variant="contained" variant="contained"

View File

@ -18,8 +18,6 @@ import { DataGrid } from "./data_grid"; //Пример: Таблица данн
import { Chart } from "./chart"; //Пример: Графики "P8PChart" import { Chart } from "./chart"; //Пример: Графики "P8PChart"
import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt" import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt"
import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG" import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG"
import { Cyclogram } from "./cyclogram"; //Пример: Циклограмма "P8PCyclogram"
import { Indicator } from "./indicator"; //Пример: Индикатор "P8PIndicator"
//--------- //---------
//Константы //Константы
@ -34,9 +32,7 @@ const MODES = {
DATAGRID: { name: "DATAGRID", caption: 'Таблица данных "P8PDataGrid"', component: DataGrid }, DATAGRID: { name: "DATAGRID", caption: 'Таблица данных "P8PDataGrid"', component: DataGrid },
CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart }, CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart },
GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt }, GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt },
SVG: { name: "SVG", caption: 'Интерактивные изображения "P8PSVG"', component: Svg }, SVG: { name: "SVG", caption: 'Интерактивные изображения "P8PSVG"', component: Svg }
CYCLOGRAM: { name: "CYCLOGRAM", caption: 'Циклограмма "P8PCyclogram"', component: Cyclogram },
INDICATOR: { name: "INDICATOR", caption: 'Индикатор "P8PIndicator"', component: Indicator }
}; };
//Стили //Стили

View File

@ -19,8 +19,8 @@ create table P8PNL_JB_JOBS
EDITABLE number(1) default 0 not null, -- Признак возможности редактирования (0 - нет, 1 - да) EDITABLE number(1) default 0 not null, -- Признак возможности редактирования (0 - нет, 1 - да)
CHANGED number(1) default 0 not null, -- Признак наличия изменений, требующих сохранения (0 - нет, 1 - да) CHANGED number(1) default 0 not null, -- Признак наличия изменений, требующих сохранения (0 - нет, 1 - да)
constraint C_P8PNL_JB_JOBS_RN_PK primary key (RN), constraint C_P8PNL_JB_JOBS_RN_PK primary key (RN),
constraint C_P8PNL_JB_JOBS_PRN_FK foreign key (PRN) references P8PNL_JB_PRJCTS (RN) on delete cascade, constraint C_P8PNL_JB_JOBS_PRN_FK foreign key (PRN) references P8PNL_JB_PRJCTS (RN),
constraint C_P8PNL_JB_JOBS_HRN_FK foreign key (HRN) references P8PNL_JB_JOBS (RN) on delete cascade, constraint C_P8PNL_JB_JOBS_HRN_FK foreign key (HRN) references P8PNL_JB_JOBS (RN),
constraint C_P8PNL_JB_JOBS_STAGE_VAL check (STAGE in (0, 1)), constraint C_P8PNL_JB_JOBS_STAGE_VAL check (STAGE in (0, 1)),
constraint C_P8PNL_JB_JOBS_EDTBL_VAL check (EDITABLE in (0, 1)), constraint C_P8PNL_JB_JOBS_EDTBL_VAL check (EDITABLE in (0, 1)),
constraint C_P8PNL_JB_JOBS_CHNGD_VAL check (CHANGED in (0, 1)), constraint C_P8PNL_JB_JOBS_CHNGD_VAL check (CHANGED in (0, 1)),

View File

@ -9,7 +9,7 @@ create table P8PNL_JB_JOBSPREV
PRN number(17) not null, -- Рег. номер родителя PRN number(17) not null, -- Рег. номер родителя
JB_JOBS number(17) not null, -- Рег. номер предшествующей работы/этапа JB_JOBS number(17) not null, -- Рег. номер предшествующей работы/этапа
constraint C_P8PNL_JB_JOBSPREV_RN_PK primary key (RN), constraint C_P8PNL_JB_JOBSPREV_RN_PK primary key (RN),
constraint C_P8PNL_JB_JOBSPREV_PRN_FK foreign key (PRN) references P8PNL_JB_JOBS (RN) on delete cascade, constraint C_P8PNL_JB_JOBSPREV_PRN_FK foreign key (PRN) references P8PNL_JB_JOBS (RN),
constraint C_P8PNL_JB_JOBSPREV_JB_JOBS_FK foreign key (JB_JOBS) references P8PNL_JB_JOBS (RN) on delete cascade, constraint C_P8PNL_JB_JOBSPREV_JB_JOBS_FK foreign key (JB_JOBS) references P8PNL_JB_JOBS (RN),
constraint C_P8PNL_JB_JOBSPREV_UN unique (IDENT, PRN, JB_JOBS) constraint C_P8PNL_JB_JOBSPREV_UN unique (IDENT, PRN, JB_JOBS)
); );

View File

@ -15,7 +15,7 @@ create table P8PNL_JB_PERIODS
LAB_PLAN_JOBS number(17,3) default 0 not null, -- Трудоёмкость (план, по графику) LAB_PLAN_JOBS number(17,3) default 0 not null, -- Трудоёмкость (план, по графику)
constraint C_P8PNL_JB_PERIODS_RN_PK primary key (RN), constraint C_P8PNL_JB_PERIODS_RN_PK primary key (RN),
constraint C_P8PNL_JB_PERIODS_DATE_VAL check (DATE_FROM <= DATE_TO), constraint C_P8PNL_JB_PERIODS_DATE_VAL check (DATE_FROM <= DATE_TO),
constraint C_P8PNL_JB_PERIODS_INS_DEP_FK foreign key (INS_DEPARTMENT) references INS_DEPARTMENT (RN) on delete cascade, constraint C_P8PNL_JB_PERIODS_INS_DEP_FK foreign key (INS_DEPARTMENT) references INS_DEPARTMENT (RN),
constraint C_P8PNL_JB_PERIODS_FCMNPWR_FK foreign key (FCMANPOWER) references FCMANPOWER (RN) on delete cascade, constraint C_P8PNL_JB_PERIODS_FCMNPWR_FK foreign key (FCMANPOWER) references FCMANPOWER (RN),
constraint C_P8PNL_JB_PERIODS_UN unique (IDENT, DATE_FROM, INS_DEPARTMENT, FCMANPOWER) constraint C_P8PNL_JB_PERIODS_UN unique (IDENT, DATE_FROM, INS_DEPARTMENT, FCMANPOWER)
); );

View File

@ -11,7 +11,7 @@ create table P8PNL_JB_PRJCTS
EDITABLE number(1) default 0 not null, -- Признак возможности редактирования (0 - нет, 1 - да) EDITABLE number(1) default 0 not null, -- Признак возможности редактирования (0 - нет, 1 - да)
CHANGED number(1) default 0 not null, -- Признак наличия изменений, требующих сохранения (0 - нет, 1 - да) CHANGED number(1) default 0 not null, -- Признак наличия изменений, требующих сохранения (0 - нет, 1 - да)
constraint C_P8PNL_JB_PRJCTS_RN_PK primary key (RN), constraint C_P8PNL_JB_PRJCTS_RN_PK primary key (RN),
constraint C_P8PNL_JB_PRJCTS_PROJECT_FK foreign key (PROJECT) references PROJECT (RN) on delete cascade, constraint C_P8PNL_JB_PRJCTS_PROJECT_FK foreign key (PROJECT) references PROJECT (RN),
constraint C_P8PNL_JB_PRJCTS_JOBS_VAL check (JOBS in (0, 1)), constraint C_P8PNL_JB_PRJCTS_JOBS_VAL check (JOBS in (0, 1)),
constraint C_P8PNL_JB_PRJCTS_EDTBL_VAL check (EDITABLE in (0, 1)), constraint C_P8PNL_JB_PRJCTS_EDTBL_VAL check (EDITABLE in (0, 1)),
constraint C_P8PNL_JB_PRJCTS_CHNGD_VAL check (CHANGED in (0, 1)), constraint C_P8PNL_JB_PRJCTS_CHNGD_VAL check (CHANGED in (0, 1)),

View File

@ -1,18 +0,0 @@
/*
Парус 8 - Панели мониторинга - Примеры
Буфер для циклограммы
*/
create table P8PNL_SMPL_CYCLOGRAM
(
RN number(17) not null, -- Рег. номер записи
IDENT number(17) not null, -- Идентификатор процесса
TYPE number(1) not null, -- Тип (0 - колонка, 1 - группа, 2 - задача)
NAME varchar2(200) not null, -- Наименование
POS_START number(17) default null, -- Начальная позиция на циклограмме
POS_END number(17) default null, -- Конечная позиция на циклограмме
DATE_FROM date default null, -- Дата начала
DATE_TO date default null, -- Дата окончания
TASK_GROUP number(17) default null, -- Группа задач
constraint C_P8PNL_SMPL_CYCLOGRAM_RN_PK primary key (RN),
constraint C_P8PNL_SMPL_CYCLOGRAM_TP_VAL check (TYPE in (0, 1, 2))
);

View File

@ -27,13 +27,6 @@ create or replace package PKG_P8PANELS_BASE as
NDOCUMENT in number -- Рег. номер документа NDOCUMENT in number -- Рег. номер документа
) return number; -- Флаг доступности (см. константы NACCESS_*) ) return number; -- Флаг доступности (см. константы NACCESS_*)
/* Подготовка пользовательской строки поиска для вставки в запрос */
procedure UTL_SEARCH_PREPARE
(
SSEARCH in varchar2, -- Пользовательская строка поиска
SSEARCH_PREPARED out varchar2 -- Подготовленная строка поиска
);
/* Базовое исполнение действий */ /* Базовое исполнение действий */
procedure PROCESS procedure PROCESS
( (
@ -198,36 +191,6 @@ create or replace package body PKG_P8PANELS_BASE as
return NACCESS_NO; return NACCESS_NO;
end UTL_DOC_ACCESS_CHECK; end UTL_DOC_ACCESS_CHECK;
/* Подготовка пользовательской строки поиска для вставки в запрос */
procedure UTL_SEARCH_PREPARE
(
SSEARCH in varchar2, -- Пользовательская строка поиска
SSEARCH_PREPARED out varchar2 -- Подготовленная строка поиска
)
is
/* Локальные константы */
SANY_SYS constant char(1) := '%'; -- Маска "любое количество любых символов" Oracle
SONE_SYS constant char(1) := '_'; -- Маска "любой один символ" Oracle
SENT_WRD_DELIM constant varchar2(1) := ' '; -- Разделитель слов в предложении
SANY_PRS constant varchar2(240) := PKG_OPTIONS.STARSYMB; -- Маска "любое количество любых символов" Парус
SONE_PRS constant varchar2(240) := PKG_OPTIONS.QUESTSYMB; -- Маска "любой один символ" Парус
begin
/* Если пользовательская строка пустая - то это всё что угодно */
if (SSEARCH is null)
then
SSEARCH_PREPARED := SANY_SYS;
else
/* Подменим пользовательские маски на системные и соберем подготовленную строку поиска */
SSEARCH_PREPARED := '%' || replace(replace(replace(SSEARCH
,SANY_PRS
,SANY_SYS)
,SONE_PRS
,SONE_SYS)
,SENT_WRD_DELIM
,SANY_SYS) || '%';
end if;
end UTL_SEARCH_PREPARE;
/* Формирование сообщения об отсутствии значения */ /* Формирование сообщения об отсутствии значения */
function MSG_NO_DATA_MAKE function MSG_NO_DATA_MAKE
( (

View File

@ -1,127 +0,0 @@
create or replace package PKG_P8PANELS_EDITOR as
/* Список аргументов пользовательской процедуры */
procedure USERPROCS_DESC
(
SCODE in varchar2, -- Мнемокод пользовательской процедуры
COUT out clob -- Сериализованный список аргументов
);
end PKG_P8PANELS_EDITOR;
/
create or replace package body PKG_P8PANELS_EDITOR as
/* Описание пользовательской процедуры */
procedure USERPROCS_DESC
(
SCODE in varchar2, -- Мнемокод пользовательской процедуры
COUT out clob -- Сериализованный список аргументов
)
is
SRESP_ARG PKG_STD.TSTRING; -- Имя выходного визуализируемого параметра
begin
/* Обращаемся к процедуре */
for C in (select T.RN NRN,
T.PROCNAME SPROC_NAME,
T.PROCTYPE NPROC_TYPE
from USERPROCS T
where T.CODE = SCODE)
loop
/* Проверим возможность использования ПП в качестве источника данных */
if (C.NPROC_TYPE <> 0) then
P_EXCEPTION(0,
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь тип "Хранимая процедура".',
SCODE);
end if;
if (C.SPROC_NAME is null) then
P_EXCEPTION(0,
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: не указана хранимая процедура.',
SCODE);
end if;
/* Начинаем формирование XML */
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
/* Открываем корень */
PKG_XFAST.DOWN_NODE(SNAME => 'XDATA');
/* Открываем описание процедуры */
PKG_XFAST.DOWN_NODE(SNAME => 'XUSERPROC');
/* Обходим параметры */
for P in (select T.PARAMTYPE NTYPE,
T.PARAMNAME SNAME,
T.NAME SCAPTION,
T.DATATYPE NDATA_TYPE,
case T.DATATYPE
when 0 then
'STR'
when 1 then
'NUMB'
when 2 then
'DATE'
else
null
end SDATA_TYPE,
T.MANDATORY NREQ,
T.VISUALIZE NVISUALIZE
from USERPROCSPARAMS T
where T.PRN = C.NRN
order by T.POSITION)
loop
/* В результирующий список забираем только входные поддерживаемого типа */
if ((P.NTYPE = 0) and (P.SDATA_TYPE is not null)) then
/* Открываем описание аргумента */
PKG_XFAST.DOWN_NODE(SNAME => 'arguments');
/* Описываем аргумент */
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => P.SNAME);
PKG_XFAST.ATTR(SNAME => 'caption', SVALUE => P.SCAPTION);
PKG_XFAST.ATTR(SNAME => 'dataType', SVALUE => P.SDATA_TYPE);
PKG_XFAST.ATTR(SNAME => 'req',
BVALUE => case P.NREQ
when 1 then
true
else
false
end);
/* Закрываем описание аргумента */
PKG_XFAST.UP();
end if;
/* Если встретился визуализируемый параметр типа CLOB */
if ((P.NVISUALIZE = 1) and (P.NDATA_TYPE = 4)) then
if (SRESP_ARG is null) then
SRESP_ARG := P.SNAME;
else
/* Это уже второй такой - ошибка */
P_EXCEPTION(0,
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: имеет более одного выходного параметра типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
SCODE);
end if;
end if;
end loop;
/* Сформируем описание хранимой процедуры */
PKG_XFAST.DOWN_NODE(SNAME => 'stored');
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => C.SPROC_NAME);
PKG_XFAST.ATTR(SNAME => 'respArg', SVALUE => SRESP_ARG);
PKG_XFAST.UP();
/* Закрываем описание процедуры */
PKG_XFAST.UP();
/* Закрываем описание корня */
PKG_XFAST.UP();
/* Сериализуем */
COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
/* Завершаем формирование XML */
PKG_XFAST.EPILOGUE();
end loop;
/* Если ничего не нашли */
if (COUT is null) then
P_EXCEPTION(0,
'Пользовательская процедура "%s" не определена.',
COALESCE(SCODE, '<НЕ УКАЗАНА>'));
end if;
/* Проверим возможность использования ПП в качестве источника данных - должен быть выходной визуализируемый параметр типа CLOB */
if (SRESP_ARG is null) then
P_EXCEPTION(0,
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь выходной параметр типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
SCODE);
end if;
end USERPROCS_DESC;
end PKG_P8PANELS_EDITOR;
/

Some files were not shown because too many files have changed in this diff Show More