Compare commits
No commits in common. "main" and "MechRecCostProdPlans" have entirely different histories.
main
...
MechRecCos
@ -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: {
|
||||||
|
|||||||
39
app.text.js
39
app.text.js
@ -12,21 +12,13 @@ export const TITLES = {
|
|||||||
INFO: "Информация", //Информационный блок
|
INFO: "Информация", //Информационный блок
|
||||||
WARN: "Предупреждение", //Блок предупреждения
|
WARN: "Предупреждение", //Блок предупреждения
|
||||||
ERR: "Ошибка", //Информация об ошибке
|
ERR: "Ошибка", //Информация об ошибке
|
||||||
DEFAULT_PANELS_GROUP: "Без привязки к группе", //Заголовок группы панелей по умолчанию
|
DEFAULT_PANELS_GROUP: "Без привязки к группе" //Заголовок группы панелей по умолчанию
|
||||||
DATA_SOURCE_CONFIG: "Настройка источника данных", //Заголовок для настройки источника данных
|
|
||||||
INSERT: "Добавление", //Заголовок для диалогов/форм добавления
|
|
||||||
UPDATE: "Исправление", //Заголовок для диалогов/форм исправления
|
|
||||||
CONFIG: "Настройка" //Заголовок для диалога настройки
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Текст
|
//Текст
|
||||||
export const TEXTS = {
|
export const TEXTS = {
|
||||||
LOADING: "Ожидайте...", //Ожидание завершения процесса
|
LOADING: "Ожидайте...", //Ожидание завершения процесса
|
||||||
NO_DATA_FOUND: "Данных не найдено", //Отсутствие данных
|
NO_DATA_FOUND: "Данных не найдено" //Отсутствие данных
|
||||||
NO_DATA_FOUND_SHORT: "Н.Д.", //Отсутствие данных (кратко)
|
|
||||||
NO_SETTINGS: "Настройки не определены", //Отстутсвие настроек
|
|
||||||
UNKNOWN_SOURCE_TYPE: "Неизвестный тип источника", //Отсуствие типа источника
|
|
||||||
UNNAMED_SOURCE: "Источник без наименования" //Отсутствие наименования источника
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Текст кнопок
|
//Текст кнопок
|
||||||
@ -37,19 +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: "Сохранить", //Сохранение
|
|
||||||
CONFIG: "Настроить", //Настройка
|
|
||||||
INSERT: "Добавить", //Добавление
|
|
||||||
UPDATE: "Исправить", //Исправление
|
|
||||||
DELETE: "Удалить" //Удаление
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Метки атрибутов, сопроводительные надписи
|
//Метки атрибутов, сопроводительные надписи
|
||||||
@ -62,30 +46,17 @@ export const CAPTIONS = {
|
|||||||
START: "Начало",
|
START: "Начало",
|
||||||
END: "Окончание",
|
END: "Окончание",
|
||||||
PROGRESS: "Прогресс",
|
PROGRESS: "Прогресс",
|
||||||
LEGEND: "Легенда",
|
LEGEND: "Легенда"
|
||||||
USER_PROC: "Пользовательская процедура",
|
|
||||||
QUERY: "Запрос"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Типовые сообщения об ошибках
|
//Типовые сообщения об ошибках
|
||||||
export const ERRORS = {
|
export const ERRORS = {
|
||||||
UNDER_CONSTRUCTION: "Панель в разработке",
|
UNDER_CONSTRUCTION: "Панель в разработке",
|
||||||
P8O_API_UNAVAILABLE: '"ПАРУС 8 Онлайн" недоступен',
|
P8O_API_UNAVAILABLE: '"ПАРУС 8 Онлайн" недоступен',
|
||||||
P8O_API_UNSUPPORTED: 'Функция "ПАРУС 8 Онлайн" не поддерживается',
|
DEFAULT: "Неожиданная ошибка"
|
||||||
DEFAULT: "Неожиданная ошибка",
|
|
||||||
DATA_SOURCE_NO_REQ_ARGS: "Не заданы обязательные параметры источника данных"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Типовые сообщения для ошибок HTTP
|
//Типовые сообщения для ошибок HTTP
|
||||||
export const ERRORS_HTTP = {
|
export const ERRORS_HTTP = {
|
||||||
404: "Адрес не найден"
|
404: "Адрес не найден"
|
||||||
};
|
};
|
||||||
|
|
||||||
//Типовые статусы
|
|
||||||
export const STATE = {
|
|
||||||
UNDEFINED: "UNDEFINED",
|
|
||||||
INFO: "INFORMATION",
|
|
||||||
OK: "OK",
|
|
||||||
ERR: "ERR",
|
|
||||||
WARN: "WARN"
|
|
||||||
};
|
|
||||||
|
|||||||
@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,71 +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 P8P_COMPONENT_INLINE_MESSAGE_TYPE = {
|
|
||||||
COMMON: "COMMON",
|
|
||||||
ERROR: "ERROR"
|
|
||||||
};
|
|
||||||
|
|
||||||
//Типовые сообщения компонентов
|
|
||||||
const P8P_COMPONENT_INLINE_MESSAGE = {
|
|
||||||
NO_DATA_FOUND: TEXTS.NO_DATA_FOUND,
|
|
||||||
NO_SETTINGS: TEXTS.NO_SETTINGS
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Информационное сообщение внутри компонента
|
|
||||||
const P8PComponentInlineMessage = ({ icon, name, message, type = P8P_COMPONENT_INLINE_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 != P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR ? "text.secondary" : "error.dark"}
|
|
||||||
variant={"caption"}
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Информационное сообщение внутри компонента
|
|
||||||
P8PComponentInlineMessage.propTypes = {
|
|
||||||
icon: PropTypes.string,
|
|
||||||
name: PropTypes.string,
|
|
||||||
message: PropTypes.string.isRequired,
|
|
||||||
type: PropTypes.oneOf(Object.values(P8P_COMPONENT_INLINE_MESSAGE_TYPE))
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8P_COMPONENT_INLINE_MESSAGE_TYPE, P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage };
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Диалог настройки
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { P8PDialog } from "../p8p_dialog"; //Типовой диалог
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Диалог настройки
|
|
||||||
const P8PConfigDialog = ({ title, children, onOk, onCancel }) => {
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<P8PDialog title={title} onOk={onOk} onCancel={onCancel}>
|
|
||||||
{children}
|
|
||||||
</P8PDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Диалог настройки
|
|
||||||
P8PConfigDialog.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
|
||||||
onOk: PropTypes.func,
|
|
||||||
onCancel: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PConfigDialog };
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Источник данных
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Stack, IconButton, Icon, Typography, Chip, Button, Card, CardContent, CardActions, CardActionArea } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { BUTTONS, TEXTS } from "../../../app.text"; //Общие текстовые ресурсы
|
|
||||||
import { STYLES as COMMON_STYLES } from "./p8p_editors_common"; //Общие ресурсы редаторов
|
|
||||||
import { P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_TYPE_NAME, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
|
||||||
import { P8PDataSourceConfigDialog } from "./p8p_data_source_config_dialog"; //Диалог настройки источника данных
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Источник данных
|
|
||||||
const P8PDataSource = ({ 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({ ...P8P_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={COMMON_STYLES.CHIP(true)}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{configDlg && (
|
|
||||||
<P8PDataSourceConfigDialog
|
|
||||||
dataSource={dataSource}
|
|
||||||
valueProviders={valueProviders}
|
|
||||||
onOk={handleSetupOk}
|
|
||||||
onCancel={handleSetupCancel}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{configured && (
|
|
||||||
<Card variant={"outlined"}>
|
|
||||||
<CardActionArea onClick={handleSetup}>
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant={"subtitle1"} noWrap={true}>
|
|
||||||
{dataSource.type === P8P_DATA_SOURCE_TYPE.USER_PROC ? dataSource.userProc : TEXTS.UNNAMED_SOURCE}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant={"caption"} color={"text.secondary"} noWrap={true}>
|
|
||||||
{P8P_DATA_SOURCE_TYPE_NAME[dataSource.type] || TEXTS.UNKNOWN_SOURCE_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}>
|
|
||||||
{BUTTONS.CONFIG}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Источник данных
|
|
||||||
P8PDataSource.propTypes = {
|
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
|
||||||
valueProviders: PropTypes.object,
|
|
||||||
onChange: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PDataSource };
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Общие ресурсы компонента "Источник данных"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import client from "../../core/client"; //Клиент БД
|
|
||||||
import { CAPTIONS } from "../../../app.text"; //Общие текстовые ресурсы
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Типы даных аргументов
|
|
||||||
const P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE = {
|
|
||||||
STR: client.SERV_DATA_TYPE_STR,
|
|
||||||
NUMB: client.SERV_DATA_TYPE_NUMB,
|
|
||||||
DATE: client.SERV_DATA_TYPE_DATE
|
|
||||||
};
|
|
||||||
|
|
||||||
//Структура аргумента источника данных
|
|
||||||
const P8P_DATA_SOURCE_ARGUMENT_SHAPE = PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
caption: PropTypes.string.isRequired,
|
|
||||||
dataType: PropTypes.oneOf(Object.values(P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE)),
|
|
||||||
req: PropTypes.bool.isRequired,
|
|
||||||
value: PropTypes.any,
|
|
||||||
valueSource: PropTypes.string
|
|
||||||
});
|
|
||||||
|
|
||||||
//Начальное состояние аргумента источника данных
|
|
||||||
const P8P_DATA_SOURCE_ARGUMENT_INITIAL = {
|
|
||||||
name: "",
|
|
||||||
caption: "",
|
|
||||||
dataType: "",
|
|
||||||
req: false,
|
|
||||||
value: "",
|
|
||||||
valueSource: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
//Типы источников данных
|
|
||||||
const P8P_DATA_SOURCE_TYPE = {
|
|
||||||
USER_PROC: "USER_PROC",
|
|
||||||
QUERY: "QUERY"
|
|
||||||
};
|
|
||||||
|
|
||||||
//Типы источников данных (наименования)
|
|
||||||
const P8P_DATA_SOURCE_TYPE_NAME = {
|
|
||||||
[P8P_DATA_SOURCE_TYPE.USER_PROC]: CAPTIONS.USER_PROC,
|
|
||||||
[P8P_DATA_SOURCE_TYPE.QUERY]: CAPTIONS.QUERY
|
|
||||||
};
|
|
||||||
|
|
||||||
//Структура источника данных
|
|
||||||
const P8P_DATA_SOURCE_SHAPE = PropTypes.shape({
|
|
||||||
type: PropTypes.oneOf([...Object.values(P8P_DATA_SOURCE_TYPE), ""]),
|
|
||||||
userProc: PropTypes.string,
|
|
||||||
stored: PropTypes.string,
|
|
||||||
respArg: PropTypes.string,
|
|
||||||
arguments: PropTypes.arrayOf(P8P_DATA_SOURCE_ARGUMENT_SHAPE)
|
|
||||||
});
|
|
||||||
|
|
||||||
//Начальное состояние истоника данных
|
|
||||||
const P8P_DATA_SOURCE_INITIAL = {
|
|
||||||
type: "",
|
|
||||||
userProc: "",
|
|
||||||
stored: "",
|
|
||||||
respArg: "",
|
|
||||||
arguments: []
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE,
|
|
||||||
P8P_DATA_SOURCE_ARGUMENT_INITIAL,
|
|
||||||
P8P_DATA_SOURCE_SHAPE,
|
|
||||||
P8P_DATA_SOURCE_TYPE,
|
|
||||||
P8P_DATA_SOURCE_TYPE_NAME,
|
|
||||||
P8P_DATA_SOURCE_INITIAL
|
|
||||||
};
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Диалог настройки источника данных
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState, useEffect, useContext } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Stack, IconButton, Icon, TextField, InputAdornment, MenuItem, Menu } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
|
||||||
import { TITLES, CAPTIONS } from "../../../app.text"; //Общие текстовые ресурсы
|
|
||||||
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
|
||||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_ARGUMENT_INITIAL, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
|
||||||
import { useUserProcDesc } from "./p8p_data_source_hooks"; //Хуки источников данных
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Диалог настройки источника данных
|
|
||||||
const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => {
|
|
||||||
//Собственное состояние - параметры элемента формы
|
|
||||||
const [state, setState] = useState({ ...P8P_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({ ...P8P_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: P8P_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 => ({ ...P8P_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 (
|
|
||||||
<P8PConfigDialog title={TITLES.DATA_SOURCE_CONFIG} onOk={handleOk} onCancel={handleCancel}>
|
|
||||||
<Stack direction={"column"} spacing={1}>
|
|
||||||
{valueProvidersMenu}
|
|
||||||
<TextField
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={state.userProc}
|
|
||||||
label={CAPTIONS.USER_PROC}
|
|
||||||
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>
|
|
||||||
</P8PConfigDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Диалог настройки источника данных
|
|
||||||
P8PDataSourceConfigDialog.propTypes = {
|
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
|
||||||
valueProviders: PropTypes.object,
|
|
||||||
onOk: PropTypes.func,
|
|
||||||
onCancel: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PDataSourceConfigDialog };
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Пользовательские хуки компонента "Источник данных"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useContext, useEffect, useRef } from "react"; //Классы React
|
|
||||||
import client from "../../core/client"; //Клиент взаимодействия с сервером приложений
|
|
||||||
import { ERRORS } from "../../../app.text"; //Общие текстовые ресурсы
|
|
||||||
import { formatErrorMessage } from "../../core/utils"; //Общие вспомогательные функции
|
|
||||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE } from "./p8p_data_source_common"; //Общие ресурсы источника данных
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Описание пользовательской процедуры
|
|
||||||
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_PE.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 useDataSource = ({ 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 == P8P_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 == P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE.NUMB
|
|
||||||
? isNaN(parseFloat(v))
|
|
||||||
? null
|
|
||||||
: parseFloat(v)
|
|
||||||
: argument.dataType == P8P_DATA_SOURCE_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(ERRORS.DATA_SOURCE_NO_REQ_ARGS);
|
|
||||||
setData({ init: false });
|
|
||||||
}
|
|
||||||
return { stored, respArg, storedArgs, reqSet };
|
|
||||||
} else return pv;
|
|
||||||
} else return pv;
|
|
||||||
});
|
|
||||||
}, [dataSource, values]);
|
|
||||||
|
|
||||||
//Возвращаем интерфейс хука
|
|
||||||
return [data, error, isLoading];
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useUserProcDesc, useDataSource };
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Контейнер редактора
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Box, Divider, IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты MUI
|
|
||||||
import { BUTTONS } from "../../../app.text"; //Общие текстовые ресурсы
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Контейнер редактора
|
|
||||||
const P8PEditorBox = ({ title, children, onSave }) => {
|
|
||||||
//При нажатии на "Сохранить"
|
|
||||||
const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor);
|
|
||||||
|
|
||||||
//Флаг отображения кнопок сохранения
|
|
||||||
const showSaveBar = onSave ? true : false;
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Box p={2}>
|
|
||||||
<Divider>{title}</Divider>
|
|
||||||
<Stack direction={"column"} spacing={1}>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
{showSaveBar && (
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Контейнер редактора
|
|
||||||
P8PEditorBox.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
|
||||||
onSave: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PEditorBox };
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Заголовок раздела редактора
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Divider, Chip } from "@mui/material"; //Интерфейсные компоненты MUI
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
DIVIDER: pt => ({ paddingTop: pt || pt === 0 ? `${pt}px` : "20px" }),
|
|
||||||
CHIP: maxWidth => ({ cursor: "default", ...(maxWidth ? { maxWidth } : {}) })
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Заголовок раздела редактора
|
|
||||||
const P8PEditorSubHeader = ({ title, paddingTop, maxWidth }) => {
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Divider sx={STYLES.DIVIDER(paddingTop)}>
|
|
||||||
<Chip label={title} size={"small"} title={title} sx={STYLES.CHIP(maxWidth)} />
|
|
||||||
</Divider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Заголовок раздела редактора
|
|
||||||
P8PEditorSubHeader.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
paddingTop: PropTypes.number,
|
|
||||||
maxWidth: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PEditorSubHeader };
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Панель инструментов редактора
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты MUI
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Структура элемента панели инструментов редактора
|
|
||||||
const P8P_EDITOR_TOOL_BAR_ITEM_SHAPE = PropTypes.shape({
|
|
||||||
icon: PropTypes.string.isRequired,
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
onClick: PropTypes.func.isRequired
|
|
||||||
});
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Панель инструментов редактора
|
|
||||||
const P8PEditorToolBar = ({ items = [] }) => {
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Stack direction={"row"} p={1}>
|
|
||||||
{items.map((item, i) => (
|
|
||||||
<IconButton key={i} onClick={item.onClick} title={item.title} disabled={item?.disabled === true}>
|
|
||||||
<Icon>{item.icon}</Icon>
|
|
||||||
</IconButton>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Панель инструментов редактора
|
|
||||||
P8PEditorToolBar.propTypes = {
|
|
||||||
items: PropTypes.arrayOf(P8P_EDITOR_TOOL_BAR_ITEM_SHAPE)
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PEditorToolBar };
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Общие ресурсы редакторов
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CHIP: (fullWidth = false, multiLine = false) => ({
|
|
||||||
...(multiLine ? { height: "auto" } : {}),
|
|
||||||
"& .MuiChip-label": {
|
|
||||||
...(multiLine
|
|
||||||
? {
|
|
||||||
display: "block",
|
|
||||||
whiteSpace: "normal"
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
...(fullWidth ? { width: "100%" } : {})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { STYLES };
|
|
||||||
@ -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
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,14 +37,13 @@ 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({
|
||||||
@ -54,9 +53,7 @@ const P8PChart = ({ type, title, legendPosition, options = {}, labels = [], data
|
|||||||
? 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
|
||||||
|
|||||||
@ -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 };
|
|
||||||
@ -36,17 +36,15 @@ const P8P_DATA_GRID_FILTERS_HEIGHT = P8P_TABLE_FILTERS_HEIGHT;
|
|||||||
|
|
||||||
//Таблица данных
|
//Таблица данных
|
||||||
const P8PDataGrid = ({
|
const P8PDataGrid = ({
|
||||||
style = {},
|
columnsDef,
|
||||||
tableStyle = {},
|
|
||||||
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,
|
||||||
@ -116,8 +114,6 @@ const P8PDataGrid = ({
|
|||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<P8PTable
|
<P8PTable
|
||||||
style={style}
|
|
||||||
tableStyle={tableStyle}
|
|
||||||
columnsDef={columnsDef}
|
columnsDef={columnsDef}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
@ -158,17 +154,15 @@ const P8PDataGrid = ({
|
|||||||
|
|
||||||
//Контроль свойств - Таблица данных
|
//Контроль свойств - Таблица данных
|
||||||
P8PDataGrid.propTypes = {
|
P8PDataGrid.propTypes = {
|
||||||
style: PropTypes.object,
|
columnsDef: PropTypes.array.isRequired,
|
||||||
tableStyle: PropTypes.object,
|
|
||||||
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,
|
||||||
|
|||||||
@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга
|
|
||||||
Компонент: Диалог
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { BUTTONS } from "../../app.text"; //Общие текстовые ресурсы
|
|
||||||
import { P8P_INPUT, P8PInput } from "./p8p_input"; //Поле ввода
|
|
||||||
import { APP_STYLES } from "../../app.styles"; //Типовые стили
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Типовая ширина диалога
|
|
||||||
const P8P_DIALOG_WIDTH = {
|
|
||||||
XS: "xs",
|
|
||||||
SM: "sm",
|
|
||||||
MD: "md",
|
|
||||||
LG: "lg",
|
|
||||||
XL: "xl"
|
|
||||||
};
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
SCROLL: display =>
|
|
||||||
display === true ? { overflow: "auto", ...APP_STYLES.SCROLL } : { overflow: "hidden", display: "flex", flexDirection: "column" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------------------
|
|
||||||
//Вспомогательные функции
|
|
||||||
//-----------------------
|
|
||||||
|
|
||||||
//Формирование объекта вида {ключ: значение} из текущего состояния элементов ввода формы
|
|
||||||
const buildFormValues = inputsState =>
|
|
||||||
inputsState.reduce((res, input) => ({ ...res, [input.name]: input.value == undefined ? null : input.value }), {});
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Диалог
|
|
||||||
const P8PDialog = ({
|
|
||||||
title,
|
|
||||||
width,
|
|
||||||
fullWidth,
|
|
||||||
inputs,
|
|
||||||
children,
|
|
||||||
okDisabled = false,
|
|
||||||
scrollContent = true,
|
|
||||||
onOk,
|
|
||||||
onCancel,
|
|
||||||
onClose,
|
|
||||||
onInputChange
|
|
||||||
}) => {
|
|
||||||
//Состояние элементов ввода диалога
|
|
||||||
const [inputsState, setInputsState] = useState([]);
|
|
||||||
|
|
||||||
//При изменении элемента ввода
|
|
||||||
const handleInputChange = (name, value) => {
|
|
||||||
//Если есть функция пересчета формы - вызовем её
|
|
||||||
const doNotChangeInputsState = onInputChange ? onInputChange(name, value, inputsState) : false;
|
|
||||||
//И ориентируясь на то, пересчитала ли она элементы ввода обновим собственное состояние.
|
|
||||||
//Если функция пересчета вернула "true", значит она пересчитала что-то, тогда новые настройки элементов придут через свойство inputs и будут обработаны в useEffect ниже.
|
|
||||||
//Следовательно, и нам здесь не надо состояние выставлять, т.к. всё будет перезаписано useEffectом.
|
|
||||||
if (!doNotChangeInputsState)
|
|
||||||
setInputsState(pv => pv.reduce((accum, cur) => [...accum, { ...cur, value: cur.name === name ? value : cur.value }], []));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При нажатии на "ОК" диалога
|
|
||||||
const handleOk = () => onOk && onOk(buildFormValues(inputsState));
|
|
||||||
|
|
||||||
//При нажатии на "Отмена" диалога
|
|
||||||
const handleCancel = () => onCancel && onCancel();
|
|
||||||
|
|
||||||
//При нажатии на "Закрыть" диалога
|
|
||||||
const handleClose = () => (onClose ? onClose() : onCancel ? onCancel() : null);
|
|
||||||
|
|
||||||
//При изменении полей для ввода
|
|
||||||
useEffect(() => {
|
|
||||||
if (inputs && Array.isArray(inputs) && inputs.length > 0) setInputsState(inputs.map(input => ({ ...input })));
|
|
||||||
}, [inputs]);
|
|
||||||
|
|
||||||
//Расчет объектного представления текущих значений формы
|
|
||||||
const formValues = buildFormValues(inputsState);
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Dialog onClose={handleClose} open {...{ ...(width ? { maxWidth: width } : {}), ...(fullWidth === true ? { fullWidth: true } : {}) }}>
|
|
||||||
<DialogTitle>{title}</DialogTitle>
|
|
||||||
<DialogContent sx={STYLES.SCROLL(scrollContent)}>
|
|
||||||
{inputsState.map((input, i) => (
|
|
||||||
<P8PInput key={i} {...input} formValues={formValues} onChange={handleInputChange} />
|
|
||||||
))}
|
|
||||||
|
|
||||||
{children}
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
{onOk && (
|
|
||||||
<Button disabled={okDisabled} onClick={handleOk}>
|
|
||||||
{BUTTONS.OK}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{onCancel && <Button onClick={handleCancel}>{BUTTONS.CANCEL}</Button>}
|
|
||||||
{onClose && <Button onClick={handleClose}>{BUTTONS.CLOSE}</Button>}
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Диалог
|
|
||||||
P8PDialog.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
width: PropTypes.oneOf(Object.values(P8P_DIALOG_WIDTH)),
|
|
||||||
fullWidth: PropTypes.bool,
|
|
||||||
inputs: PropTypes.arrayOf(PropTypes.shape(P8P_INPUT)),
|
|
||||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
|
||||||
okDisabled: PropTypes.bool,
|
|
||||||
scrollContent: PropTypes.bool,
|
|
||||||
onOk: PropTypes.func,
|
|
||||||
onCancel: PropTypes.func,
|
|
||||||
onClose: PropTypes.func,
|
|
||||||
onInputChange: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PDialog, P8P_DIALOG_WIDTH };
|
|
||||||
@ -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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
//Подключение библиотек
|
//Подключение библиотек
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback, useRef } from "react"; //Классы React
|
import React, { useEffect, useState, useCallback } from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@ -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,
|
||||||
@ -368,9 +364,6 @@ const P8PGantt = ({
|
|||||||
editTask: null
|
editTask: null
|
||||||
});
|
});
|
||||||
|
|
||||||
//Ссылки на DOM
|
|
||||||
const svgContainerRef = useRef(null);
|
|
||||||
|
|
||||||
//Отображение диаграммы
|
//Отображение диаграммы
|
||||||
const showGantt = useCallback(() => {
|
const showGantt = useCallback(() => {
|
||||||
if (!state.gantt) {
|
if (!state.gantt) {
|
||||||
@ -425,11 +418,6 @@ const P8PGantt = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [tasks]);
|
}, [tasks]);
|
||||||
|
|
||||||
//При подключении компонента к старице
|
|
||||||
useEffect(() => {
|
|
||||||
svgContainerRef.current.children[0].classList.add("scroll");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<div style={{ ...(containerStyle ? containerStyle : {}) }}>
|
<div style={{ ...(containerStyle ? containerStyle : {}) }}>
|
||||||
@ -470,7 +458,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}
|
||||||
@ -481,7 +468,7 @@ const P8PGantt = ({
|
|||||||
cancelBtnCaption={cancelTaskEditorBtnCaption}
|
cancelBtnCaption={cancelTaskEditorBtnCaption}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div style={STYLES.GANTT(state.noData, title, zoomBar)} ref={svgContainerRef}>
|
<div style={STYLES.GANTT(state.noData, title, zoomBar)}>
|
||||||
<svg id="__gantt__" width="100%"></svg>
|
<svg id="__gantt__" width="100%"></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -506,7 +493,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,
|
||||||
|
|||||||
@ -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 };
|
|
||||||
@ -1,135 +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, Autocomplete, TextField } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Формат свойств поля ввода
|
|
||||||
const P8P_INPUT = {
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.instanceOf(Date)]),
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
dictionary: PropTypes.func,
|
|
||||||
list: PropTypes.array,
|
|
||||||
type: PropTypes.string,
|
|
||||||
freeSolo: PropTypes.bool,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
formValues: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Поле ввода
|
|
||||||
const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSolo = false, disabled = false, formValues, ...other }) => {
|
|
||||||
//Значение и тип элемента
|
|
||||||
const [current, setCurrent] = useState({ type: undefined, value: "" });
|
|
||||||
|
|
||||||
//При получении нового значения или типа из вне
|
|
||||||
useEffect(() => setCurrent({ value, type }), [type, value]);
|
|
||||||
|
|
||||||
//Выбор значения из словаря
|
|
||||||
const handleDictionaryClick = () => dictionary && dictionary(formValues, res => (res ? res.map(i => handleChangeByName(i.name, i.value)) : null));
|
|
||||||
|
|
||||||
//Изменение значения элемента (по событию)
|
|
||||||
const handleChange = e => {
|
|
||||||
setCurrent(pv => ({ ...pv, value: e.target.value }));
|
|
||||||
if (onChange) onChange(e.target.name, e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Изменение значения элемента (по имени и значению)
|
|
||||||
const handleChangeByName = (targetName, value) => {
|
|
||||||
if (targetName === name) setCurrent(pv => ({ ...pv, value }));
|
|
||||||
if (onChange) onChange(targetName, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Box p={1}>
|
|
||||||
<FormControl variant={"standard"} fullWidth {...other}>
|
|
||||||
{list ? (
|
|
||||||
freeSolo ? (
|
|
||||||
<Autocomplete
|
|
||||||
id={name}
|
|
||||||
name={name}
|
|
||||||
freeSolo
|
|
||||||
disabled={disabled}
|
|
||||||
inputValue={current.value ? current.value : ""}
|
|
||||||
onChange={(event, newValue) => handleChangeByName(name, newValue)}
|
|
||||||
onInputChange={(event, newInputValue) => handleChangeByName(name, newInputValue)}
|
|
||||||
options={list}
|
|
||||||
renderInput={params => <TextField {...params} label={label} name={name} variant={"standard"} />}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<InputLabel id={`${name}Lable`} shrink>
|
|
||||||
{label}
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId={`${name}Lable`}
|
|
||||||
id={name}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={[undefined, null].includes(current.value) ? "" : current.value}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={disabled}
|
|
||||||
displayEmpty
|
|
||||||
>
|
|
||||||
{list.map((item, i) => (
|
|
||||||
<MenuItem key={i} value={[undefined, null].includes(item.value) ? "" : item.value}>
|
|
||||||
{item.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<InputLabel {...(current.type == "date" ? { shrink: true } : {})} htmlFor={name}>
|
|
||||||
{label}
|
|
||||||
</InputLabel>
|
|
||||||
<Input
|
|
||||||
id={name}
|
|
||||||
name={name}
|
|
||||||
value={current.value ? current.value : ""}
|
|
||||||
endAdornment={
|
|
||||||
dictionary ? (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton aria-label={`${name} select`} onClick={handleDictionaryClick} edge="end">
|
|
||||||
<Icon>list</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
{...(current.type ? { type: current.type } : {})}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Поле ввода
|
|
||||||
P8PInput.propTypes = P8P_INPUT;
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8P_INPUT, P8PInput };
|
|
||||||
@ -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}
|
||||||
>
|
>
|
||||||
|
<Stack sx={STYLES.DESKTOP_ITEM_STACK}>
|
||||||
<Icon sx={STYLES.DESKTOP_ITEM_ICON}>{panel.icon}</Icon>
|
<Icon sx={STYLES.DESKTOP_ITEM_ICON}>{panel.icon}</Icon>
|
||||||
<Typography sx={STYLES.DESKTOP_ITEM_CATION} variant="body1">
|
<Typography sx={STYLES.DESKTOP_ITEM_CATION} variant="body1">
|
||||||
{panel.caption}
|
{panel.caption}
|
||||||
</Typography>
|
</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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Контроль свойств - Меню панелей - рабочий стол
|
//Контроль свойств - Меню панелей - рабочий стол
|
||||||
|
|||||||
@ -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,18 +486,16 @@ P8PTableFiltersChips.propTypes = {
|
|||||||
|
|
||||||
//Таблица
|
//Таблица
|
||||||
const P8PTable = ({
|
const P8PTable = ({
|
||||||
style = {},
|
columnsDef,
|
||||||
tableStyle = {},
|
groups,
|
||||||
columnsDef = [],
|
rows,
|
||||||
groups = [],
|
|
||||||
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,
|
||||||
@ -511,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);
|
||||||
@ -680,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}
|
||||||
@ -711,7 +731,7 @@ const P8PTable = ({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<TableContainer component={containerComponent ? containerComponent : Paper} {...(containerComponentProps ? containerComponentProps : {})}>
|
<TableContainer component={containerComponent ? containerComponent : Paper} {...(containerComponentProps ? containerComponentProps : {})}>
|
||||||
<Table stickyHeader={fixedHeader} sx={{ ...STYLES.TABLE, ...(tableStyle || {}) }} size={size || P8P_TABLE_SIZE.MEDIUM}>
|
<Table stickyHeader={fixedHeader} sx={STYLES.TABLE} size={size || P8P_TABLE_SIZE.MEDIUM}>
|
||||||
<TableHead sx={fixedHeader ? STYLES.TABLE_HEAD_STICKY : {}}>
|
<TableHead sx={fixedHeader ? STYLES.TABLE_HEAD_STICKY : {}}>
|
||||||
{header.displayLevels.map((level, i) => (
|
{header.displayLevels.map((level, i) => (
|
||||||
<TableRow key={level}>
|
<TableRow key={level}>
|
||||||
@ -876,8 +896,6 @@ const P8PTable = ({
|
|||||||
|
|
||||||
//Контроль свойств - Таблица
|
//Контроль свойств - Таблица
|
||||||
P8PTable.propTypes = {
|
P8PTable.propTypes = {
|
||||||
style: PropTypes.object,
|
|
||||||
tableStyle: PropTypes.object,
|
|
||||||
columnsDef: PropTypes.arrayOf(
|
columnsDef: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
@ -913,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,
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 = useCallback(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,
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -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 });
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,17 +92,15 @@ 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 object2XML = (obj, builderOptions) => {
|
const object2Base64XML = (obj, builderOptions) => {
|
||||||
const builder = new XMLBuilder(builderOptions);
|
const builder = new XMLBuilder(builderOptions);
|
||||||
return builder.build(obj);
|
//onOrderChanged({ orders: btoa(ordersBuilder.build(newOrders)) });
|
||||||
|
return btoa(unescape(encodeURIComponent(builder.build(obj))));
|
||||||
};
|
};
|
||||||
|
|
||||||
//Конвертация объекта в Base64 XML
|
|
||||||
const object2Base64XML = (obj, builderOptions) => btoa(unescape(encodeURIComponent(object2XML(obj, builderOptions))));
|
|
||||||
|
|
||||||
//Конвертация XML в JSON
|
//Конвертация XML в JSON
|
||||||
const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attributeValueProcessor, useDefaultPatterns = true }) => {
|
const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attributeValueProcessor, useDefaultPatterns = true }) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -152,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,13 +160,10 @@ export {
|
|||||||
hasValue,
|
hasValue,
|
||||||
getDisplaySize,
|
getDisplaySize,
|
||||||
deepCopyObject,
|
deepCopyObject,
|
||||||
object2XML,
|
|
||||||
object2Base64XML,
|
object2Base64XML,
|
||||||
xml2JSON,
|
xml2JSON,
|
||||||
formatDateRF,
|
formatDateRF,
|
||||||
formatDateTimeRF,
|
|
||||||
formatDateJSONDateOnly,
|
formatDateJSONDateOnly,
|
||||||
formatNumberRFCurrency,
|
formatNumberRFCurrency,
|
||||||
formatErrorMessage,
|
|
||||||
genGUID
|
genGUID
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,297 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Панель мониторинга: Корневая панель доски задач
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback } from "react"; //Классы React
|
|
||||||
import { DragDropContext, Droppable } from "react-beautiful-dnd"; //Работа с drag&drop
|
|
||||||
import { Stack, Box, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { StatusCard } from "./components/status_card.js";
|
|
||||||
import { TaskDialog } from "./task_dialog.js"; //Компонент формы события
|
|
||||||
import { Filter } from "./filter.js"; //Компонент фильтров
|
|
||||||
import { useExtraData, useColorRules, useStatuses } from "./hooks/hooks.js"; //Вспомогательные хуки
|
|
||||||
import { useTasks } from "./hooks/tasks_hooks.js"; //Хук событий
|
|
||||||
import { useFilters } from "./hooks/filter_hooks.js"; //Вспомогательные хуки фильтра
|
|
||||||
import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания
|
|
||||||
import { SettingsDialog } from "./components/settings_dialog.js"; //Диалог дополнительных настроек
|
|
||||||
import { deepCopyObject } from "../../core/utils.js"; //Вспомогательные функции
|
|
||||||
import { COMMON_STYLES } from "./styles"; //Общие стили
|
|
||||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Высота фильтра
|
|
||||||
const FILTER_HEIGHT = "56px";
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CONTAINER: { width: "100%", padding: 0 },
|
|
||||||
BOX_FILTER: { display: "flex", alignItems: "center" },
|
|
||||||
ICON_BUTTON_SETTINGS: { marginLeft: "auto" },
|
|
||||||
STACK_STATUSES: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...COMMON_STYLES.SCROLL },
|
|
||||||
BOX_STATUSES: { position: "fixed", left: "8px", top: `calc(${APP_BAR_HEIGHT} + ${FILTER_HEIGHT})` }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Корневая панель доски задач
|
|
||||||
const ClntTaskBoard = () => {
|
|
||||||
//Состояние фильтров
|
|
||||||
const [filters, handleFiltersChange] = useFilters();
|
|
||||||
|
|
||||||
//Состояние текущего загруженного фильтра
|
|
||||||
const [filterTypeLoaded, setFilterTypeLoaded] = useState(filters.values.sType);
|
|
||||||
|
|
||||||
//Состояние вспомогательных диалогов
|
|
||||||
const [dialogsState, setDialogsState] = useState({
|
|
||||||
filterDialogIsOpen: filters.isSetByUser,
|
|
||||||
settingsDialogIsOpen: false,
|
|
||||||
noteDialog: { isOpen: false, callback: null },
|
|
||||||
taskDialogIsOpen: false
|
|
||||||
});
|
|
||||||
|
|
||||||
//Состояние сортировок
|
|
||||||
const [orders, setOrders] = useState([]);
|
|
||||||
|
|
||||||
//Состояние дополнительных данных
|
|
||||||
const [extraData, setExtraData, handleDocLinksLoad] = useExtraData(filters.values.sType);
|
|
||||||
|
|
||||||
//Состояние статусов событий
|
|
||||||
const [statuses, statusesState, setStatuses, setStatusesState] = useStatuses(filters.values.sType);
|
|
||||||
|
|
||||||
//Состояние пользовательских настроек заливки событий
|
|
||||||
const [colorRules, setColorRules] = useColorRules();
|
|
||||||
|
|
||||||
//Состояние событий
|
|
||||||
const [tasks, setTasks, onDragEnd] = useTasks(filters.values, orders);
|
|
||||||
|
|
||||||
//Состояние доступных маршрутов события
|
|
||||||
const [availableRoutes, setAvailableRoutes] = useState({ source: "", routes: [] });
|
|
||||||
|
|
||||||
//При открытии/закрытии диалога фильтра
|
|
||||||
const handleFilterOpen = isOpen => {
|
|
||||||
setDialogsState(pv => ({ ...pv, filterDialogIsOpen: isOpen }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При открытии/закрытии диалога дополнительных настроек
|
|
||||||
const handleSettingsOpen = () => setDialogsState(pv => ({ ...pv, settingsDialogIsOpen: !pv.settingsDialogIsOpen }));
|
|
||||||
|
|
||||||
//При открытии/закрытии диалога примечания
|
|
||||||
const handleNoteOpen = (cb = null) => {
|
|
||||||
setDialogsState(pv => ({ ...pv, noteDialog: { isOpen: !dialogsState.noteDialog.isOpen, callback: cb ? v => cb(v) : null } }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При открытии/закрытии диалога события
|
|
||||||
const handleTaskDialogOpen = () => setDialogsState(pv => ({ ...pv, taskDialogIsOpen: !dialogsState.taskDialogIsOpen }));
|
|
||||||
|
|
||||||
//При необходимости обновить дополнительные данные
|
|
||||||
const handleExtraDataReload = useCallback(() => {
|
|
||||||
setExtraData(pv => ({ ...pv, reload: true }));
|
|
||||||
}, [setExtraData]);
|
|
||||||
|
|
||||||
//При необходимости обновить информацию о событиях
|
|
||||||
const handleTasksReload = useCallback(
|
|
||||||
(bAccountsReload = true) => {
|
|
||||||
setTasks(pv => ({ ...pv, reload: true, accountsReload: bAccountsReload }));
|
|
||||||
},
|
|
||||||
[setTasks]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При необходимости обновить состояние статусов
|
|
||||||
const handleStatusesStateReload = useCallback(() => {
|
|
||||||
setStatusesState(pv => ({ ...pv, reload: true, sorted: false }));
|
|
||||||
}, [setStatusesState]);
|
|
||||||
|
|
||||||
//При изменении дополнительных настроек
|
|
||||||
const handleSettingsChange = (newSettings, statusesState) => {
|
|
||||||
setColorRules(pv => ({ ...pv, selectedColorRule: newSettings.selectedColorRule }));
|
|
||||||
setStatusesState({ ...statusesState, sorted: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении цвета карточки статуса
|
|
||||||
const handleSettingStatusColorChange = (changedStatus, newColor) => {
|
|
||||||
//Считываем массив статусов
|
|
||||||
let newStatuses = [...statuses];
|
|
||||||
//Изменяем цвет нужного статуса
|
|
||||||
newStatuses.find(status => status.ID === changedStatus.ID).color = newColor;
|
|
||||||
//Обновляем состояние
|
|
||||||
setStatuses([...newStatuses]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении сортировки
|
|
||||||
const handleOrderChanged = columnName => {
|
|
||||||
//Копируем состояние сортировки
|
|
||||||
let newOrders = deepCopyObject(orders);
|
|
||||||
//Находим сортируемую колонку
|
|
||||||
const orderedColumn = newOrders.find(o => o.name == columnName);
|
|
||||||
//Определяем направление сортировки
|
|
||||||
const newDirection = orderedColumn?.direction == "ASC" ? "DESC" : orderedColumn?.direction == "DESC" ? null : "ASC";
|
|
||||||
//Если сортировка отключается - очищаем информацию о сортировке
|
|
||||||
if (newDirection == null && orderedColumn) newOrders.splice(newOrders.indexOf(orderedColumn), 1);
|
|
||||||
//Если сортировки не было - устанавливаем
|
|
||||||
if (newDirection != null && !orderedColumn) newOrders.push({ name: columnName, direction: newDirection });
|
|
||||||
//Если сортировка была и не отключается - изменяем
|
|
||||||
if (newDirection != null && orderedColumn) orderedColumn.direction = newDirection;
|
|
||||||
//Устанавливаем новую сортировку
|
|
||||||
setOrders(newOrders);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При необходимости очистки доступных маршрутов события
|
|
||||||
const handleAvailableRoutesStateClear = () => {
|
|
||||||
setAvailableRoutes({ source: "", routes: [] });
|
|
||||||
};
|
|
||||||
|
|
||||||
//Проверка доступности карточки события
|
|
||||||
const isCardAvailable = code => {
|
|
||||||
return availableRoutes.source === code || availableRoutes.routes.find(r => r.SDESTINATION === code) || !availableRoutes.source ? true : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении фильтра
|
|
||||||
useEffect(() => {
|
|
||||||
//Если изменился тип
|
|
||||||
if (filters.loaded && filters.values.sType) {
|
|
||||||
//Если тип события изменился
|
|
||||||
if (filterTypeLoaded !== filters.values.sType) {
|
|
||||||
//Обновляем информацию о дополнительных данных
|
|
||||||
handleExtraDataReload();
|
|
||||||
//Обновляем информацию о статусах
|
|
||||||
handleStatusesStateReload();
|
|
||||||
//Обновляем текущий загруженный тип события
|
|
||||||
setFilterTypeLoaded(filters.values.sType);
|
|
||||||
}
|
|
||||||
//Обновляем информацию о событиях
|
|
||||||
handleTasksReload();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [filters.loaded, filters.values]);
|
|
||||||
|
|
||||||
//При изменении сортировки
|
|
||||||
useEffect(() => {
|
|
||||||
//Если есть все данные для загрузки событий
|
|
||||||
if (filters.loaded && filters.values.sType) {
|
|
||||||
//Обновляем информацию о событиях без обновления контрагентов
|
|
||||||
handleTasksReload(false);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [orders]);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Box sx={STYLES.CONTAINER}>
|
|
||||||
{dialogsState.settingsDialogIsOpen ? (
|
|
||||||
<SettingsDialog
|
|
||||||
initial={{ colorRules: colorRules, statusesState: statusesState }}
|
|
||||||
onSettingsChange={handleSettingsChange}
|
|
||||||
onClose={handleSettingsOpen}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{dialogsState.taskDialogIsOpen ? (
|
|
||||||
<TaskDialog taskType={filters.values.sType} onTasksReload={() => handleTasksReload(true)} onClose={handleTaskDialogOpen} />
|
|
||||||
) : null}
|
|
||||||
<Box sx={STYLES.BOX_FILTER}>
|
|
||||||
<Stack direction="row">
|
|
||||||
<Box>
|
|
||||||
<Stack direction="row" pl={1} pt={1}>
|
|
||||||
<IconButton onClick={handleTaskDialogOpen} title={"Добавить событие"}>
|
|
||||||
<Icon>add</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
<Filter
|
|
||||||
isFilterDialogOpen={dialogsState.filterDialogIsOpen}
|
|
||||||
filter={filters.values}
|
|
||||||
docLinks={extraData.docLinks}
|
|
||||||
selectedDocLink={filters.values.sDocLink ? extraData.docLinks.find(d => d.NRN === filters.values.sDocLink) : null}
|
|
||||||
onFilterChange={handleFiltersChange}
|
|
||||||
onDocLinksLoad={handleDocLinksLoad}
|
|
||||||
onFilterOpen={() => handleFilterOpen(true)}
|
|
||||||
onFilterClose={() => handleFilterOpen(false)}
|
|
||||||
onTasksReload={handleTasksReload}
|
|
||||||
orders={orders}
|
|
||||||
onOrderChanged={handleOrderChanged}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<IconButton title="Настройки" onClick={handleSettingsOpen} sx={STYLES.ICON_BUTTON_SETTINGS}>
|
|
||||||
<Icon>settings</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
{dialogsState.noteDialog.isOpen ? (
|
|
||||||
<NoteDialog noteTypes={extraData.noteTypes} onCallback={note => dialogsState.noteDialog.callback(note)} onClose={handleNoteOpen} />
|
|
||||||
) : null}
|
|
||||||
{filters.loaded && filters.values.sType && extraData.dataLoaded && tasks.loaded ? (
|
|
||||||
<DragDropContext
|
|
||||||
onDragStart={path => {
|
|
||||||
//Поиск кода текущего статуса задачи
|
|
||||||
let sourceCode = statuses.find(status => status.ID == path.source.droppableId).SEVNSTAT_CODE;
|
|
||||||
//Устанавливаем доступные маршруты события
|
|
||||||
setAvailableRoutes({ source: sourceCode, routes: [...extraData.evRoutes.filter(route => route.SSOURCE === sourceCode)] });
|
|
||||||
}}
|
|
||||||
onDragEnd={path => {
|
|
||||||
//Если есть статус назначения
|
|
||||||
if (path.destination) {
|
|
||||||
//Определяем мнемокод статуса назначения
|
|
||||||
let destCode = statuses.find(status => status.ID == path.destination.droppableId).SEVNSTAT_CODE;
|
|
||||||
//Переносим событие
|
|
||||||
onDragEnd({ path: path, eventPoints: extraData.evPoints, openNoteDialog: handleNoteOpen, destCode: destCode });
|
|
||||||
}
|
|
||||||
//Очищаем информацию о доступных маршрутах события
|
|
||||||
handleAvailableRoutesStateClear();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={STYLES.BOX_STATUSES}>
|
|
||||||
<Droppable droppableId="Statuses" type="droppableTask">
|
|
||||||
{provided => (
|
|
||||||
<div ref={provided.innerRef}>
|
|
||||||
<Stack direction="row" spacing={2} sx={STYLES.STACK_STATUSES}>
|
|
||||||
{statusesState.sorted
|
|
||||||
? statuses.map((status, index) => (
|
|
||||||
<div key={index}>
|
|
||||||
<Droppable
|
|
||||||
isDropDisabled={!isCardAvailable(status.SEVNSTAT_CODE)}
|
|
||||||
droppableId={status.ID.toString()}
|
|
||||||
>
|
|
||||||
{provided => (
|
|
||||||
<div ref={provided.innerRef}>
|
|
||||||
<StatusCard
|
|
||||||
tasks={tasks}
|
|
||||||
status={status}
|
|
||||||
statusTitle={status[statusesState.attr] || status.SEVNSTAT_NAME}
|
|
||||||
colorRules={colorRules}
|
|
||||||
extraData={extraData}
|
|
||||||
isCardAvailable={isCardAvailable}
|
|
||||||
onTasksReload={handleTasksReload}
|
|
||||||
onNoteDialogOpen={handleNoteOpen}
|
|
||||||
onStatusColorChange={handleSettingStatusColorChange}
|
|
||||||
placeholder={provided.placeholder}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Droppable>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</Stack>
|
|
||||||
{provided.placeholder}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Droppable>
|
|
||||||
</Box>
|
|
||||||
</DragDropContext>
|
|
||||||
) : null}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { ClntTaskBoard };
|
|
||||||
@ -1,174 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Кастомное поле ввода
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
HELPER_TEXT: { color: "red" },
|
|
||||||
SELECT_MENU: width => {
|
|
||||||
return { ...COMMON_STYLES.SCROLL, width: width ? width + 24 : null };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//---------------
|
|
||||||
//Тело компонента
|
|
||||||
//---------------
|
|
||||||
|
|
||||||
//Кастомное поле ввода
|
|
||||||
const CustomInputField = ({
|
|
||||||
elementCode,
|
|
||||||
elementValue,
|
|
||||||
labelText,
|
|
||||||
onChange,
|
|
||||||
required = false,
|
|
||||||
items = null,
|
|
||||||
emptyItem = null,
|
|
||||||
dictionary,
|
|
||||||
menuItemRender,
|
|
||||||
...other
|
|
||||||
}) => {
|
|
||||||
//Значение элемента
|
|
||||||
const [value, setValue] = useState(elementValue);
|
|
||||||
|
|
||||||
//Состояние элемента HTML (для оптимизации ширины MenuItems)
|
|
||||||
const [anchorEl, setAnchorEl] = useState();
|
|
||||||
|
|
||||||
//При открытии меню заливки событий
|
|
||||||
const handleMenuOpen = e => {
|
|
||||||
//Устанавливаем элемент меню
|
|
||||||
setAnchorEl(e.target);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При получении нового значения из вне
|
|
||||||
useEffect(() => {
|
|
||||||
setValue(elementValue);
|
|
||||||
}, [elementValue]);
|
|
||||||
|
|
||||||
//Изменение значения элемента
|
|
||||||
const handleChange = e => {
|
|
||||||
setValue(e.target.value);
|
|
||||||
if (onChange) onChange(e.target.name, e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Выбор значения из словаря
|
|
||||||
const handleDictionaryClick = () => {
|
|
||||||
dictionary ? dictionary(res => (res ? handleChange({ target: { name: elementCode, value: res } }) : null)) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация поля с выбором из словаря Парус
|
|
||||||
const renderInput = validationError => {
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
error={validationError}
|
|
||||||
id={elementCode}
|
|
||||||
name={elementCode}
|
|
||||||
value={value}
|
|
||||||
endAdornment={
|
|
||||||
dictionary ? (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
|
|
||||||
<Icon>list</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
aria-describedby={`${elementCode}-helper-text`}
|
|
||||||
label={labelText}
|
|
||||||
onChange={handleChange}
|
|
||||||
{...other}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация поля с выпадающим списком
|
|
||||||
const renderSelect = (items, anchorEl, handleMenuOpen, validationError) => {
|
|
||||||
//Формируем общий список элементов меню
|
|
||||||
const menuItems = emptyItem ? [emptyItem, ...items] : [...items];
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
error={validationError}
|
|
||||||
id={elementCode}
|
|
||||||
name={elementCode}
|
|
||||||
//!!!Пересмотреть момент. При изменении типа происходит ререндер со старым значением учетного документа:
|
|
||||||
//1. Изменяется тип
|
|
||||||
//2. Очищается items (список учетных документов)
|
|
||||||
//3. Рисуется компонент со старым value и пустым items, из-за чего ошибка "You have provided an out-of-range value"
|
|
||||||
//4. Вызывается useEffect, меняется значение value на новое (пустое значение)
|
|
||||||
value={value}
|
|
||||||
aria-describedby={`${elementCode}-helper-text`}
|
|
||||||
label={labelText}
|
|
||||||
MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU(anchorEl?.offsetWidth) } } }}
|
|
||||||
onChange={handleChange}
|
|
||||||
onOpen={handleMenuOpen}
|
|
||||||
{...other}
|
|
||||||
>
|
|
||||||
{menuItems
|
|
||||||
? menuItems.map((item, index) => {
|
|
||||||
let customRender = null;
|
|
||||||
if (menuItemRender) customRender = menuItemRender({ item: item, key: item?.key ?? index }) || null;
|
|
||||||
return customRender ? (
|
|
||||||
customRender
|
|
||||||
) : (
|
|
||||||
<MenuItem key={item?.key ?? index} value={item.id}>
|
|
||||||
<Typography variant="inherit" noWrap title={item.caption} component="div">
|
|
||||||
{item.caption}
|
|
||||||
</Typography>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Признак ошибки валидации
|
|
||||||
const validationError = !value && required ? true : false;
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<FormControl fullWidth variant="standard">
|
|
||||||
<InputLabel htmlFor={elementCode}>{labelText}</InputLabel>
|
|
||||||
{items ? renderSelect(items, anchorEl, handleMenuOpen, validationError) : renderInput(validationError)}
|
|
||||||
{validationError ? (
|
|
||||||
<FormHelperText id={`${elementCode}-helper-text`} sx={STYLES.HELPER_TEXT}>
|
|
||||||
*Обязательное поле
|
|
||||||
</FormHelperText>
|
|
||||||
) : null}
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Кастомное поле ввода
|
|
||||||
CustomInputField.propTypes = {
|
|
||||||
elementCode: PropTypes.string.isRequired,
|
|
||||||
elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
||||||
labelText: PropTypes.string.isRequired,
|
|
||||||
required: PropTypes.bool,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
emptyItem: PropTypes.object,
|
|
||||||
dictionary: PropTypes.func,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
menuItemRender: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------
|
|
||||||
//Интерфейс компонента
|
|
||||||
//--------------------
|
|
||||||
|
|
||||||
export { CustomInputField };
|
|
||||||
@ -1,336 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Диалог фильтра отбора
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
|
||||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogTitle,
|
|
||||||
IconButton,
|
|
||||||
Icon,
|
|
||||||
DialogContent,
|
|
||||||
DialogActions,
|
|
||||||
Button,
|
|
||||||
Box,
|
|
||||||
Stack,
|
|
||||||
Checkbox,
|
|
||||||
FormControlLabel,
|
|
||||||
Radio,
|
|
||||||
RadioGroup
|
|
||||||
} from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { CustomInputField } from "./custom_input_field"; //Кастомное поле ввода
|
|
||||||
import { hasValue } from "../../../core/utils"; //Вспомогательные функции
|
|
||||||
import { EVENT_STATES } from "../layouts"; //Перечисление состояний события
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
SELECT: { width: "450px" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//---------------
|
|
||||||
//Тело компонента
|
|
||||||
//---------------
|
|
||||||
|
|
||||||
//Диалог фильтра отбора
|
|
||||||
const FilterDialog = ({ initial, onFilterChange, onFilterClose, onDocLinksLoad }) => {
|
|
||||||
//Собственное состояние
|
|
||||||
const [filter, setFilter] = useState(initial.filter);
|
|
||||||
|
|
||||||
//Состояние текущих учётных документов
|
|
||||||
const [curDocLinks, setCurDocLinks] = useState({ loaded: true, docLinks: initial.docLinks });
|
|
||||||
|
|
||||||
//Вспомогательные функции открытия раздела
|
|
||||||
const { handleCatalogTreeOpen, handleEventTypesOpen, handleAgnlistOpen, handleInsDepartmentOpen, handleCostStaffGroupsOpen } = useDictionary();
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//При изменении типа события фильтра
|
|
||||||
const handleTypeChange = callBack =>
|
|
||||||
handleEventTypesOpen({
|
|
||||||
sCode: filter.sType,
|
|
||||||
callBack: res => {
|
|
||||||
callBack(res.outParameters.eventtypecode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//При изменении каталога фильтра
|
|
||||||
const handleCrnChange = callBack =>
|
|
||||||
handleCatalogTreeOpen({
|
|
||||||
sUnitName: "ClientEvents",
|
|
||||||
sName: filter.sCrnName,
|
|
||||||
callBack: res => {
|
|
||||||
callBack(res.outParameters.out_NAME);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//При изменении исполнителя фильтра
|
|
||||||
const handleSendPersonChange = callBack =>
|
|
||||||
handleAgnlistOpen({
|
|
||||||
sMnemo: filter.sSendPerson,
|
|
||||||
callBack: res => {
|
|
||||||
callBack(res.outParameters.agnmnemo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//При изменении подразделения фильтра
|
|
||||||
const handleSendDivisionChange = callBack =>
|
|
||||||
handleInsDepartmentOpen({
|
|
||||||
sCode: filter.sSendDivision,
|
|
||||||
callBack: res => {
|
|
||||||
callBack(res.outParameters.out_CODE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//При изменении группы пользователей фильтра
|
|
||||||
const handleSendUsrGrpChange = callBack =>
|
|
||||||
handleCostStaffGroupsOpen({
|
|
||||||
sCode: filter.sSendUsrGrp,
|
|
||||||
callBack: res => {
|
|
||||||
callBack(res.outParameters.out_CODE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Считывание подкаталогов
|
|
||||||
const getSubCatalogs = useCallback(async () => {
|
|
||||||
//Считываем каталоги
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET",
|
|
||||||
args: {
|
|
||||||
SCRN_NAME: filter.sCrnName,
|
|
||||||
NSUBCAT: filter.bSubcatalogs ? 1 : 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//Возвращаем список каталогов
|
|
||||||
return data.SRESULT;
|
|
||||||
}, [executeStored, filter.sCrnName, filter.bSubcatalogs]);
|
|
||||||
|
|
||||||
//При закрытии диалога с изменением фильтра
|
|
||||||
const handleDialogOk = async () => {
|
|
||||||
//Если указано имя каталога, но не загружен список рег. номеров
|
|
||||||
if (filter.sCrnName && !filter.sCrnRnList) {
|
|
||||||
//Загружаем список рег. номеров каталогов
|
|
||||||
const crns = await getSubCatalogs();
|
|
||||||
//Устанавливаем новый фильтр
|
|
||||||
onFilterChange({ ...filter, ...(crns ? { sCrnRnList: crns } : {}) });
|
|
||||||
//Закрываем диалог фильтра
|
|
||||||
onFilterClose();
|
|
||||||
} else {
|
|
||||||
//Устанавливаем новый фильтр
|
|
||||||
onFilterChange(filter);
|
|
||||||
//Закрываем диалог фильтра
|
|
||||||
onFilterClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//При очистке фильтра
|
|
||||||
const handleFilterClear = () => {
|
|
||||||
setFilter({
|
|
||||||
sState: EVENT_STATES[1],
|
|
||||||
sType: "",
|
|
||||||
sCrnName: "",
|
|
||||||
sCrnRnList: "",
|
|
||||||
bSubcatalogs: false,
|
|
||||||
sSendPerson: "",
|
|
||||||
sSendDivision: "",
|
|
||||||
sSendUsrGrp: "",
|
|
||||||
sDocLink: ""
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении значения элемента
|
|
||||||
const handleFilterItemChange = (item, value) => {
|
|
||||||
//Если это изменение типа
|
|
||||||
if (item === "sType") {
|
|
||||||
//Указываем тип с очисткой информации об учетных документах
|
|
||||||
setFilter(pv => ({ ...pv, [item]: value, sDocLink: "" }));
|
|
||||||
setCurDocLinks(pv => ({ ...pv, loaded: false, docLinks: [] }));
|
|
||||||
} else {
|
|
||||||
//Обновляем значение поля
|
|
||||||
setFilter(pv => ({ ...pv, [item]: value }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//При очистке учётного документа
|
|
||||||
const handleDocLinkClear = () => setFilter(pv => ({ ...pv, sDocLink: "" }));
|
|
||||||
|
|
||||||
//Обработка изменений с каталогами
|
|
||||||
useEffect(() => {
|
|
||||||
//Если каталог не указан, но галка подкаталогов установлена - снимаем её
|
|
||||||
if (!filter.sCrnName && filter.bSubcatalogs) setFilter(pv => ({ ...pv, bSubcatalogs: false }));
|
|
||||||
//Если изменился каталог и остался список рег. номеров каталогов - очищаем его
|
|
||||||
if (filter.sCrnName !== initial.sCrnName && filter.sCrnRnList) setFilter(pv => ({ ...pv, sCrnRnList: "" }));
|
|
||||||
//Если каталог равен изначальному
|
|
||||||
if (filter.sCrnName === initial.sCrnName) {
|
|
||||||
//Если признак подкаталогов равен изначальному, но список рег. номеров каталогов не соответствует - загружаем изначальный
|
|
||||||
if (filter.bSubcatalogs === initial.bSubcatalogs && filter.sCrnRnList !== initial.sCrnRnList) {
|
|
||||||
setFilter(pv => ({ ...pv, sCrnRnList: initial.sCrnRnList }));
|
|
||||||
}
|
|
||||||
//Если признак подкаталогов не равен изначальному
|
|
||||||
if (filter.bSubcatalogs !== initial.bSubcatalogs) {
|
|
||||||
//Если не установлен - считываем первый из списка рег. номеров изначальных каталогов
|
|
||||||
if (!filter.bSubcatalogs) {
|
|
||||||
setFilter(pv => ({
|
|
||||||
...pv,
|
|
||||||
sCrnRnList: initial.sCrnRnList.split(";")[0]
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
//Если установлен - очищаем список рег. номеров каталогов для последующей загрузки
|
|
||||||
setFilter(pv => ({ ...pv, sCrnRnList: "" }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [filter.sCrnName, filter.sCrnRnList, filter.bSubcatalogs, initial.sCrnName, initial.sCrnRnList, initial.bSubcatalogs]);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Dialog open onClose={onFilterClose} fullWidth maxWidth="sm">
|
|
||||||
<DialogTitle>Фильтр отбора</DialogTitle>
|
|
||||||
<IconButton aria-label="close" onClick={onFilterClose} sx={COMMON_STYLES.DIALOG_CLOSE_BUTTON}>
|
|
||||||
<Icon>close</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<DialogContent sx={COMMON_STYLES.SCROLL}>
|
|
||||||
<Box sx={COMMON_STYLES.BOX_WITH_LEGEND} component="fieldset">
|
|
||||||
<legend style={COMMON_STYLES.LEGEND}>Состояние</legend>
|
|
||||||
<RadioGroup
|
|
||||||
row
|
|
||||||
aria-labelledby="sState-label"
|
|
||||||
id="sState"
|
|
||||||
name="sState"
|
|
||||||
value={filter.sState}
|
|
||||||
onChange={e => handleFilterItemChange(e.target.name, e.target.value)}
|
|
||||||
>
|
|
||||||
{Object.keys(EVENT_STATES).map(function (k) {
|
|
||||||
return <FormControlLabel key={k} value={EVENT_STATES[k]} control={<Radio />} label={EVENT_STATES[k]} />;
|
|
||||||
})}
|
|
||||||
</RadioGroup>
|
|
||||||
</Box>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="sType"
|
|
||||||
elementValue={filter.sType}
|
|
||||||
labelText="Тип"
|
|
||||||
dictionary={callBack => handleTypeChange(callBack)}
|
|
||||||
onChange={handleFilterItemChange}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="sCrnName"
|
|
||||||
elementValue={filter.sCrnName}
|
|
||||||
labelText="Каталог"
|
|
||||||
dictionary={callBack => handleCrnChange(callBack)}
|
|
||||||
onChange={handleFilterItemChange}
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
id="bSubcatalogs"
|
|
||||||
name="bSubcatalogs"
|
|
||||||
checked={filter.bSubcatalogs}
|
|
||||||
disabled={filter.sCrnName ? false : true}
|
|
||||||
onChange={e => handleFilterItemChange(e.target.name, e.target.checked)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Включая подкаталоги"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="sSendPerson"
|
|
||||||
elementValue={filter.sSendPerson}
|
|
||||||
labelText="Исполнитель"
|
|
||||||
dictionary={callBack => handleSendPersonChange(callBack)}
|
|
||||||
onChange={handleFilterItemChange}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="sSendDivision"
|
|
||||||
elementValue={filter.sSendDivision}
|
|
||||||
labelText="Подразделение"
|
|
||||||
dictionary={callBack => handleSendDivisionChange(callBack)}
|
|
||||||
onChange={handleFilterItemChange}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="sSendUsrGrp"
|
|
||||||
elementValue={filter.sSendUsrGrp}
|
|
||||||
labelText="Группа пользователей"
|
|
||||||
dictionary={callBack => handleSendUsrGrpChange(callBack)}
|
|
||||||
onChange={handleFilterItemChange}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<Stack direction="row" sx={COMMON_STYLES.STACK_DOCLINKS}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="sDocLink"
|
|
||||||
elementValue={filter.sDocLink}
|
|
||||||
labelText="Учётный документ"
|
|
||||||
items={[...(curDocLinks.docLinks || [])].reduce((prev, cur) => [...prev, { id: cur.NRN, caption: cur.SDESCR }], [])}
|
|
||||||
disabled={!curDocLinks.docLinks.length ? true : false}
|
|
||||||
onChange={handleFilterItemChange}
|
|
||||||
sx={STYLES.SELECT}
|
|
||||||
/>
|
|
||||||
<IconButton title="Очистить" disabled={!filter.sDocLink} onClick={handleDocLinkClear}>
|
|
||||||
<Icon>clear</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
title="Обновить"
|
|
||||||
disabled={curDocLinks.loaded}
|
|
||||||
onClick={() => {
|
|
||||||
//Очищаем учетный документ
|
|
||||||
handleDocLinkClear();
|
|
||||||
//Загружаем учетные документы типа
|
|
||||||
onDocLinksLoad(filter.sType).then(dl => setCurDocLinks(pv => ({ ...pv, loaded: true, docLinks: dl })));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon>refresh</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions sx={COMMON_STYLES.DIALOG_ACTIONS}>
|
|
||||||
<Button disabled={!hasValue(filter.sType)} variant="text" onClick={handleDialogOk}>
|
|
||||||
ОК
|
|
||||||
</Button>
|
|
||||||
<Button variant="text" onClick={handleFilterClear}>
|
|
||||||
Очистить
|
|
||||||
</Button>
|
|
||||||
<Button variant="text" onClick={onFilterClose}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Диалог фильтра отбора
|
|
||||||
FilterDialog.propTypes = {
|
|
||||||
initial: PropTypes.object.isRequired,
|
|
||||||
onFilterChange: PropTypes.func.isRequired,
|
|
||||||
onFilterClose: PropTypes.func.isRequired,
|
|
||||||
onDocLinksLoad: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------
|
|
||||||
//Интерфейс компонента
|
|
||||||
//--------------------
|
|
||||||
|
|
||||||
export { FilterDialog };
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Диалог примечания
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, TextField } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
DIALOG_CONTENT: { paddingTop: 0, paddingBottom: 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
//---------------
|
|
||||||
//Тело компонента
|
|
||||||
//---------------
|
|
||||||
|
|
||||||
//Диалог примечания
|
|
||||||
const NoteDialog = ({ noteTypes, onCallback, onClose }) => {
|
|
||||||
//Собственное состояние
|
|
||||||
const [note, setNote] = useState({ noteTypeIndex: 0, text: "" });
|
|
||||||
|
|
||||||
//При изменении примечания
|
|
||||||
const handleNoteChange = value => setNote(pv => ({ ...pv, text: value }));
|
|
||||||
|
|
||||||
//При изменении заголовка примечания
|
|
||||||
const handleNoteHeaderChange = (name, value) => {
|
|
||||||
setNote(pv => ({ ...pv, noteTypeIndex: value }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При закрытии диалога с изменением примечания
|
|
||||||
const handleDialogOk = () => {
|
|
||||||
//Передаем информацию о примечание в callback
|
|
||||||
onCallback({ header: noteTypes[note.noteTypeIndex], text: note.text });
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Dialog open onClose={onClose} fullWidth maxWidth="sm">
|
|
||||||
<DialogTitle>Примечание</DialogTitle>
|
|
||||||
<IconButton aria-label="close" onClick={onClose} sx={COMMON_STYLES.DIALOG_CLOSE_BUTTON}>
|
|
||||||
<Icon>close</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="noteHeader"
|
|
||||||
elementValue={note.noteTypeIndex}
|
|
||||||
labelText="Заголовок примечания"
|
|
||||||
items={noteTypes.reduce((prev, cur) => [...prev, { id: prev.length, caption: cur }], [])}
|
|
||||||
onChange={handleNoteHeaderChange}
|
|
||||||
margin="dense"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="note"
|
|
||||||
label="Описание"
|
|
||||||
variant="standard"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
multiline
|
|
||||||
minRows={7}
|
|
||||||
maxRows={7}
|
|
||||||
value={note.text}
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{ sx: COMMON_STYLES.SCROLL }}
|
|
||||||
onChange={e => handleNoteChange(e.target.value)}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions sx={COMMON_STYLES.DIALOG_ACTIONS}>
|
|
||||||
<Button disabled={!note.text} variant="text" onClick={handleDialogOk}>
|
|
||||||
ОК
|
|
||||||
</Button>
|
|
||||||
<Button variant="text" onClick={onClose}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Диалог примечания
|
|
||||||
NoteDialog.propTypes = {
|
|
||||||
noteTypes: PropTypes.array,
|
|
||||||
onCallback: PropTypes.func.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { NoteDialog };
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Диалог дополнительных настроек
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Icon, Button, Box, Stack, Typography } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода
|
|
||||||
import { sortAttrs, sortDest } from "../layouts.js"; //Допустимые значение поля и направления сортировки
|
|
||||||
import { hasValue } from "../../../core/utils.js"; //Проверка наличия значения
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
SELECT: { width: "100%" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//---------------
|
|
||||||
//Тело компонента
|
|
||||||
//---------------
|
|
||||||
|
|
||||||
//Диалог дополнительных настроек
|
|
||||||
const SettingsDialog = ({ initial, onSettingsChange, onClose, ...other }) => {
|
|
||||||
//Состояние дополнительных настроек
|
|
||||||
const [colorRules, seColorRules] = useState(initial.colorRules);
|
|
||||||
|
|
||||||
//Состояние статусов
|
|
||||||
const [statusesState, setStatusesState] = useState(initial.statusesState);
|
|
||||||
|
|
||||||
//Изменение поля сортировки
|
|
||||||
const handleSortAttrChange = (item, value) => setStatusesState(pv => ({ ...pv, [item]: value }));
|
|
||||||
|
|
||||||
//Изменение направления сортировки
|
|
||||||
const handleSortDestChange = newDirection => setStatusesState(pv => ({ ...pv, direction: newDirection }));
|
|
||||||
|
|
||||||
//При изменении правила заливки событий
|
|
||||||
const handleColorRuleChange = (item, value) => {
|
|
||||||
//Определяем новое правило заливки
|
|
||||||
let newColorRule = colorRules.rules[value];
|
|
||||||
//Обновляем в основных настройках
|
|
||||||
seColorRules(pv => ({ ...pv, selectedColorRule: newColorRule ? newColorRule : {} }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<div {...other}>
|
|
||||||
<Dialog open onClose={onClose} fullWidth maxWidth="sm">
|
|
||||||
<DialogTitle>Настройки</DialogTitle>
|
|
||||||
<IconButton aria-label="close" onClick={onClose} sx={COMMON_STYLES.DIALOG_CLOSE_BUTTON}>
|
|
||||||
<Icon>close</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<DialogContent sx={COMMON_STYLES.SCROLL}>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="clrRules"
|
|
||||||
elementValue={hasValue(colorRules.selectedColorRule.id) && colorRules.length !== 0 ? colorRules.selectedColorRule.id : -1}
|
|
||||||
labelText="Заливка событий*"
|
|
||||||
items={colorRules.rules.reduce(
|
|
||||||
(prev, cur) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
id: cur.id,
|
|
||||||
caption:
|
|
||||||
`${cur.SDP_NAME}` +
|
|
||||||
(cur.STYPE == "string"
|
|
||||||
? `${cur.fromValue ? `, значение "${cur.fromValue}"` : ""}`
|
|
||||||
: `${cur.fromValue ? `, с ${cur.fromValue}` : ""}` + `${cur.toValue ? `, по ${cur.toValue}` : ""}`) +
|
|
||||||
`${cur.SCOLOR ? `, ${cur.SCOLOR}` : ""}`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
)}
|
|
||||||
emptyItem={{ key: -1, id: -1, caption: "Нет" }}
|
|
||||||
onChange={handleColorRuleChange}
|
|
||||||
sx={STYLES.SELECT}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<Stack direction="row" sx={COMMON_STYLES.STACK_DOCLINKS}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="attr"
|
|
||||||
elementValue={statusesState.attr}
|
|
||||||
labelText="Порядок сортировки колонок"
|
|
||||||
items={sortAttrs.reduce((prev, cur) => [...prev, { id: cur.id, caption: cur.descr }], [])}
|
|
||||||
onChange={handleSortAttrChange}
|
|
||||||
sx={STYLES.SELECT}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
title={statusesState.direction === "asc" ? "По возрастанию" : "По убыванию"}
|
|
||||||
onClick={() => handleSortDestChange(sortDest[sortDest.indexOf(statusesState.direction) * -1])}
|
|
||||||
>
|
|
||||||
<Icon>{statusesState.direction === "asc" ? "arrow_upward" : "arrow_downward"}</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
<Typography variant={"caption"}>
|
|
||||||
*Поддерживаются правила заливки, базирующиеся на дополнительных свойствах типа "Строка" или "Число", из
|
|
||||||
профиля пользователя, настроенного для раздела "События" в WEB-интерфейсе данного приложения.
|
|
||||||
</Typography>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions sx={COMMON_STYLES.DIALOG_ACTIONS}>
|
|
||||||
<Button
|
|
||||||
variant="text"
|
|
||||||
onClick={() => {
|
|
||||||
onSettingsChange(colorRules, statusesState);
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
ОК
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="text"
|
|
||||||
onClick={() => {
|
|
||||||
seColorRules(pv => ({ ...pv, selectedColorRule: {} }));
|
|
||||||
setStatusesState(pv => ({ ...pv, attr: "SEVNSTAT_NAME", direction: "asc" }));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Очистить
|
|
||||||
</Button>
|
|
||||||
<Button variant="text" onClick={onClose}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Диалог дополнительных настроек
|
|
||||||
SettingsDialog.propTypes = {
|
|
||||||
initial: PropTypes.object.isRequired,
|
|
||||||
onSettingsChange: PropTypes.func.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------
|
|
||||||
//Интерфейс компонента
|
|
||||||
//--------------------
|
|
||||||
|
|
||||||
export { SettingsDialog };
|
|
||||||
@ -1,166 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Карточка статуса событий
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Card, CardHeader, CardContent, IconButton, Icon, Typography, Stack } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { TaskCard } from "./task_card.js"; //Компонент Карточка события
|
|
||||||
import { StatusCardSettings } from "./status_card_settings.js"; //Компонент Диалог настройки карточки событий
|
|
||||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
|
||||||
import { COLORS } from "../layouts.js"; //Цвета статусов
|
|
||||||
import { APP_BAR_HEIGHT } from "../../../components/p8p_app_workspace"; //Заголовок страницы
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Нижний отступ заголовка
|
|
||||||
const TITLE_PADDING_BOTTOM = "16px";
|
|
||||||
|
|
||||||
//Высота фильтра
|
|
||||||
const FILTER_HEIGHT = "56px";
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
STATUS_BLOCK: statusColor => {
|
|
||||||
return {
|
|
||||||
width: "350px",
|
|
||||||
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`,
|
|
||||||
backgroundColor: statusColor,
|
|
||||||
padding: "8px"
|
|
||||||
};
|
|
||||||
},
|
|
||||||
BLOCK_OPACITY: isAvailable => {
|
|
||||||
return isAvailable ? { opacity: 1 } : { opacity: 0.5 };
|
|
||||||
},
|
|
||||||
CARD_HEADER_TITLE: {
|
|
||||||
textAlign: "left",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
overflow: "hidden",
|
|
||||||
display: "-webkit-box",
|
|
||||||
hyphens: "auto",
|
|
||||||
WebkitBoxOrient: "vertical",
|
|
||||||
WebkitLineClamp: 1,
|
|
||||||
maxWidth: "calc(300px)",
|
|
||||||
width: "-webkit-fill-available",
|
|
||||||
fontSize: "1.2rem",
|
|
||||||
cursor: "default"
|
|
||||||
},
|
|
||||||
CARD_HEADER: { padding: 0 },
|
|
||||||
CARD_CONTENT: {
|
|
||||||
padding: 0,
|
|
||||||
paddingRight: "5px",
|
|
||||||
paddingBottom: "5px !important",
|
|
||||||
overflowY: "auto",
|
|
||||||
maxHeight: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 55px)`,
|
|
||||||
...APP_STYLES.SCROLL
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//---------------
|
|
||||||
//Тело компонента
|
|
||||||
//---------------
|
|
||||||
|
|
||||||
//Карточка статуса события
|
|
||||||
const StatusCard = ({
|
|
||||||
tasks,
|
|
||||||
status,
|
|
||||||
statusTitle,
|
|
||||||
colorRules,
|
|
||||||
extraData,
|
|
||||||
isCardAvailable,
|
|
||||||
onTasksReload,
|
|
||||||
onNoteDialogOpen,
|
|
||||||
onStatusColorChange,
|
|
||||||
placeholder
|
|
||||||
}) => {
|
|
||||||
//Состояние диалога настройки
|
|
||||||
const [statusCardSettingsOpen, setStatusCardSettingsOpen] = useState(false);
|
|
||||||
|
|
||||||
//Открыть/закрыть диалог настройки
|
|
||||||
const handleStatusCardSettingsOpen = () => setStatusCardSettingsOpen(!statusCardSettingsOpen);
|
|
||||||
|
|
||||||
//При изменении цвета статуса
|
|
||||||
const handleStatusColorChange = newColor => {
|
|
||||||
onStatusColorChange(status, newColor);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{statusCardSettingsOpen ? (
|
|
||||||
<StatusCardSettings
|
|
||||||
statusColor={status.color}
|
|
||||||
availableColors={COLORS.includes(status.color) ? COLORS : [status.color, ...COLORS]}
|
|
||||||
onClose={handleStatusCardSettingsOpen}
|
|
||||||
onColorChange={handleStatusColorChange}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Card
|
|
||||||
className="statusId-card"
|
|
||||||
sx={{
|
|
||||||
...STYLES.STATUS_BLOCK(status.color),
|
|
||||||
...STYLES.BLOCK_OPACITY(isCardAvailable(status.SEVNSTAT_CODE))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CardHeader
|
|
||||||
action={
|
|
||||||
<IconButton aria-label="settings" onClick={handleStatusCardSettingsOpen}>
|
|
||||||
<Icon>more_vert</Icon>
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
title={
|
|
||||||
<Typography sx={STYLES.CARD_HEADER_TITLE} title={statusTitle} variant="h5">
|
|
||||||
{statusTitle}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
sx={STYLES.CARD_HEADER}
|
|
||||||
/>
|
|
||||||
<CardContent sx={STYLES.CARD_CONTENT}>
|
|
||||||
<Stack spacing={1}>
|
|
||||||
{tasks.rows
|
|
||||||
.filter(item => item.sStatus === status.SEVNSTAT_NAME)
|
|
||||||
.map((item, index) => (
|
|
||||||
<TaskCard
|
|
||||||
task={item}
|
|
||||||
index={index}
|
|
||||||
onTasksReload={onTasksReload}
|
|
||||||
key={item.id}
|
|
||||||
colorRule={colorRules.selectedColorRule}
|
|
||||||
pointSettings={extraData.evPoints.find(p => p.SEVPOINT === status.SEVNSTAT_CODE)}
|
|
||||||
onOpenNoteDialog={onNoteDialogOpen}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{placeholder}
|
|
||||||
</Stack>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Карточка статуса события
|
|
||||||
StatusCard.propTypes = {
|
|
||||||
tasks: PropTypes.object.isRequired,
|
|
||||||
status: PropTypes.object.isRequired,
|
|
||||||
statusTitle: PropTypes.string.isRequired,
|
|
||||||
colorRules: PropTypes.object.isRequired,
|
|
||||||
extraData: PropTypes.object.isRequired,
|
|
||||||
isCardAvailable: PropTypes.func.isRequired,
|
|
||||||
onTasksReload: PropTypes.func.isRequired,
|
|
||||||
onNoteDialogOpen: PropTypes.func.isRequired,
|
|
||||||
onStatusColorChange: PropTypes.func.isRequired,
|
|
||||||
placeholder: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------
|
|
||||||
//Интерфейс компонента
|
|
||||||
//--------------------
|
|
||||||
|
|
||||||
export { StatusCard };
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Диалог настройки карточки статуса
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------------------
|
|
||||||
//Вспомогательные классы и функции
|
|
||||||
//--------------------------------
|
|
||||||
|
|
||||||
//Генерация элемента меню
|
|
||||||
const menuItemRender = ({ item, key }) => {
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<MenuItem key={key} value={item.id} sx={STYLES.BCKG_COLOR(item.caption)}>
|
|
||||||
<Typography variant="inherit" noWrap title={item.caption} component="div">
|
|
||||||
{item.caption}
|
|
||||||
</Typography>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//---------------
|
|
||||||
//Тело компонента
|
|
||||||
//---------------
|
|
||||||
|
|
||||||
//Диалог настройки карточки статуса
|
|
||||||
const StatusCardSettings = ({ statusColor, availableColors, onClose, onColorChange }) => {
|
|
||||||
//Состояние индекса текущего цвета
|
|
||||||
const [colorIndex, setColorIndex] = useState(availableColors.indexOf(statusColor));
|
|
||||||
|
|
||||||
//При закрытии диалога с применением настройки статуса
|
|
||||||
const handleDialogOk = () => {
|
|
||||||
//Изменяем цвет статуса
|
|
||||||
onColorChange(availableColors[colorIndex]);
|
|
||||||
//Закрываем диалог
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении значения элемента
|
|
||||||
const handleSettingsItemChange = (item, value) => {
|
|
||||||
setColorIndex(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Dialog open onClose={onClose} fullWidth maxWidth="sm">
|
|
||||||
<DialogTitle>Настройки</DialogTitle>
|
|
||||||
<IconButton aria-label="close" onClick={onClose} sx={COMMON_STYLES.DIALOG_CLOSE_BUTTON}>
|
|
||||||
<Icon>close</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<DialogContent>
|
|
||||||
<Box component="section" p={1}>
|
|
||||||
<CustomInputField
|
|
||||||
elementCode="color"
|
|
||||||
elementValue={colorIndex}
|
|
||||||
labelText="Цвет"
|
|
||||||
items={availableColors.reduce((prev, cur) => [...prev, { id: prev.length, caption: cur }], [])}
|
|
||||||
onChange={handleSettingsItemChange}
|
|
||||||
sx={STYLES.BCKG_COLOR(availableColors[colorIndex])}
|
|
||||||
menuItemRender={menuItemRender}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions sx={COMMON_STYLES.DIALOG_ACTIONS}>
|
|
||||||
<Button variant="text" onClick={handleDialogOk}>
|
|
||||||
Применить
|
|
||||||
</Button>
|
|
||||||
<Button variant="text" onClick={onClose}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Диалог настройки карточки статуса
|
|
||||||
StatusCardSettings.propTypes = {
|
|
||||||
statusColor: PropTypes.string.isRequired,
|
|
||||||
availableColors: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
onColorChange: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------
|
|
||||||
//Интерфейс компонента
|
|
||||||
//--------------------
|
|
||||||
|
|
||||||
export { StatusCardSettings };
|
|
||||||
@ -1,415 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Карточка события
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Draggable } from "react-beautiful-dnd"; //Работа с drag&drop
|
|
||||||
import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { TaskDialog } from "../task_dialog"; //Форма события
|
|
||||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
|
||||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений
|
|
||||||
import { TASK_COLORS, getTaskExpiredColor, getTaskBgColorByRule, makeCardActionsArray } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
|
||||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
|
||||||
import { useTasksFunctions } from "../hooks/tasks_hooks"; //Состояние вспомогательных функций событий
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" },
|
|
||||||
CARD: (task, colorRule) => {
|
|
||||||
const expiredColor = getTaskExpiredColor(task);
|
|
||||||
const backgroundColor = task.nClosed ? "#d3d3d3" : colorRule.SCOLOR ? getTaskBgColorByRule(task, colorRule) : null;
|
|
||||||
return {
|
|
||||||
...(expiredColor ? { borderLeft: `solid ${expiredColor}` } : {}),
|
|
||||||
...(backgroundColor ? { backgroundColor: backgroundColor } : {})
|
|
||||||
};
|
|
||||||
},
|
|
||||||
CARD_HEADER_TITLE: {
|
|
||||||
padding: "4px",
|
|
||||||
width: "292px",
|
|
||||||
display: "-webkit-box",
|
|
||||||
hyphens: "auto",
|
|
||||||
WebkitBoxOrient: "vertical",
|
|
||||||
WebkitLineClamp: 2,
|
|
||||||
overflow: "hidden"
|
|
||||||
},
|
|
||||||
CARD_HEADER: { padding: 0, cursor: "pointer" },
|
|
||||||
CARD_CONTENT: { padding: "4px !important" },
|
|
||||||
CARD_CONTENT_BOX: { display: "flex", alignItems: "center", width: "100%" },
|
|
||||||
STACK_SENDER: { alignItems: "center", marginLeft: "auto", width: "50%", justifyContent: "flex-end", paddingLeft: "10px", gap: "5px" },
|
|
||||||
TYPOGRAPHY_TASK: { color: "text.secondary", fontSize: 14, width: "40%", overflow: "hidden" },
|
|
||||||
TYPOGRAPHY_SENDER: { color: "text.secondary", fontSize: 14, width: "80%", overflow: "hidden", textAlign: "end" },
|
|
||||||
ICON_COLOR: linked => {
|
|
||||||
return { color: theme => (linked ? TASK_COLORS.LINKED : theme.palette.grey[500]), width: "10%" };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//Действия карточки события
|
|
||||||
const CardActions = ({
|
|
||||||
taskRn,
|
|
||||||
taskClosed,
|
|
||||||
menuItems,
|
|
||||||
cardActions,
|
|
||||||
onMethodsMenuButtonClick,
|
|
||||||
onMethodsMenuClose,
|
|
||||||
onTasksReload,
|
|
||||||
pointSettings,
|
|
||||||
onOpenNoteDialog
|
|
||||||
}) => {
|
|
||||||
//При нажатии на действие меню
|
|
||||||
const handleActionClick = action => {
|
|
||||||
//Выполняем действие
|
|
||||||
action.func({
|
|
||||||
nEvent: taskRn,
|
|
||||||
onReload: action.tasksReload ? () => onTasksReload(action.needAccountsReload) : null,
|
|
||||||
onNoteOpen: pointSettings.ADDNOTE_ONSEND ? onOpenNoteDialog : null
|
|
||||||
});
|
|
||||||
//Закрываем меню действий
|
|
||||||
onMethodsMenuClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={STYLES.BOX_ROW}>
|
|
||||||
<IconButton id={`${taskRn}_menu_button`} aria-haspopup="true" onClick={onMethodsMenuButtonClick}>
|
|
||||||
<Icon>more_vert</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<Menu id={`${taskRn}_menu`} anchorEl={cardActions.anchorMenuMethods} open={cardActions.openMethods} onClose={onMethodsMenuClose}>
|
|
||||||
{menuItems.map(action =>
|
|
||||||
action.visible ? (
|
|
||||||
<MenuItem
|
|
||||||
sx={action.delimiter ? STYLES.MENU_ITEM_DELIMITER : {}}
|
|
||||||
key={`${taskRn}_${action.method}`}
|
|
||||||
onClick={() => handleActionClick(action)}
|
|
||||||
disabled={taskClosed === 1 && action.disableClosed ? true : false}
|
|
||||||
>
|
|
||||||
<Icon>{action.icon}</Icon>
|
|
||||||
<Typography pl={1}>{action.name}</Typography>
|
|
||||||
</MenuItem>
|
|
||||||
) : null
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Действия карточки события
|
|
||||||
CardActions.propTypes = {
|
|
||||||
taskRn: PropTypes.number.isRequired,
|
|
||||||
taskClosed: PropTypes.oneOf([0, 1]).isRequired,
|
|
||||||
menuItems: PropTypes.array.isRequired,
|
|
||||||
cardActions: PropTypes.object.isRequired,
|
|
||||||
onMethodsMenuButtonClick: PropTypes.func.isRequired,
|
|
||||||
onMethodsMenuClose: PropTypes.func.isRequired,
|
|
||||||
onTasksReload: PropTypes.func,
|
|
||||||
pointSettings: PropTypes.object,
|
|
||||||
onOpenNoteDialog: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Карточка события
|
|
||||||
const TaskCard = ({ task, index, onTasksReload, colorRule, pointSettings, onOpenNoteDialog }) => {
|
|
||||||
//Состояние диалога события
|
|
||||||
const [taskDialogOpen, setTaskDialogOpen] = useState(false);
|
|
||||||
|
|
||||||
//Состояние действий события
|
|
||||||
const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false });
|
|
||||||
|
|
||||||
//Состояние списка действий меню
|
|
||||||
const [menuItems, setMenuItems] = useState([]);
|
|
||||||
|
|
||||||
//Вспомогательные функции открытия раздела
|
|
||||||
const { handleClientEventsOpen, handleClientEventsNotesOpen, handleFileLinksOpen, handleCatalogTreeOpen } = useDictionary();
|
|
||||||
|
|
||||||
//Состояние вспомогательных функций событий
|
|
||||||
const { handleTaskStateChange, handleTaskSend } = useTasksFunctions();
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//Подключение к контексту сообщений
|
|
||||||
const { showMsgWarn } = useContext(MessagingСtx);
|
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
|
||||||
const { pOnlineShowDocument } = useContext(ApplicationСtx);
|
|
||||||
|
|
||||||
//По нажатию на открытие меню действий
|
|
||||||
const handleMethodsMenuButtonClick = useCallback(event => {
|
|
||||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true }));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//При закрытии меню
|
|
||||||
const handleMethodsMenuClose = useCallback(() => {
|
|
||||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false }));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//При удалении контрагента
|
|
||||||
const handleTaskDelete = useCallback(
|
|
||||||
async ({ nEvent, onReload }) => {
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DELETE",
|
|
||||||
args: { NCLNEVENTS: nEvent }
|
|
||||||
});
|
|
||||||
//Если требуется перезагрузить данные
|
|
||||||
onReload ? onReload() : null;
|
|
||||||
},
|
|
||||||
[executeStored]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При возврате в предыдущую точку события
|
|
||||||
const handleTaskReturn = useCallback(
|
|
||||||
async ({ nEvent, onReload }) => {
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_RETURN",
|
|
||||||
args: { NCLNEVENTS: nEvent }
|
|
||||||
});
|
|
||||||
//Если требуется перезагрузить данные
|
|
||||||
onReload ? onReload() : null;
|
|
||||||
},
|
|
||||||
[executeStored]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При перемещении в каталог
|
|
||||||
const handleTaskMove = useCallback(
|
|
||||||
async ({ nEvent, nCrn, onReload }) => {
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_MOVE",
|
|
||||||
args: { NCLNEVENTS: nEvent, NCRN: nCrn }
|
|
||||||
});
|
|
||||||
//Если требуется перезагрузить данные
|
|
||||||
onReload ? onReload() : null;
|
|
||||||
},
|
|
||||||
[executeStored]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Направить"
|
|
||||||
const handleTaskSendAction = useCallback(
|
|
||||||
async ({ nEvent, onReload, onNoteOpen }) => {
|
|
||||||
//Выполняем направление события
|
|
||||||
handleTaskSend({ nEvent, onReload, onNoteOpen });
|
|
||||||
},
|
|
||||||
[handleTaskSend]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатия действия "Редактировать"
|
|
||||||
const handleTaskEditAction = useCallback(() => {
|
|
||||||
setTaskDialogOpen(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//По нажатия действия "Редактировать в разделе"
|
|
||||||
const handleTaskEditClientAction = useCallback(
|
|
||||||
async ({ nEvent }) => {
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SELECT",
|
|
||||||
args: {
|
|
||||||
NCLNEVENTS: nEvent
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (data.NIDENT) {
|
|
||||||
//Открываем раздел "События" с фильтром по записи
|
|
||||||
handleClientEventsOpen({ nIdent: data.NIDENT });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[executeStored, handleClientEventsOpen]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Удалить"
|
|
||||||
const handleTaskDeleteAction = useCallback(
|
|
||||||
({ nEvent, onReload }) => {
|
|
||||||
showMsgWarn("Удалить событие?", () => handleTaskDelete({ nEvent, onReload }));
|
|
||||||
},
|
|
||||||
[handleTaskDelete, showMsgWarn]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Выполнить возврат"
|
|
||||||
const handleTaskReturnAction = useCallback(
|
|
||||||
({ nEvent, onReload }) => {
|
|
||||||
showMsgWarn("Выполнить возврат события в предыдущую точку?", () => handleTaskReturn({ nEvent, onReload }));
|
|
||||||
},
|
|
||||||
[handleTaskReturn, showMsgWarn]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Примечания"
|
|
||||||
const handleEventNotesOpenAction = useCallback(
|
|
||||||
({ nEvent }) => {
|
|
||||||
handleClientEventsNotesOpen({ nPrn: nEvent });
|
|
||||||
},
|
|
||||||
[handleClientEventsNotesOpen]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Присоединенные документы"
|
|
||||||
const handleTaskFileLinksOpenAction = useCallback(
|
|
||||||
({ nEvent }) => {
|
|
||||||
handleFileLinksOpen({ nPrn: nEvent, sUnitCode: "ClientEvents" });
|
|
||||||
},
|
|
||||||
[handleFileLinksOpen]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Перейти"
|
|
||||||
const handleTaskStateChangeAction = useCallback(
|
|
||||||
async ({ nEvent, onReload, onNoteOpen }) => {
|
|
||||||
//Выполняем изменения статуса события
|
|
||||||
handleTaskStateChange({ nEvent, onReload, onNoteOpen });
|
|
||||||
},
|
|
||||||
[handleTaskStateChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Переместить"
|
|
||||||
const handleTaskMoveAction = useCallback(
|
|
||||||
async ({ nEvent, onReload }) => {
|
|
||||||
//Открываем выбор записи из раздела "Каталоги иерархии"
|
|
||||||
handleCatalogTreeOpen({
|
|
||||||
sUnitName: "ClientEvents",
|
|
||||||
nRn: task.nCrn,
|
|
||||||
callBack: res => {
|
|
||||||
//Выполняем перемещение события
|
|
||||||
handleTaskMove({
|
|
||||||
nEvent,
|
|
||||||
nCrn: res.outParameters.out_RN,
|
|
||||||
onReload
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[handleCatalogTreeOpen, handleTaskMove, task.nCrn]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменении ссылок в меню действий (для того, чтобы ссылка на объект менялась при реальной необходимости)
|
|
||||||
useEffect(() => {
|
|
||||||
//Устанавливаем список меню
|
|
||||||
setMenuItems(
|
|
||||||
makeCardActionsArray(
|
|
||||||
handleTaskEditAction,
|
|
||||||
handleTaskEditClientAction,
|
|
||||||
handleTaskDeleteAction,
|
|
||||||
handleTaskStateChangeAction,
|
|
||||||
handleTaskReturnAction,
|
|
||||||
handleTaskSendAction,
|
|
||||||
handleEventNotesOpenAction,
|
|
||||||
handleTaskFileLinksOpenAction,
|
|
||||||
handleTaskMoveAction
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
handleEventNotesOpenAction,
|
|
||||||
handleTaskFileLinksOpenAction,
|
|
||||||
handleTaskSendAction,
|
|
||||||
handleTaskStateChangeAction,
|
|
||||||
handleTaskDeleteAction,
|
|
||||||
handleTaskEditAction,
|
|
||||||
handleTaskEditClientAction,
|
|
||||||
handleTaskReturnAction,
|
|
||||||
handleTaskMoveAction
|
|
||||||
]);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
{taskDialogOpen ? (
|
|
||||||
<TaskDialog
|
|
||||||
taskRn={task.nRn}
|
|
||||||
taskType={task.sType}
|
|
||||||
editable={pointSettings.BAN_UPDATE ? false : true}
|
|
||||||
onTasksReload={onTasksReload}
|
|
||||||
onClose={() => {
|
|
||||||
setTaskDialogOpen(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Draggable draggableId={task.id.toString()} key={task.id} index={index}>
|
|
||||||
{provided => (
|
|
||||||
<Card ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} sx={STYLES.CARD(task, colorRule)}>
|
|
||||||
<CardHeader
|
|
||||||
title={
|
|
||||||
<Typography
|
|
||||||
className="task-info"
|
|
||||||
sx={STYLES.CARD_HEADER_TITLE}
|
|
||||||
lang="ru"
|
|
||||||
onClick={() => {
|
|
||||||
menuItems.find(action =>
|
|
||||||
action.method === "EDIT" ? action.func(task.nRn, action.tasksReload ? onTasksReload : null) : null
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
title={task.sDescription}
|
|
||||||
>
|
|
||||||
{task.sDescription}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
sx={STYLES.CARD_HEADER}
|
|
||||||
action={
|
|
||||||
<CardActions
|
|
||||||
taskRn={task.nRn}
|
|
||||||
taskClosed={task.nClosed}
|
|
||||||
menuItems={menuItems}
|
|
||||||
cardActions={cardActions}
|
|
||||||
onMethodsMenuButtonClick={handleMethodsMenuButtonClick}
|
|
||||||
onMethodsMenuClose={handleMethodsMenuClose}
|
|
||||||
onTasksReload={onTasksReload}
|
|
||||||
pointSettings={pointSettings}
|
|
||||||
onOpenNoteDialog={onOpenNoteDialog}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CardContent sx={STYLES.CARD_CONTENT}>
|
|
||||||
<Box sx={STYLES.CARD_CONTENT_BOX}>
|
|
||||||
<IconButton
|
|
||||||
title={task.nLinkedRn ? "Событие получено по статусной модели" : null}
|
|
||||||
onClick={
|
|
||||||
task.nLinkedRn ? () => pOnlineShowDocument({ unitCode: task.sLinkedUnit, document: task.nLinkedRn }) : null
|
|
||||||
}
|
|
||||||
sx={STYLES.ICON_COLOR(task.nLinkedRn)}
|
|
||||||
disabled={!task.nLinkedRn}
|
|
||||||
>
|
|
||||||
<Icon>assignment</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<Typography sx={STYLES.TYPOGRAPHY_TASK} noWrap title={task.name}>
|
|
||||||
{task.name}
|
|
||||||
</Typography>
|
|
||||||
{task.sSender ? (
|
|
||||||
<Stack direction="row" spacing={0.5} sx={STYLES.STACK_SENDER}>
|
|
||||||
<Typography sx={STYLES.TYPOGRAPHY_SENDER} title={task.sSender} noWrap>
|
|
||||||
{task.sSender}
|
|
||||||
</Typography>
|
|
||||||
<Avatar src={task.avatar ? `data:image/png;base64,${task.avatar}` : null} />
|
|
||||||
</Stack>
|
|
||||||
) : null}
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Карточка события
|
|
||||||
TaskCard.propTypes = {
|
|
||||||
task: PropTypes.object.isRequired,
|
|
||||||
index: PropTypes.number.isRequired,
|
|
||||||
onTasksReload: PropTypes.func,
|
|
||||||
colorRule: PropTypes.object,
|
|
||||||
pointSettings: PropTypes.object,
|
|
||||||
onOpenNoteDialog: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { TaskCard };
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент панели: Форма события
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState, useCallback } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Box, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { TaskFormTabInfo } from "./task_form_tab_info"; //Вкладка основной информации
|
|
||||||
import { TaskFormTabExecutor } from "./task_form_tab_executor"; //Вкладка информации об исполнителе
|
|
||||||
import { TaskFormTabProps } from "./task_form_tab_props"; //Вкладка информации со свойствами
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CONTAINER: { height: "625px", textAlign: "center", overflow: "hidden", display: "flex", flexDirection: "column" },
|
|
||||||
BOX_TAB: { height: "575px", overflowY: "auto", ...COMMON_STYLES.SCROLL }
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//Свойства вкладки
|
|
||||||
function a11yProps(index) {
|
|
||||||
return {
|
|
||||||
id: `simple-tab-${index}`,
|
|
||||||
"aria-controls": `simple-tabpanel-${index}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//Вкладка информации
|
|
||||||
function CustomTabPanel(props) {
|
|
||||||
const { children, value, index, ...other } = props;
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
role="tabpanel"
|
|
||||||
hidden={value !== index}
|
|
||||||
id={`simple-tabpanel-${index}`}
|
|
||||||
aria-labelledby={`simple-tab-${index}`}
|
|
||||||
{...other}
|
|
||||||
sx={STYLES.BOX_TAB}
|
|
||||||
>
|
|
||||||
{value === index && <Box pt={1}>{children}</Box>}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Контроль свойств - Вкладка информации
|
|
||||||
CustomTabPanel.propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
index: PropTypes.number.isRequired,
|
|
||||||
value: PropTypes.number.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Формирование кнопки для открытия раздела
|
|
||||||
export const getInputProps = (onClick, disabled = false, icon = "list") => {
|
|
||||||
//Генерация содержимого
|
|
||||||
return {
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton disabled={disabled} aria-label={`select`} onClick={onClick} edge="end">
|
|
||||||
<Icon>{icon}</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Форма события
|
|
||||||
const TaskForm = ({ task, taskType, editable, docProps, onTaskChange, onEventNextNumbGet }) => {
|
|
||||||
//Состояние вкладки
|
|
||||||
const [tab, setTab] = useState(0);
|
|
||||||
|
|
||||||
//При изменении вкладки
|
|
||||||
const handleTabChange = (e, newValue) => {
|
|
||||||
setTab(newValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении поля
|
|
||||||
const handleFieldEdit = useCallback(
|
|
||||||
e => {
|
|
||||||
onTaskChange({
|
|
||||||
[e.target.id]: e.target.value,
|
|
||||||
//Связанные значения, если меняется одно, то необходимо обнулить другое
|
|
||||||
...(e.target.id === "sClntClnperson" ? { sClntClients: "" } : {}),
|
|
||||||
...(e.target.id === "sClntClients" ? { sClntClnperson: "" } : {})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[onTaskChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменении доп. свойства
|
|
||||||
const handlePropEdit = useCallback(
|
|
||||||
(docProp, value) => {
|
|
||||||
onTaskChange({ docProps: { ...task.docProps, [docProp]: value } });
|
|
||||||
},
|
|
||||||
[onTaskChange, task.docProps]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Box sx={STYLES.CONTAINER}>
|
|
||||||
<Tabs value={tab} onChange={handleTabChange} aria-label="tabs of values">
|
|
||||||
<Tab label="Событие" {...a11yProps(0)} />
|
|
||||||
<Tab label="Исполнитель" {...a11yProps(1)} />
|
|
||||||
{docProps.length > 0 ? <Tab label="Свойства" {...a11yProps(2)} /> : null}
|
|
||||||
</Tabs>
|
|
||||||
<CustomTabPanel value={tab} index={0}>
|
|
||||||
<TaskFormTabInfo task={task} editable={editable} onFieldEdit={handleFieldEdit} onEventNextNumbGet={onEventNextNumbGet} />
|
|
||||||
</CustomTabPanel>
|
|
||||||
<CustomTabPanel value={tab} index={1}>
|
|
||||||
<TaskFormTabExecutor task={task} onFieldEdit={handleFieldEdit} />
|
|
||||||
</CustomTabPanel>
|
|
||||||
{docProps.length > 0 ? (
|
|
||||||
<CustomTabPanel value={tab} index={2}>
|
|
||||||
<TaskFormTabProps task={task} taskType={taskType} docProps={docProps} onPropEdit={handlePropEdit} />
|
|
||||||
</CustomTabPanel>
|
|
||||||
) : null}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Форма события
|
|
||||||
TaskForm.propTypes = {
|
|
||||||
task: PropTypes.object.isRequired,
|
|
||||||
taskType: PropTypes.string.isRequired,
|
|
||||||
editable: PropTypes.bool.isRequired,
|
|
||||||
docProps: PropTypes.array,
|
|
||||||
onTaskChange: PropTypes.func.isRequired,
|
|
||||||
onEventNextNumbGet: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { TaskForm };
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Вкладка информации об исполнителе
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
|
||||||
import dayjs from "dayjs"; //Работа с датами
|
|
||||||
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//Подключение настройки пользовательского формата даты
|
|
||||||
dayjs.extend(customParseFormat);
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Вкладка информации об исполнителе
|
|
||||||
const TaskFormTabExecutor = ({ task, onFieldEdit }) => {
|
|
||||||
//Вспомогательные функции открытия раздела
|
|
||||||
const { handleClientPersonOpen } = useDictionary();
|
|
||||||
|
|
||||||
//При изменении сотрудника-инициатора
|
|
||||||
const handleInitClnpersonChange = () =>
|
|
||||||
handleClientPersonOpen({
|
|
||||||
sCode: task.sInitClnperson,
|
|
||||||
callBack: res => {
|
|
||||||
onFieldEdit({
|
|
||||||
target: {
|
|
||||||
id: "sInitClnperson",
|
|
||||||
value: res.outParameters.out_CODE
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box sx={{ ...COMMON_STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset">
|
|
||||||
<legend style={COMMON_STYLES.LEGEND}>Планирование</legend>
|
|
||||||
<TextField
|
|
||||||
id="dPlanDate"
|
|
||||||
label="Начало работ"
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type="datetime-local"
|
|
||||||
variant="standard"
|
|
||||||
value={task.dPlanDate ? dayjs(task.dPlanDate, "DD.MM.YYYY HH:mm").format("YYYY-MM-DD HH:mm") : ""}
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled={task.isUpdate}
|
|
||||||
></TextField>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ ...COMMON_STYLES.BOX_WITH_LEGEND, ...COMMON_STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
|
||||||
<legend style={COMMON_STYLES.LEGEND}>Инициатор</legend>
|
|
||||||
<TextField
|
|
||||||
id="sInitClnperson"
|
|
||||||
label="Сотрудник"
|
|
||||||
value={task.sInitClnperson}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled={task.isUpdate}
|
|
||||||
InputProps={getInputProps(() => handleInitClnpersonChange(), task.isUpdate)}
|
|
||||||
></TextField>
|
|
||||||
<TextField id="sInitUser" label="Пользователь" value={task.sInitUser} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
|
||||||
<TextField
|
|
||||||
id="sInitReason"
|
|
||||||
label="Основание"
|
|
||||||
value={task.sInitReason}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled={task.isUpdate}
|
|
||||||
></TextField>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ ...COMMON_STYLES.BOX_WITH_LEGEND, ...COMMON_STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
|
||||||
<legend style={COMMON_STYLES.LEGEND}>Направить</legend>
|
|
||||||
<TextField id="sToCompany" label="Организация" value={task.sToCompany} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
|
||||||
<TextField
|
|
||||||
id="sToDepartment"
|
|
||||||
label="Подразделение"
|
|
||||||
value={task.sToDepartment}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled
|
|
||||||
></TextField>
|
|
||||||
<TextField id="sToClnpost" label="Должность" value={task.sToClnpost} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
|
||||||
<TextField
|
|
||||||
id="sToClnpsdep"
|
|
||||||
label="Штатная должность"
|
|
||||||
value={task.sToClnpsdep}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled
|
|
||||||
></TextField>
|
|
||||||
<TextField
|
|
||||||
id="sToClnperson"
|
|
||||||
label="Сотрудник"
|
|
||||||
value={task.sToClnperson}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled
|
|
||||||
></TextField>
|
|
||||||
<TextField
|
|
||||||
id="sToFcstaffgrp"
|
|
||||||
label="Нештатная должность"
|
|
||||||
value={task.sToFcstaffgrp}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled
|
|
||||||
></TextField>
|
|
||||||
<TextField id="sToUser" label="Пользователь" value={task.sToUser} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
|
||||||
<TextField
|
|
||||||
id="sToUsergrp"
|
|
||||||
label="Группа пользователей"
|
|
||||||
value={task.sToUsergrp}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled
|
|
||||||
></TextField>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Вкладка информации об исполнителе
|
|
||||||
TaskFormTabExecutor.propTypes = {
|
|
||||||
task: PropTypes.object.isRequired,
|
|
||||||
onFieldEdit: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { TaskFormTabExecutor };
|
|
||||||
@ -1,192 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Вкладка основной информации
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Вкладка основной информации
|
|
||||||
const TaskFormTabInfo = ({ task, editable, onFieldEdit, onEventNextNumbGet }) => {
|
|
||||||
//Вспомогательные функции открытия раздела
|
|
||||||
const { handleClientPersonOpen, handleCatalogTreeOpen, handleClientClientsOpen } = useDictionary();
|
|
||||||
|
|
||||||
//При изменении каталога
|
|
||||||
const handleCrnChange = () =>
|
|
||||||
handleCatalogTreeOpen({
|
|
||||||
sUnitName: "ClientEvents",
|
|
||||||
sName: task.sCrn,
|
|
||||||
callBack: res => {
|
|
||||||
onFieldEdit({
|
|
||||||
target: {
|
|
||||||
id: "sCrn",
|
|
||||||
value: res.outParameters.out_NAME
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//При изменении клиента-сотрудника
|
|
||||||
const handleClntClnpersonChange = () =>
|
|
||||||
handleClientPersonOpen({
|
|
||||||
sCode: task.sClntClnperson,
|
|
||||||
callBack: res => {
|
|
||||||
onFieldEdit({
|
|
||||||
target: {
|
|
||||||
id: "sClntClnperson",
|
|
||||||
value: res.outParameters.out_CODE
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//При изменении клиента-организации
|
|
||||||
const handleClntClientsChange = () =>
|
|
||||||
handleClientClientsOpen({
|
|
||||||
sCode: task.sClntClients,
|
|
||||||
callBack: res => {
|
|
||||||
onFieldEdit({
|
|
||||||
target: {
|
|
||||||
id: "sClntClients",
|
|
||||||
value: res.outParameters.out_CLIENT_CODE
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box sx={COMMON_STYLES.BOX_WITH_LEGEND} component="fieldset">
|
|
||||||
<legend style={COMMON_STYLES.LEGEND}>Событие</legend>
|
|
||||||
<Box sx={STYLES.BOX_FEW_COLUMNS}>
|
|
||||||
<TextField
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
|
||||||
id="sCrn"
|
|
||||||
label="Каталог"
|
|
||||||
fullWidth
|
|
||||||
value={task.sCrn}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
InputProps={getInputProps(handleCrnChange, task.isUpdate || task.nClosed === 1)}
|
|
||||||
required
|
|
||||||
disabled={task.isUpdate}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD("225px")}
|
|
||||||
id="sPrefix"
|
|
||||||
label="Префикс"
|
|
||||||
value={task.sPrefix}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
required
|
|
||||||
disabled={task.isUpdate}
|
|
||||||
></TextField>
|
|
||||||
<TextField
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD("225px")}
|
|
||||||
id="sNumber"
|
|
||||||
label="Номер"
|
|
||||||
value={task.sNumber}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
required
|
|
||||||
disabled={task.isUpdate}
|
|
||||||
InputProps={getInputProps(onEventNextNumbGet, !task.sPrefix || task.isUpdate, "refresh")}
|
|
||||||
></TextField>
|
|
||||||
<TextField
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD("225px", !task.isUpdate)}
|
|
||||||
id="sType"
|
|
||||||
label="Тип"
|
|
||||||
value={task.sType}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled
|
|
||||||
required
|
|
||||||
></TextField>
|
|
||||||
<TextField
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD("225px", !task.isUpdate)}
|
|
||||||
id="sStatus"
|
|
||||||
label="Статус"
|
|
||||||
value={task.sStatus}
|
|
||||||
variant="standard"
|
|
||||||
disabled
|
|
||||||
required
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
></TextField>
|
|
||||||
<TextField
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
|
||||||
fullWidth
|
|
||||||
id="sDescription"
|
|
||||||
label="Описание"
|
|
||||||
value={task.sDescription}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled={!task.sType || !editable}
|
|
||||||
required
|
|
||||||
multiline
|
|
||||||
minRows={7}
|
|
||||||
maxRows={7}
|
|
||||||
></TextField>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ ...COMMON_STYLES.BOX_WITH_LEGEND, ...COMMON_STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
|
||||||
<legend style={COMMON_STYLES.LEGEND}>Клиент</legend>
|
|
||||||
<TextField
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
|
||||||
id="sClntClients"
|
|
||||||
label="Организация"
|
|
||||||
value={task.sClntClients}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled={!task.sType || task.nClosed === 1}
|
|
||||||
InputProps={getInputProps(() => handleClntClientsChange(), !task.sType || task.nClosed === 1)}
|
|
||||||
></TextField>
|
|
||||||
<TextField
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
|
||||||
id="sClntClnperson"
|
|
||||||
label="Сотрудник"
|
|
||||||
value={task.sClntClnperson}
|
|
||||||
variant="standard"
|
|
||||||
onChange={onFieldEdit}
|
|
||||||
disabled={!task.sType || task.nClosed === 1}
|
|
||||||
InputProps={getInputProps(() => handleClntClnpersonChange(), !task.sType || task.nClosed === 1)}
|
|
||||||
></TextField>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Вкладка основной информации
|
|
||||||
TaskFormTabInfo.propTypes = {
|
|
||||||
task: PropTypes.object.isRequired,
|
|
||||||
editable: PropTypes.bool.isRequired,
|
|
||||||
onFieldEdit: PropTypes.func.isRequired,
|
|
||||||
onEventNextNumbGet: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { TaskFormTabInfo };
|
|
||||||
@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Вкладка информации со свойствами
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
|
||||||
import dayjs from "dayjs"; //Работа с датами
|
|
||||||
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
|
|
||||||
import { DP_DEFAULT_VALUE, DP_IN_VALUE, DP_RETURN_VALUE, validationError, formatSqlDate } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
|
||||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
|
||||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//Подключение настройки пользовательского формата даты
|
|
||||||
dayjs.extend(customParseFormat);
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Вкладка информации со свойствами
|
|
||||||
const TaskFormTabProps = ({ task, docProps, onPropEdit }) => {
|
|
||||||
//Вспомогательные функции открытия раздела
|
|
||||||
const { handleExtraDictionariesOpen, handleUnitOpen } = useDictionary();
|
|
||||||
|
|
||||||
//Выбор из словаря или дополнительного словаря
|
|
||||||
const handleDictOpen = async (docProp, curValue = null) => {
|
|
||||||
//Если способ выбора - словарь
|
|
||||||
docProp.NENTRY_TYPE === 1
|
|
||||||
? handleUnitOpen({
|
|
||||||
sUnitCode: docProp.SUNITCODE,
|
|
||||||
sShowMethod: docProp.SMETHOD_CODE,
|
|
||||||
inputParameters: docProp.NPARAM_RN ? [{ name: docProp.SPARAM_IN_CODE, value: curValue }] : null,
|
|
||||||
callBack: res => {
|
|
||||||
onPropEdit(docProp.SFORMATTED_ID, res.outParameters[docProp.SPARAM_OUT_CODE]);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: //Если способ выбора - доп. словарь
|
|
||||||
handleExtraDictionariesOpen({
|
|
||||||
nRn: docProp.NEXTRA_DICT_RN,
|
|
||||||
sParamName: DP_IN_VALUE[docProp.NFORMAT],
|
|
||||||
paramValue: curValue,
|
|
||||||
callBack: res => {
|
|
||||||
onPropEdit(docProp.SFORMATTED_ID, res.outParameters[DP_RETURN_VALUE[docProp.NFORMAT]]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//Инициализация дополнительного свойства
|
|
||||||
const initPropValue = prop => {
|
|
||||||
//Считываем значение свойства из события
|
|
||||||
const value = task.docProps[prop.SFORMATTED_ID];
|
|
||||||
//Если есть значение свойства
|
|
||||||
if (value) {
|
|
||||||
//Строка или число
|
|
||||||
if (prop.NFORMAT < 2) {
|
|
||||||
return prop.NNUM_PRECISION ? String(value).replace(".", ",") : value;
|
|
||||||
}
|
|
||||||
//Дата
|
|
||||||
if (prop.NFORMAT === 2) {
|
|
||||||
//Возвращаем значение исходя из подтипа даты
|
|
||||||
switch (prop.NDATA_SUBTYPE) {
|
|
||||||
//Дата без времени
|
|
||||||
case 0:
|
|
||||||
return dayjs(value).format("YYYY-MM-DD");
|
|
||||||
//Дата и время без секунд
|
|
||||||
case 1:
|
|
||||||
return dayjs(value).format("YYYY-MM-DD HH:mm");
|
|
||||||
//Дата и время с секундами
|
|
||||||
default:
|
|
||||||
return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Если это ничего из вышестоящего - время
|
|
||||||
return formatSqlDate(value);
|
|
||||||
}
|
|
||||||
//Если нет значения, но это изменение события
|
|
||||||
if (task.nRn) {
|
|
||||||
//Возвращаем пустоту
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
//Если нет значения и это добавление события - возвращаем значение по умолчанию
|
|
||||||
return prop[DP_DEFAULT_VALUE[prop.NFORMAT]];
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Box sx={COMMON_STYLES.BOX_WITH_LEGEND} component="fieldset">
|
|
||||||
<Box sx={STYLES.BOX_FEW_COLUMNS}>
|
|
||||||
{docProps.map((docProp, index) => {
|
|
||||||
return docProp.BSHOW_IN_GRID ? (
|
|
||||||
<TextField
|
|
||||||
error={
|
|
||||||
!validationError(
|
|
||||||
task.docProps[docProp.SFORMATTED_ID],
|
|
||||||
docProp.NFORMAT,
|
|
||||||
docProp.NNUM_WIDTH,
|
|
||||||
docProp.NNUM_PRECISION,
|
|
||||||
docProp.NSTR_WIDTH
|
|
||||||
)
|
|
||||||
}
|
|
||||||
key={index}
|
|
||||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
|
||||||
id={docProp.SFORMATTED_ID}
|
|
||||||
type={
|
|
||||||
docProp.NFORMAT < 2
|
|
||||||
? "string"
|
|
||||||
: docProp.NFORMAT === 2
|
|
||||||
? docProp.NDATA_SUBTYPE === 0
|
|
||||||
? "date"
|
|
||||||
: "datetime-local"
|
|
||||||
: "time"
|
|
||||||
}
|
|
||||||
label={docProp.SNAME}
|
|
||||||
fullWidth
|
|
||||||
value={initPropValue(docProp)}
|
|
||||||
variant="standard"
|
|
||||||
onChange={e => onPropEdit(e.target.id, e.target.value)}
|
|
||||||
inputProps={
|
|
||||||
(docProp.NFORMAT === 2 && docProp.NDATA_SUBTYPE === 2) || (docProp.NFORMAT === 3 && docProp.NDATA_SUBTYPE === 1)
|
|
||||||
? { step: 1 }
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
InputProps={
|
|
||||||
docProp.NENTRY_TYPE > 0
|
|
||||||
? getInputProps(() => handleDictOpen(docProp, task.docProps[docProp.SFORMATTED_ID]))
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
InputLabelProps={
|
|
||||||
docProp.NFORMAT < 2
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
shrink: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
required={docProp.BREQUIRE}
|
|
||||||
disabled={docProp.BREADONLY}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Вкладка информации со свойствами
|
|
||||||
TaskFormTabProps.propTypes = {
|
|
||||||
task: PropTypes.object.isRequired,
|
|
||||||
docProps: PropTypes.array.isRequired,
|
|
||||||
onPropEdit: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { TaskFormTabProps };
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Фильтр отбора
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Chip, Stack, Icon, IconButton, Box, Menu, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
|
|
||||||
import { FilterDialog } from "./components/filter_dialog.js"; //Диалог фильтра
|
|
||||||
import { COMMON_STYLES } from "./styles"; //Общие стили
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
ICON_ORDERS: orders => {
|
|
||||||
return orders.length > 0 ? { color: "#1976d2" } : {};
|
|
||||||
},
|
|
||||||
MENU_ORDER: {
|
|
||||||
width: "260px"
|
|
||||||
},
|
|
||||||
MENU_ITEM_ORDER: {
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between"
|
|
||||||
},
|
|
||||||
FILTERS_STACK: {
|
|
||||||
paddingBottom: "5px",
|
|
||||||
...COMMON_STYLES.SCROLL
|
|
||||||
},
|
|
||||||
STACK_FILTER: { maxWidth: "99vw" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------------
|
|
||||||
//Вспомогательные компоненты
|
|
||||||
//--------------------------
|
|
||||||
|
|
||||||
//Элемент меню сортировок
|
|
||||||
const SortMenuItem = ({ item, caption, orders, onOrderChanged }) => {
|
|
||||||
//Кнопка сортировки
|
|
||||||
const order = orders.find(order => order.name == item);
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<MenuItem sx={STYLES.MENU_ITEM_ORDER} key={item} onClick={() => onOrderChanged(item)}>
|
|
||||||
<Typography>{caption}</Typography>
|
|
||||||
{order ? order.direction === "ASC" ? <Icon>arrow_upward</Icon> : <Icon>arrow_downward</Icon> : null}
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Элемент меню сортировок
|
|
||||||
SortMenuItem.propTypes = {
|
|
||||||
item: PropTypes.string.isRequired,
|
|
||||||
caption: PropTypes.string.isRequired,
|
|
||||||
orders: PropTypes.array,
|
|
||||||
onOrderChanged: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Меню сортировок
|
|
||||||
const SortMenu = ({ menuOrders, onOrdersMenuClose, orders, onOrderChanged }) => {
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Menu
|
|
||||||
id={`sort_menu`}
|
|
||||||
anchorEl={menuOrders.anchorMenuOrders}
|
|
||||||
open={menuOrders.openOrders}
|
|
||||||
onClose={onOrdersMenuClose}
|
|
||||||
MenuListProps={{ sx: STYLES.MENU_ORDER }}
|
|
||||||
>
|
|
||||||
<SortMenuItem item={"DCHANGE_DATE"} caption={"Дата последнего изменения"} orders={orders} onOrderChanged={onOrderChanged} />
|
|
||||||
<SortMenuItem item={"DPLAN_DATE"} caption={"Дата начала работ"} orders={orders} onOrderChanged={onOrderChanged} />
|
|
||||||
<SortMenuItem item={"SPREF_NUMB"} caption={"Номер"} orders={orders} onOrderChanged={onOrderChanged} />
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Меню сортировок
|
|
||||||
SortMenu.propTypes = {
|
|
||||||
menuOrders: PropTypes.object.isRequired,
|
|
||||||
onOrdersMenuClose: PropTypes.func.isRequired,
|
|
||||||
orders: PropTypes.array,
|
|
||||||
onOrderChanged: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//Элемент фильтра
|
|
||||||
const FilterItem = ({ caption, value, onClick }) => {
|
|
||||||
//При нажатии на элемент
|
|
||||||
const handleClick = () => (onClick ? onClick() : null);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<Chip
|
|
||||||
label={
|
|
||||||
<Stack direction={"row"} alignItems={"center"}>
|
|
||||||
<strong>{caption}</strong>
|
|
||||||
{value ? `:\u00A0${value}` : null}
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
variant="outlined"
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Элемент фильтра
|
|
||||||
FilterItem.propTypes = {
|
|
||||||
caption: PropTypes.string.isRequired,
|
|
||||||
value: PropTypes.any,
|
|
||||||
onClick: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//---------------
|
|
||||||
//Тело компонента
|
|
||||||
//---------------
|
|
||||||
|
|
||||||
//Фильтр отбора
|
|
||||||
const Filter = ({
|
|
||||||
isFilterDialogOpen,
|
|
||||||
filter,
|
|
||||||
docLinks,
|
|
||||||
selectedDocLink,
|
|
||||||
onFilterChange,
|
|
||||||
onDocLinksLoad,
|
|
||||||
onFilterOpen,
|
|
||||||
onFilterClose,
|
|
||||||
onTasksReload,
|
|
||||||
orders,
|
|
||||||
onOrderChanged,
|
|
||||||
...other
|
|
||||||
}) => {
|
|
||||||
//Состояние меню сортировки
|
|
||||||
const [menuOrders, setMenuOrders] = useState({ anchorMenuOrders: null, openOrders: false });
|
|
||||||
|
|
||||||
//При нажатии на открытие меню сортировки
|
|
||||||
const handleOrdersMenuButtonClick = event => {
|
|
||||||
setMenuOrders(pv => ({ ...pv, anchorMenuOrders: event.currentTarget, openOrders: true }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При закрытии меню
|
|
||||||
const handleOrdersMenuClose = () => {
|
|
||||||
setMenuOrders(pv => ({ ...pv, anchorMenuOrders: null, openOrders: false }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{isFilterDialogOpen ? (
|
|
||||||
<FilterDialog
|
|
||||||
initial={{ filter, docLinks }}
|
|
||||||
// docLinks={docLinks}
|
|
||||||
onFilterChange={onFilterChange}
|
|
||||||
onFilterClose={onFilterClose}
|
|
||||||
onDocLinksLoad={onDocLinksLoad}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Box {...other}>
|
|
||||||
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.STACK_FILTER}>
|
|
||||||
<IconButton title="Обновить" onClick={onTasksReload}>
|
|
||||||
<Icon>refresh</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton title="Сортировать" sx={STYLES.ICON_ORDERS(orders)} onClick={handleOrdersMenuButtonClick}>
|
|
||||||
<Icon>sort</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton title="Фильтр" onClick={onFilterOpen}>
|
|
||||||
<Icon>filter_alt</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<Stack direction="row" spacing={1} alignItems={"center"} sx={STYLES.FILTERS_STACK}>
|
|
||||||
{filter.sState ? <FilterItem caption={"Состояние"} value={filter.sState} onClick={onFilterOpen} /> : null}
|
|
||||||
{filter.sType ? <FilterItem caption={"Тип"} value={filter.sType} onClick={onFilterOpen} /> : null}
|
|
||||||
{filter.sCrnName ? <FilterItem caption={"Каталог"} value={filter.sCrnName} onClick={onFilterOpen} /> : null}
|
|
||||||
{filter.bSubcatalogs ? <FilterItem caption={"Включая подкаталоги"} onClick={onFilterOpen} /> : null}
|
|
||||||
{filter.sSendPerson ? <FilterItem caption={"Исполнитель"} value={filter.sSendPerson} onClick={onFilterOpen} /> : null}
|
|
||||||
{filter.sSendDivision ? <FilterItem caption={"Подразделение"} value={filter.sSendDivision} onClick={onFilterOpen} /> : null}
|
|
||||||
{filter.sSendUsrGrp ? (
|
|
||||||
<FilterItem caption={"Группа пользователей"} value={filter.sSendUsrGrp} onClick={onFilterOpen} />
|
|
||||||
) : null}
|
|
||||||
{filter.sDocLink && selectedDocLink ? (
|
|
||||||
<FilterItem caption={"Учётный документ"} value={selectedDocLink.descr} onClick={onFilterOpen} />
|
|
||||||
) : null}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
<SortMenu menuOrders={menuOrders} onOrdersMenuClose={handleOrdersMenuClose} orders={orders} onOrderChanged={onOrderChanged} />
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Фильтр отбора
|
|
||||||
Filter.propTypes = {
|
|
||||||
isFilterDialogOpen: PropTypes.bool.isRequired,
|
|
||||||
filter: PropTypes.object.isRequired,
|
|
||||||
docLinks: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
selectedDocLink: PropTypes.object,
|
|
||||||
onFilterChange: PropTypes.func.isRequired,
|
|
||||||
onDocLinksLoad: PropTypes.func,
|
|
||||||
onFilterOpen: PropTypes.func.isRequired,
|
|
||||||
onFilterClose: PropTypes.func.isRequired,
|
|
||||||
onTasksReload: PropTypes.func.isRequired,
|
|
||||||
orders: PropTypes.array,
|
|
||||||
onOrderChanged: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------
|
|
||||||
//Интерфейс компонента
|
|
||||||
//--------------------
|
|
||||||
|
|
||||||
export { Filter };
|
|
||||||
@ -1,256 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Пользовательские хуки: Хуки открытия разделов
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useContext, useCallback } from "react"; //Классы React
|
|
||||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Состояние открытия разделов
|
|
||||||
const useDictionary = () => {
|
|
||||||
//Подключение к контексту приложения
|
|
||||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
|
||||||
|
|
||||||
//Отображение раздела "Сотрудники"
|
|
||||||
const handleClientPersonOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "ClientPersons",
|
|
||||||
showMethod: "main",
|
|
||||||
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Клиенты"
|
|
||||||
const handleClientClientsOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "ClientClients",
|
|
||||||
showMethod: "main",
|
|
||||||
inputParameters: [{ name: "in_CLIENT_CODE", value: prms.sCode }],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Каталоги"
|
|
||||||
const handleCatalogTreeOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "CatalogTree",
|
|
||||||
showMethod: "main",
|
|
||||||
inputParameters: [
|
|
||||||
{ name: "in_DOCNAME", value: prms.sUnitName },
|
|
||||||
{ name: "in_NAME", value: prms.sName },
|
|
||||||
{ name: "in_RN", value: prms.nRn }
|
|
||||||
],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Типы событий"
|
|
||||||
const handleEventTypesOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "ClientEventTypes",
|
|
||||||
showMethod: "dictionary",
|
|
||||||
inputParameters: [{ name: "pos_eventtypecode", value: prms.sCode }],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Контрагенты"
|
|
||||||
const handleAgnlistOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "AGNLIST",
|
|
||||||
showMethod: "agents",
|
|
||||||
inputParameters: [{ name: "pos_agnmnemo", value: prms.sMnemo }],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Штатные подразделения"
|
|
||||||
const handleInsDepartmentOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "INS_DEPARTMENT",
|
|
||||||
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Нештатные структуры"
|
|
||||||
const handleCostStaffGroupsOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "CostStaffGroups",
|
|
||||||
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Дополнительные словари"
|
|
||||||
const handleExtraDictionariesOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "ExtraDictionaries",
|
|
||||||
showMethod: "values",
|
|
||||||
inputParameters: [
|
|
||||||
{ name: "pos_rn", value: prms.nRn },
|
|
||||||
{ name: prms.sParamName, value: prms.paramValue }
|
|
||||||
],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Маршруты событий (исполнители в точках)"
|
|
||||||
const handleEventRoutesPointExecutersOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "EventRoutesPointExecuters",
|
|
||||||
showMethod: "executers",
|
|
||||||
inputParameters: prms.inputParameters,
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "События"
|
|
||||||
const handleClientEventsOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "ClientEvents",
|
|
||||||
inputParameters: [{ name: "in_Ident", value: prms.nIdent }]
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "События (примечания)"
|
|
||||||
const handleClientEventsNotesOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "ClientEventsNotes",
|
|
||||||
showMethod: "main",
|
|
||||||
inputParameters: [{ name: "in_PRN", value: prms.nPrn }]
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Присоединенные документы"
|
|
||||||
const handleFileLinksOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "FileLinks",
|
|
||||||
showMethod: "main_link",
|
|
||||||
inputParameters: [
|
|
||||||
{ name: "in_PRN", value: prms.nPrn },
|
|
||||||
{ name: "in_UNITCODE", value: prms.sUnitCode }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Отображение раздела "Маршруты событий (точки перехода)"
|
|
||||||
const handleEventRoutesPointsPassessOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "EventRoutesPointsPasses",
|
|
||||||
showMethod: "main_passes",
|
|
||||||
inputParameters: [
|
|
||||||
{ name: "in_ENVTYPE_CODE", value: prms.sEventType },
|
|
||||||
{ name: "in_ENVSTAT_CODE", value: prms.sEventStatus },
|
|
||||||
{ name: "in_POINT", value: prms.nPoint }
|
|
||||||
],
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Универсальное отображение раздела
|
|
||||||
const handleUnitOpen = useCallback(
|
|
||||||
async prms => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: prms.sUnitCode,
|
|
||||||
showMethod: prms.sShowMethod,
|
|
||||||
inputParameters: prms.inputParameters,
|
|
||||||
callBack: res => {
|
|
||||||
res.success ? prms.callBack(res) : null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[pOnlineShowDictionary]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleClientPersonOpen,
|
|
||||||
handleClientClientsOpen,
|
|
||||||
handleCatalogTreeOpen,
|
|
||||||
handleEventTypesOpen,
|
|
||||||
handleAgnlistOpen,
|
|
||||||
handleInsDepartmentOpen,
|
|
||||||
handleCostStaffGroupsOpen,
|
|
||||||
handleExtraDictionariesOpen,
|
|
||||||
handleEventRoutesPointExecutersOpen,
|
|
||||||
handleClientEventsOpen,
|
|
||||||
handleClientEventsNotesOpen,
|
|
||||||
handleFileLinksOpen,
|
|
||||||
handleEventRoutesPointsPassessOpen,
|
|
||||||
handleUnitOpen
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useDictionary };
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Пользовательские хуки: Хуки фильтра
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react"; //Классы React
|
|
||||||
import { EVENT_STATES } from "../layouts"; //Перечисление состояний события
|
|
||||||
import { getLocalStorageValue } from "../layouts"; //Вспомогательные функции
|
|
||||||
|
|
||||||
//--------------------------
|
|
||||||
//Вспомогательные компоненты
|
|
||||||
//--------------------------
|
|
||||||
|
|
||||||
//Проверка возможности загрузки данных фильтра из локального хранилища
|
|
||||||
const isLocalStorageExists = () => {
|
|
||||||
return getLocalStorageValue("sType");
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук фильтра
|
|
||||||
//const useFilters = filterOpen => {
|
|
||||||
const useFilters = () => {
|
|
||||||
//Состояние фильтра
|
|
||||||
const [filters, setFilters] = useState({
|
|
||||||
loaded: false,
|
|
||||||
isSetByUser: !isLocalStorageExists(),
|
|
||||||
values: {
|
|
||||||
sState: EVENT_STATES[1],
|
|
||||||
sType: "",
|
|
||||||
sCrnName: "",
|
|
||||||
sCrnRnList: "",
|
|
||||||
bSubcatalogs: false,
|
|
||||||
sSendPerson: "",
|
|
||||||
sSendDivision: "",
|
|
||||||
sSendUsrGrp: "",
|
|
||||||
sDocLink: ""
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Установить значение фильтра
|
|
||||||
const setFilterValues = useCallback((values, isSetByUser = true) => {
|
|
||||||
setFilters({ loaded: true, isSetByUser: isSetByUser, values: values });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//Загрузка значений фильтра из локального хранилища браузера
|
|
||||||
const loadLocalStorageValues = useCallback(async () => {
|
|
||||||
//Загружаем значения по умолчанию
|
|
||||||
let values = { ...filters.values };
|
|
||||||
//Обходим ключи объекта значений
|
|
||||||
for (let key in values) {
|
|
||||||
//Заполняем значениями из хранилища
|
|
||||||
switch (key) {
|
|
||||||
//Локальное хранилище не хранит булево, форматируем строку в булево
|
|
||||||
case "bSubcatalogs":
|
|
||||||
values[key] = getLocalStorageValue(key) === "true";
|
|
||||||
break;
|
|
||||||
//Не переносим информацию о связанных записях
|
|
||||||
case "sDocLink":
|
|
||||||
break;
|
|
||||||
//Переносим все остальные значения
|
|
||||||
default:
|
|
||||||
values[key] = getLocalStorageValue(key, "");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Устанавливаем значения фильтра
|
|
||||||
setFilterValues(values, false);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//При изменении значений фильтра
|
|
||||||
const handleFiltersChange = useCallback(
|
|
||||||
filters => {
|
|
||||||
setFilterValues(filters);
|
|
||||||
},
|
|
||||||
[setFilterValues]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Сохранение при закрытии панели
|
|
||||||
useEffect(() => {
|
|
||||||
//Обработка события закрытия
|
|
||||||
const onBeforeUnload = () => {
|
|
||||||
//Обходим ключи фильтра
|
|
||||||
for (let key in filters.values) {
|
|
||||||
//Если это не связи - сохраняем значение в хранилище
|
|
||||||
key !== "sDocLink" ? localStorage.setItem(key, filters.values[key] ? filters.values[key] : "") : null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//Если данные были загружены и произошли изменения
|
|
||||||
if (filters.loaded && filters.isSetByUser) {
|
|
||||||
//Вешаем обработчик события закрытия
|
|
||||||
window.addEventListener("beforeunload", onBeforeUnload);
|
|
||||||
}
|
|
||||||
//Очищаем при размонтировании
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
||||||
};
|
|
||||||
}, [filters.loaded, filters.isSetByUser, filters.values]);
|
|
||||||
|
|
||||||
//При подключении к странице
|
|
||||||
useEffect(() => {
|
|
||||||
//Если требуется загрузить фильтр из локального хранилища
|
|
||||||
if (!filters.loaded && !filters.isSetByUser) {
|
|
||||||
loadLocalStorageValues();
|
|
||||||
}
|
|
||||||
}, [filters.isSetByUser, filters.loaded, loadLocalStorageValues]);
|
|
||||||
|
|
||||||
return [filters, handleFiltersChange];
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useFilters };
|
|
||||||
@ -1,243 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Пользовательские хуки: Хуки основных данных
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
|
||||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { getRandomColor, getLocalStorageValue } from "../layouts"; //Вспомогательные функции
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук дополнительных данных
|
|
||||||
const useExtraData = filtersType => {
|
|
||||||
//Состояние дополнительных данных
|
|
||||||
const [extraData, setExtraData] = useState({
|
|
||||||
dataLoaded: false,
|
|
||||||
reload: false,
|
|
||||||
typeLoaded: "",
|
|
||||||
evRoutes: [],
|
|
||||||
evPoints: [],
|
|
||||||
noteTypes: [],
|
|
||||||
docLinks: []
|
|
||||||
});
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//Считывание учётных документов
|
|
||||||
const handleDocLinksLoad = useCallback(
|
|
||||||
async (type = filtersType) => {
|
|
||||||
//Считываем данные
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DOCLINKS_GET",
|
|
||||||
args: {
|
|
||||||
SEVNTYPE_CODE: type
|
|
||||||
},
|
|
||||||
isArray: name => name === "XDOCLINKS",
|
|
||||||
respArg: "COUT"
|
|
||||||
});
|
|
||||||
//Возвращаем учётные документы
|
|
||||||
return [...(data?.XDOCLINKS || [])];
|
|
||||||
},
|
|
||||||
[executeStored, filtersType]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//Загрузка дополнительных данных
|
|
||||||
const loadExtraData = async () => {
|
|
||||||
//Считываем данные
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_GET_INFO_BY_CODE",
|
|
||||||
args: {
|
|
||||||
SEVNTYPE_CODE: filtersType
|
|
||||||
},
|
|
||||||
isArray: name => ["XEVROUTES", "XEVPOINTS", "XNOTETYPES"].includes(name),
|
|
||||||
respArg: "COUT"
|
|
||||||
});
|
|
||||||
//Форматируем типы примечаний под нужный формат
|
|
||||||
let noteTypes = [...(data?.XNOTETYPES || [])].reduce((prev, cur) => [...prev, cur.SNAME], []);
|
|
||||||
//Считываем учётные документы
|
|
||||||
let docLinks = await handleDocLinksLoad(filtersType);
|
|
||||||
//Обновляем дополнительные данные
|
|
||||||
setExtraData({
|
|
||||||
dataLoaded: true,
|
|
||||||
reload: false,
|
|
||||||
typeLoaded: filtersType,
|
|
||||||
evRoutes: [...(data?.XEVROUTES || [])],
|
|
||||||
evPoints: [...(data?.XEVPOINTS || [])],
|
|
||||||
noteTypes: [...noteTypes],
|
|
||||||
docLinks: [...docLinks]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//Если указан тип событий и необходимо обновить
|
|
||||||
if (extraData.reload && filtersType) {
|
|
||||||
//Загружаем дополнительные данные
|
|
||||||
if (!extraData.typeLoaded || filtersType !== extraData.typeLoaded) {
|
|
||||||
loadExtraData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [executeStored, extraData.reload, extraData.typeLoaded, filtersType, handleDocLinksLoad]);
|
|
||||||
|
|
||||||
return [extraData, setExtraData, handleDocLinksLoad];
|
|
||||||
};
|
|
||||||
|
|
||||||
//Хук заливок пользовательских настроек
|
|
||||||
const useColorRules = () => {
|
|
||||||
//Собственное состояние
|
|
||||||
const [colorRules, setColorRules] = useState({
|
|
||||||
loaded: false,
|
|
||||||
rules: [],
|
|
||||||
selectedColorRule: JSON.parse(getLocalStorageValue("settingsColorRule")) || {}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//При необходимости загрузки заливок
|
|
||||||
useEffect(() => {
|
|
||||||
//Считывание пользовательских настроек
|
|
||||||
let getColorRules = async () => {
|
|
||||||
//Считываем данные
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DP_RULES_GET",
|
|
||||||
isArray: name => name === "XRULES",
|
|
||||||
respArg: "COUT"
|
|
||||||
});
|
|
||||||
//Формируем массив правил заливки пользовательских настроек
|
|
||||||
let newColorRules = [...(data.XRULES || [])].reduce(
|
|
||||||
(prev, cur) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
id: prev.length,
|
|
||||||
SFIELD: cur.SFIELD,
|
|
||||||
SDP_NAME: cur.SDP_NAME,
|
|
||||||
SCOLOR: cur.SCOLOR,
|
|
||||||
STYPE: cur.STYPE,
|
|
||||||
fromValue: cur.NFROM ?? cur.SFROM ?? cur.DFROM,
|
|
||||||
toValue: cur.NTO ?? cur.STO ?? cur.DTO
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
//Устанавливаем заливки пользовательских настроек
|
|
||||||
setColorRules(pv => ({ ...pv, loaded: true, rules: [...newColorRules] }));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!colorRules.loaded) getColorRules();
|
|
||||||
}, [colorRules.loaded, executeStored]);
|
|
||||||
|
|
||||||
//Сохранение при закрытии панели
|
|
||||||
useEffect(() => {
|
|
||||||
//Обработка события закрытия
|
|
||||||
const onBeforeUnload = () => {
|
|
||||||
localStorage.setItem("settingsColorRule", JSON.stringify(colorRules.selectedColorRule));
|
|
||||||
};
|
|
||||||
//Вешаем обработчик события закрытия
|
|
||||||
window.addEventListener("beforeunload", onBeforeUnload);
|
|
||||||
//Очищаем при размонтировании
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
||||||
};
|
|
||||||
}, [colorRules.selectedColorRule]);
|
|
||||||
|
|
||||||
return [colorRules, setColorRules];
|
|
||||||
};
|
|
||||||
|
|
||||||
//Хук статусов событий
|
|
||||||
const useStatuses = filterType => {
|
|
||||||
//Собственное состояние статусов
|
|
||||||
const [statuses, setStatuses] = useState([]);
|
|
||||||
|
|
||||||
//Состояние статусов
|
|
||||||
const [statusesState, setStatusesState] = useState({
|
|
||||||
sorted: false,
|
|
||||||
reload: true,
|
|
||||||
attr: getLocalStorageValue("statusesSortAttr", "SEVNSTAT_NAME"),
|
|
||||||
direction: getLocalStorageValue("statusesSortDirection", "asc")
|
|
||||||
});
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//При необходимости сортировки статусов
|
|
||||||
useEffect(() => {
|
|
||||||
//Сортируем статусы
|
|
||||||
const sortStatuses = unsortedStatuses => {
|
|
||||||
//Инициализируем поле сортировки и порядок сортировки
|
|
||||||
const attr = statusesState.attr;
|
|
||||||
const direction = statusesState.direction;
|
|
||||||
//Сортируем
|
|
||||||
let sortedStatuses = unsortedStatuses.sort((a, b) =>
|
|
||||||
direction === "asc" ? a[attr].localeCompare(b[attr]) : b[attr].localeCompare(a[attr])
|
|
||||||
);
|
|
||||||
//Возвращаем
|
|
||||||
return sortedStatuses;
|
|
||||||
};
|
|
||||||
//Загружаем и сортируем статусы
|
|
||||||
const loadAndSortStatuses = async filterType => {
|
|
||||||
//Инициализируем статусы
|
|
||||||
let newStatuses = [];
|
|
||||||
//Если требуется перезагрузка
|
|
||||||
if (statusesState.reload) {
|
|
||||||
const loadedStatuses = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVNSTATS_LOAD",
|
|
||||||
args: {
|
|
||||||
SCLNEVNTYPES: filterType
|
|
||||||
},
|
|
||||||
isArray: name => name === "XSTATUS",
|
|
||||||
respArg: "COUT"
|
|
||||||
});
|
|
||||||
//Загружаем статусы и инициализируем цвета
|
|
||||||
newStatuses = [...(loadedStatuses?.XSTATUS || [])].reduce(
|
|
||||||
(prev, cur) => [...prev, { ...cur, color: getRandomColor(prev.length + 1) }],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
//Загружаем из состояния
|
|
||||||
newStatuses = [...statuses];
|
|
||||||
}
|
|
||||||
//Сортируем, если требуется
|
|
||||||
newStatuses = !statusesState.sorted ? sortStatuses(newStatuses) : newStatuses;
|
|
||||||
//Обновляем состояние статусов
|
|
||||||
setStatuses([...newStatuses]);
|
|
||||||
//Обновляем информацию о состоянии статусов
|
|
||||||
setStatusesState(pv => ({ ...pv, sorted: true, reload: false }));
|
|
||||||
};
|
|
||||||
//При необходимости изменения сортировки
|
|
||||||
if (filterType && (statusesState.reload || !statusesState.sorted)) {
|
|
||||||
//Считываем старые статусы или загружаем новые
|
|
||||||
loadAndSortStatuses(filterType);
|
|
||||||
}
|
|
||||||
}, [executeStored, filterType, statuses, statusesState.attr, statusesState.direction, statusesState.reload, statusesState.sorted]);
|
|
||||||
|
|
||||||
//Сохранение при закрытии панели
|
|
||||||
useEffect(() => {
|
|
||||||
//Обработка события закрытия
|
|
||||||
const onBeforeUnload = () => {
|
|
||||||
localStorage.setItem("statusesSortAttr", statusesState.attr);
|
|
||||||
localStorage.setItem("statusesSortDirection", statusesState.direction);
|
|
||||||
};
|
|
||||||
//Вешаем обработчик события закрытия
|
|
||||||
window.addEventListener("beforeunload", onBeforeUnload);
|
|
||||||
//Очищаем при размонтировании
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
||||||
};
|
|
||||||
}, [statusesState.attr, statusesState.direction]);
|
|
||||||
|
|
||||||
return [statuses, statusesState, setStatuses, setStatusesState];
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useExtraData, useColorRules, useStatuses };
|
|
||||||
@ -1,193 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Пользовательские хуки: Хуки диалога события
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useContext, useEffect } from "react"; //Классы React
|
|
||||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук для события
|
|
||||||
const useClientEvent = (taskRn, taskType = "") => {
|
|
||||||
//Собственное состояние
|
|
||||||
const [task, setTask] = useState({
|
|
||||||
init: true,
|
|
||||||
nRn: taskRn,
|
|
||||||
sCrn: "",
|
|
||||||
sPrefix: "",
|
|
||||||
sNumber: "",
|
|
||||||
sType: taskType,
|
|
||||||
sStatus: "",
|
|
||||||
sDescription: "",
|
|
||||||
sClntClients: "",
|
|
||||||
sClntClnperson: "",
|
|
||||||
dStartDate: "",
|
|
||||||
sInitClnperson: "",
|
|
||||||
sInitUser: "",
|
|
||||||
sInitReason: "",
|
|
||||||
sToCompany: "",
|
|
||||||
sToDepartment: "",
|
|
||||||
sToClnpost: "",
|
|
||||||
sToClnpsdep: "",
|
|
||||||
sToClnperson: "",
|
|
||||||
sToFcstaffgrp: "",
|
|
||||||
sToUser: "",
|
|
||||||
sToUsergrp: "",
|
|
||||||
sCurrentUser: "",
|
|
||||||
isUpdate: false,
|
|
||||||
insertDisabled: true,
|
|
||||||
updateDisabled: true,
|
|
||||||
docProps: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//При инициализации события
|
|
||||||
useEffect(() => {
|
|
||||||
//Если это инициализация
|
|
||||||
if (task.init) {
|
|
||||||
//Если указан рег. номер события
|
|
||||||
if (taskRn) {
|
|
||||||
//Считывание параметров события
|
|
||||||
const readEvent = async () => {
|
|
||||||
//Считываем информацию о событии по рег. номеру
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_GET",
|
|
||||||
args: {
|
|
||||||
NCLNEVENTS: task.nRn
|
|
||||||
},
|
|
||||||
respArg: "COUT"
|
|
||||||
});
|
|
||||||
//Фильтруем доп. свойства
|
|
||||||
let docProps = Object.keys(data.XEVENT)
|
|
||||||
.filter(key => key.includes("DP_"))
|
|
||||||
.reduce((prev, key) => ({ ...prev, [key]: data.XEVENT[key] }), {});
|
|
||||||
//Устанавливаем информацию о событии
|
|
||||||
setTask(pv => ({
|
|
||||||
...pv,
|
|
||||||
sCrn: data.XEVENT.SCRN,
|
|
||||||
nClosed: data.XEVENT.NCLOSED,
|
|
||||||
sPrefix: data.XEVENT.SPREF,
|
|
||||||
sNumber: data.XEVENT.SNUMB,
|
|
||||||
sType: data.XEVENT.STYPE,
|
|
||||||
sStatus: data.XEVENT.SSTATUS,
|
|
||||||
sDescription: data.XEVENT.SDESCRIPTION,
|
|
||||||
sClntClients: data.XEVENT.SCLIENT_CLIENT,
|
|
||||||
sClntClnperson: data.XEVENT.SCLIENT_PERSON,
|
|
||||||
dPlanDate: data.XEVENT.SPLAN_DATE,
|
|
||||||
sInitClnperson: data.XEVENT.SINIT_PERSON,
|
|
||||||
sInitUser: data.XEVENT.SINIT_AUTHID,
|
|
||||||
sInitReason: data.XEVENT.SREASON,
|
|
||||||
sToCompany: data.XEVENT.SSEND_CLIENT,
|
|
||||||
sToDepartment: data.XEVENT.SSEND_DIVISION,
|
|
||||||
sToClnpost: data.XEVENT.SSEND_POST,
|
|
||||||
sToClnpsdep: data.XEVENT.SSEND_PERFORM,
|
|
||||||
sToClnperson: data.XEVENT.SSEND_PERSON,
|
|
||||||
sToFcstaffgrp: data.XEVENT.SSEND_STAFFGRP,
|
|
||||||
sToUser: data.XEVENT.SSEND_USER_NAME,
|
|
||||||
sToUsergrp: data.XEVENT.SSEND_USER_GROUP,
|
|
||||||
sCurrentUser: data.XEVENT.SINIT_AUTHID,
|
|
||||||
isUpdate: true,
|
|
||||||
init: false,
|
|
||||||
docProps: docProps
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
//Инициализация параметров события
|
|
||||||
readEvent();
|
|
||||||
} else {
|
|
||||||
//Считывание изначальных параметров события
|
|
||||||
const initEvent = async () => {
|
|
||||||
//Инициализируем параметры события
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INIT",
|
|
||||||
args: {
|
|
||||||
SEVENT_TYPE: task.sType
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//Если есть данные
|
|
||||||
if (data) {
|
|
||||||
//Устанавливаем данные по событию
|
|
||||||
setTask(pv => ({
|
|
||||||
...pv,
|
|
||||||
sPrefix: data.SPREF,
|
|
||||||
sNumber: data.SNUMB,
|
|
||||||
sStatus: data.SSTATUS,
|
|
||||||
sCurrentUser: data.SINIT_AUTHNAME,
|
|
||||||
sInitClnperson: data.SINIT_PERSON,
|
|
||||||
sInitUser: !data.SINIT_PERSON ? data.SINIT_AUTHNAME : "",
|
|
||||||
init: false
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//Инициализация изначальных параметров события
|
|
||||||
initEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!task.init) {
|
|
||||||
setTask(pv => ({ ...pv, sInitUser: !task.sInitClnperson ? task.sCurrentUser : "" }));
|
|
||||||
}
|
|
||||||
}, [executeStored, task.init, task.nRn, task.sType, task.sCurrentUser, task.sInitClnperson, taskRn]);
|
|
||||||
|
|
||||||
//Проверка доступности действия
|
|
||||||
useEffect(() => {
|
|
||||||
setTask(pv => ({
|
|
||||||
...pv,
|
|
||||||
insertDisabled:
|
|
||||||
!task.sCrn ||
|
|
||||||
!task.sPrefix ||
|
|
||||||
!task.sNumber ||
|
|
||||||
!task.sType ||
|
|
||||||
!task.sStatus ||
|
|
||||||
!task.sDescription ||
|
|
||||||
(!task.sInitClnperson && !task.sInitUser),
|
|
||||||
updateDisabled: !task.sDescription
|
|
||||||
}));
|
|
||||||
}, [task.sCrn, task.sDescription, task.sInitClnperson, task.sInitUser, task.sNumber, task.sPrefix, task.sStatus, task.sType]);
|
|
||||||
|
|
||||||
return [task, setTask];
|
|
||||||
};
|
|
||||||
|
|
||||||
//Хук для получения свойств раздела "События"
|
|
||||||
const useDocsProps = taskType => {
|
|
||||||
//Собственное состояние
|
|
||||||
const [docProps, setDocsProps] = useState({ loaded: false, props: [] });
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//Загрузка доп. свойств
|
|
||||||
let getDocsProps = async () => {
|
|
||||||
//Считываема доп. свойства по типу события
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_PROPS_GET",
|
|
||||||
args: { SEVNTYPE_CODE: taskType },
|
|
||||||
isArray: name => name === "XPROPS",
|
|
||||||
respArg: "COUT"
|
|
||||||
});
|
|
||||||
//Устанавливаем доп. свойства
|
|
||||||
setDocsProps({ loaded: true, props: [...(data?.XPROPS || [])] });
|
|
||||||
};
|
|
||||||
//Если доп. свойства не загружены
|
|
||||||
if (!docProps.loaded) {
|
|
||||||
//Загружаем доп. свойства
|
|
||||||
getDocsProps();
|
|
||||||
}
|
|
||||||
}, [docProps.loaded, executeStored, taskType]);
|
|
||||||
|
|
||||||
return [docProps];
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useClientEvent, useDocsProps };
|
|
||||||
@ -1,453 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Пользовательские хуки: Хуки событий
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
|
||||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { object2Base64XML } from "../../../core/utils"; //Вспомогательные функции
|
|
||||||
import { convertFilterValuesToArray } from "../layouts"; //Вспомогательные функции
|
|
||||||
import { useDictionary } from "./dict_hooks"; //Состояние открытия разделов
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук обработки перехода события
|
|
||||||
const useTasksFunctions = () => {
|
|
||||||
//Состояние открытия раздела
|
|
||||||
const { handleEventRoutesPointExecutersOpen, handleEventRoutesPointsPassessOpen } = useDictionary();
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//Выполнение направления события
|
|
||||||
const handleSendExec = useCallback(
|
|
||||||
//Выполняем финальное перенаправление события
|
|
||||||
async ({ mainArgs, onReload = null }) => {
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND",
|
|
||||||
args: { ...mainArgs }
|
|
||||||
});
|
|
||||||
//Если требуется перезагрузить данные
|
|
||||||
onReload ? onReload() : null;
|
|
||||||
},
|
|
||||||
[executeStored]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При направлении события
|
|
||||||
const handleSend = useCallback(
|
|
||||||
async ({ mainArgs, onReload = null, onNoteOpen = null }) => {
|
|
||||||
//Если требуется добавить примечание
|
|
||||||
if (onNoteOpen) {
|
|
||||||
//Открываем примечание с коллбэком на направление события
|
|
||||||
onNoteOpen(async note => {
|
|
||||||
//Выполняем изменение статуса
|
|
||||||
handleSendExec({ mainArgs: { ...mainArgs, SNOTE_HEADER: note.header, SNOTE: note.text }, onReload });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
//Выполняем изменение статуса
|
|
||||||
handleSendExec({ mainArgs, onReload });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleSendExec]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Направить"
|
|
||||||
const handleTaskSend = useCallback(
|
|
||||||
async ({ nEvent, onReload = null, onNoteOpen = null }) => {
|
|
||||||
//Выполняем инициализацию параметров
|
|
||||||
const firstStep = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND",
|
|
||||||
args: {
|
|
||||||
NSTEP: 1,
|
|
||||||
NEVENT: nEvent
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (firstStep) {
|
|
||||||
//Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
|
|
||||||
handleEventRoutesPointExecutersOpen({
|
|
||||||
inputParameters: [
|
|
||||||
{ name: "in_IDENT", value: firstStep.NIDENT },
|
|
||||||
{ name: "in_EVENT", value: nEvent },
|
|
||||||
{ name: "in_PERSON_CODE", value: firstStep.SSEND_PERSON },
|
|
||||||
{ name: "in_USER_NAME", value: firstStep.SSEND_USER_NAME },
|
|
||||||
{ name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE },
|
|
||||||
{ name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT },
|
|
||||||
{ name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON },
|
|
||||||
{ name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME },
|
|
||||||
{ name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT },
|
|
||||||
{ name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON }
|
|
||||||
],
|
|
||||||
callBack: sendPrms => {
|
|
||||||
//Собираем основные параметры направления события
|
|
||||||
const mainArgs = {
|
|
||||||
NIDENT: firstStep.NIDENT,
|
|
||||||
NSTEP: 2,
|
|
||||||
NEVENT: nEvent,
|
|
||||||
SSEND_CLIENT: sendPrms.outParameters.out_CLIENT_CODE,
|
|
||||||
SSEND_DIVISION: sendPrms.outParameters.out_DIVISION_CODE,
|
|
||||||
SSEND_POST: sendPrms.outParameters.out_POST_CODE,
|
|
||||||
SSEND_PERFORM: sendPrms.outParameters.out_POST_IN_DIV_CODE,
|
|
||||||
SSEND_PERSON: sendPrms.outParameters.out_PERSON_CODE,
|
|
||||||
SSEND_STAFFGRP: sendPrms.outParameters.out_STAFFGRP_CODE,
|
|
||||||
SSEND_USER_GROUP: sendPrms.outParameters.out_USER_GROUP_CODE,
|
|
||||||
SSEND_USER_NAME: sendPrms.outParameters.out_USER_NAME,
|
|
||||||
NSEND_PREDEFINED_EXEC: sendPrms.outParameters.out_PREDEFINED_EXEC,
|
|
||||||
NSEND_PREDEFINED_PROC: sendPrms.outParameters.out_PREDEFINED_PROC
|
|
||||||
};
|
|
||||||
//Перенаправляем событие
|
|
||||||
handleSend({ nEvent, mainArgs, onReload, onNoteOpen });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[executeStored, handleEventRoutesPointExecutersOpen, handleSend]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Выполнение изменения статуса события
|
|
||||||
const handleStateChangeExec = useCallback(
|
|
||||||
async ({ mainArgs, onReload = null }) => {
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
|
||||||
args: { ...mainArgs }
|
|
||||||
});
|
|
||||||
//Если требуется перезагрузить данные
|
|
||||||
onReload ? onReload() : null;
|
|
||||||
},
|
|
||||||
[executeStored]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменении статуса события
|
|
||||||
const handleStateChange = useCallback(
|
|
||||||
async ({ mainArgs, onReload = null, onNoteOpen = null }) => {
|
|
||||||
//Если необходимо добавить примечание
|
|
||||||
if (onNoteOpen) {
|
|
||||||
//Открываем примечание с коллбэком на изменение статуса
|
|
||||||
onNoteOpen(async note => {
|
|
||||||
//Выполняем изменение статуса
|
|
||||||
handleStateChangeExec({ mainArgs: { ...mainArgs, SNOTE_HEADER: note.header, SNOTE: note.text }, onReload });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
//Выполняем изменение статуса
|
|
||||||
handleStateChangeExec({ mainArgs, onReload });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleStateChangeExec]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При выборе исполнителя
|
|
||||||
const handleExecuterSelect = useCallback(
|
|
||||||
async ({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => {
|
|
||||||
//Если требуется выбрать получателя
|
|
||||||
if (pointInfo.NSELECT_EXEC === 1) {
|
|
||||||
//Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
|
|
||||||
handleEventRoutesPointExecutersOpen({
|
|
||||||
inputParameters: [
|
|
||||||
{ name: "in_IDENT", value: pointInfo.NIDENT },
|
|
||||||
{ name: "in_EVENT", value: nEvent },
|
|
||||||
{ name: "in_EVENT_TYPE", value: pointInfo.SEVENT_TYPE },
|
|
||||||
{ name: "in_EVENT_STAT", value: pointInfo.SEVENT_STAT },
|
|
||||||
{ name: "in_INIT_PERSON", value: pointInfo.SINIT_PERSON },
|
|
||||||
{ name: "in_INIT_AUTHNAME", value: pointInfo.SINIT_AUTHNAME },
|
|
||||||
{ name: "in_CLIENT_CLIENT", value: pointInfo.SCLIENT_CLIENT },
|
|
||||||
{ name: "in_CLIENT_PERSON", value: pointInfo.SCLIENT_PERSON }
|
|
||||||
],
|
|
||||||
callBack: sendPrms => {
|
|
||||||
const mainArgs = {
|
|
||||||
NIDENT: pointInfo.NIDENT,
|
|
||||||
NSTEP: 4,
|
|
||||||
NEVENT: nEvent,
|
|
||||||
SEVENT_STAT: pointInfo.SEVENT_STAT,
|
|
||||||
SSEND_CLIENT: sendPrms.outParameters.out_CLIENT_CODE,
|
|
||||||
SSEND_DIVISION: sendPrms.outParameters.out_DIVISION_CODE,
|
|
||||||
SSEND_POST: sendPrms.outParameters.out_POST_CODE,
|
|
||||||
SSEND_PERFORM: sendPrms.outParameters.out_POST_IN_DIV_CODE,
|
|
||||||
SSEND_PERSON: sendPrms.outParameters.out_PERSON_CODE,
|
|
||||||
SSEND_STAFFGRP: sendPrms.outParameters.out_STAFFGRP_CODE,
|
|
||||||
SSEND_USER_GROUP: sendPrms.outParameters.out_USER_GROUP_CODE,
|
|
||||||
SSEND_USER_NAME: sendPrms.outParameters.out_USER_NAME,
|
|
||||||
NSEND_PREDEFINED_EXEC: sendPrms.outParameters.out_PREDEFINED_EXEC,
|
|
||||||
NSEND_PREDEFINED_PROC: sendPrms.outParameters.out_PREDEFINED_PROC
|
|
||||||
};
|
|
||||||
//Выполняем изменение статуса
|
|
||||||
handleStateChange({ mainArgs, onReload, onNoteOpen });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
//Общие аргументы
|
|
||||||
const mainArgs = {
|
|
||||||
NIDENT: pointInfo.NIDENT,
|
|
||||||
NSTEP: 4,
|
|
||||||
NEVENT: nEvent,
|
|
||||||
SEVENT_STAT: pointInfo.SEVENT_STAT
|
|
||||||
};
|
|
||||||
//Выполняем изменение статуса
|
|
||||||
handleStateChange({ mainArgs, onReload, onNoteOpen });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleEventRoutesPointExecutersOpen, handleStateChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При выполнении третьего шага
|
|
||||||
const handleMakeThirdStep = useCallback(
|
|
||||||
async ({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => {
|
|
||||||
//Выполняем переход на следующий шаг
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
|
||||||
args: {
|
|
||||||
NIDENT: pointInfo.NIDENT,
|
|
||||||
NSTEP: 3,
|
|
||||||
NPASS: pointInfo.NPASS
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//Выполняем выбор исполнителя
|
|
||||||
handleExecuterSelect({
|
|
||||||
nEvent,
|
|
||||||
pointInfo,
|
|
||||||
onReload,
|
|
||||||
onNoteOpen
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[executeStored, handleExecuterSelect]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При выполнении второго шага
|
|
||||||
const handleMakeSecondStep = useCallback(
|
|
||||||
async ({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => {
|
|
||||||
//Состояние параметров текущего действия
|
|
||||||
let currentPointInfo = { ...pointInfo };
|
|
||||||
//Выполняем переход на следующий шаг
|
|
||||||
const secondStep = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
|
||||||
args: {
|
|
||||||
NIDENT: currentPointInfo.NIDENT,
|
|
||||||
NSTEP: 2,
|
|
||||||
NPASS: currentPointInfo.NPASS
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//Устанавливаем признак необходимости выбора исполнителя
|
|
||||||
currentPointInfo.NSELECT_EXEC = secondStep.NSELECT_EXEC;
|
|
||||||
//Выполняем третий шаг
|
|
||||||
handleMakeThirdStep({ nEvent, pointInfo: currentPointInfo, onReload, onNoteOpen });
|
|
||||||
},
|
|
||||||
[executeStored, handleMakeThirdStep]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При выборе следующей точки события
|
|
||||||
const handleNextPointSelect = useCallback(
|
|
||||||
({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => {
|
|
||||||
//Состояние параметров текущего действия
|
|
||||||
let currentPointInfo = { ...pointInfo };
|
|
||||||
//Открываем раздел "Маршруты событий (точки перехода)" для выбора следующей точки
|
|
||||||
handleEventRoutesPointsPassessOpen({
|
|
||||||
sEventType: currentPointInfo.SEVENT_TYPE,
|
|
||||||
sEventStatus: currentPointInfo.SEVENT_STAT,
|
|
||||||
nPoint: currentPointInfo.NPOINT,
|
|
||||||
callBack: async point => {
|
|
||||||
//Устанавливаем полученную точку перехода
|
|
||||||
currentPointInfo.NPASS = point.outParameters.out_RN;
|
|
||||||
currentPointInfo.SEVENT_STAT = point.outParameters.out_NEXT_POINT;
|
|
||||||
//Выполняем второй шаг
|
|
||||||
handleMakeSecondStep({ nEvent, pointInfo: currentPointInfo, onReload, onNoteOpen });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[handleEventRoutesPointsPassessOpen, handleMakeSecondStep]
|
|
||||||
);
|
|
||||||
|
|
||||||
//По нажатию действия "Перейти"
|
|
||||||
const handleTaskStateChange = useCallback(
|
|
||||||
async ({ nEvent, sNextStat = null, onReload = null, onNoteOpen = null }) => {
|
|
||||||
//Выполняем инициализацию параметров
|
|
||||||
const eventInfo = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
|
||||||
args: {
|
|
||||||
NSTEP: 1,
|
|
||||||
NEVENT: nEvent,
|
|
||||||
SNEXT_STAT: sNextStat
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//Если информация о события проинициализирована
|
|
||||||
if (eventInfo) {
|
|
||||||
//Если следующий статус неопределен
|
|
||||||
if (!sNextStat) {
|
|
||||||
//Выполнение перехода с выбором точки
|
|
||||||
handleNextPointSelect({
|
|
||||||
nEvent,
|
|
||||||
pointInfo: eventInfo,
|
|
||||||
onReload,
|
|
||||||
onNoteOpen
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
//Выполняем второй шаг
|
|
||||||
handleMakeSecondStep({ nEvent, pointInfo: eventInfo, onReload, onNoteOpen });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[executeStored, handleMakeSecondStep, handleNextPointSelect]
|
|
||||||
);
|
|
||||||
|
|
||||||
return { handleTaskStateChange, handleTaskSend };
|
|
||||||
};
|
|
||||||
|
|
||||||
//Хук получения событий
|
|
||||||
const useTasks = (filterValues, ordersValues) => {
|
|
||||||
//Состояние событий
|
|
||||||
const [tasks, setTasks] = useState({
|
|
||||||
loaded: false,
|
|
||||||
rows: [],
|
|
||||||
reload: false,
|
|
||||||
accountsReload: false,
|
|
||||||
loadedAccounts: []
|
|
||||||
});
|
|
||||||
|
|
||||||
//Состояние вспомогательных функций событий
|
|
||||||
const { handleTaskStateChange } = useTasksFunctions();
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//Инициализация параметров события
|
|
||||||
const initTask = (id, task, avatar = null) => {
|
|
||||||
//Фильтруем доп. свойства
|
|
||||||
let newDocProps = Object.keys(task)
|
|
||||||
.filter(key => key.includes("DP_"))
|
|
||||||
.reduce((prev, key) => ({ ...prev, [key]: task[key] }), {});
|
|
||||||
//Возвращаем структуру события
|
|
||||||
return {
|
|
||||||
id: id,
|
|
||||||
avatar: avatar,
|
|
||||||
name: task.SPREF_NUMB,
|
|
||||||
nRn: task.NRN,
|
|
||||||
sCrn: "",
|
|
||||||
nCrn: task.NCRN,
|
|
||||||
nClosed: task.NCLOSED,
|
|
||||||
sPrefix: task.SEVPREF,
|
|
||||||
sNumber: task.SEVNUMB,
|
|
||||||
sType: task.SEVTYPE_CODE,
|
|
||||||
sStatus: task.SEVSTAT_NAME,
|
|
||||||
sDescription: task.SEVDESCR,
|
|
||||||
sClntClients: "",
|
|
||||||
sClntClnperson: "",
|
|
||||||
dchange_date: task.DCHANGE_DATE,
|
|
||||||
dStartDate: task.DREG_DATE,
|
|
||||||
dExpireDate: task.DEXPIRE_DATE,
|
|
||||||
dPlanDate: task.DPLAN_DATE,
|
|
||||||
sInitClnperson: task.SINIT_PERSON,
|
|
||||||
sInitUser: "",
|
|
||||||
sInitReason: "",
|
|
||||||
sToCompany: "",
|
|
||||||
sToDepartment: task.SSEND_DIVISION,
|
|
||||||
sToClnpost: "",
|
|
||||||
sToClnpsdep: "",
|
|
||||||
sToClnperson: task.SSEND_PERSON,
|
|
||||||
sToFcstaffgrp: "",
|
|
||||||
sToUser: "",
|
|
||||||
sToUsergrp: task.SSEND_USRGRP,
|
|
||||||
sSender: task.SSENDER,
|
|
||||||
sCurrentUser: "",
|
|
||||||
sLinkedUnit: task.SLINKED_UNIT,
|
|
||||||
nLinkedRn: task.NLINKED_RN,
|
|
||||||
docProps: newDocProps
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
//Взаимодействие с событием (через перенос)
|
|
||||||
const onDragEnd = useCallback(
|
|
||||||
({ path, eventPoints, openNoteDialog, destCode }) => {
|
|
||||||
//Определяем нужные параметры
|
|
||||||
const { source, destination } = path;
|
|
||||||
//Если путь не указан
|
|
||||||
if (!destination) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//Если происходит изменение статуса
|
|
||||||
if (destination.droppableId !== source.droppableId) {
|
|
||||||
//Конвертим ID переносимого события
|
|
||||||
let nDraggableTaskId = parseInt(path.draggableId);
|
|
||||||
//Считываем строку, у которой изменяется статус
|
|
||||||
let task = tasks.rows.find(r => r.id === nDraggableTaskId);
|
|
||||||
//Изменяем статус у события
|
|
||||||
task.statusId = parseInt(path.destination.droppableId);
|
|
||||||
//Получение настройки точки назначения
|
|
||||||
const pointSettings = eventPoints.find(eventPoint => eventPoint.SEVPOINT === destCode);
|
|
||||||
//Изменяем статус события с добавлением примечания
|
|
||||||
handleTaskStateChange({
|
|
||||||
nEvent: task.nRn,
|
|
||||||
sNextStat: destCode,
|
|
||||||
onReload: () => setTasks(pv => ({ ...pv, reload: true, accountsReload: true })),
|
|
||||||
onNoteOpen: pointSettings.ADDNOTE_ONCHST ? openNoteDialog : null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleTaskStateChange, tasks.rows]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При необходимости перезагрузки данных
|
|
||||||
useEffect(() => {
|
|
||||||
//Считывание данных с учетом фильтрации
|
|
||||||
let getTasks = async () => {
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_LOAD",
|
|
||||||
args: {
|
|
||||||
CFILTERS: {
|
|
||||||
VALUE: object2Base64XML(convertFilterValuesToArray(filterValues), { arrayNodeName: "filters" }),
|
|
||||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
|
||||||
},
|
|
||||||
CORDERS: { VALUE: object2Base64XML(ordersValues, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
|
||||||
NINCLUDE_ACCOUNTS: tasks.accountsReload ? 1 : 0
|
|
||||||
},
|
|
||||||
isArray: name => name === "XAGENTS",
|
|
||||||
respArg: "COUT"
|
|
||||||
});
|
|
||||||
//Считываем информацию о событиях
|
|
||||||
let events = data.XCLNEVENTS.XDATA.XDATA_GRID;
|
|
||||||
//Считываем иноформацию о контрагентах
|
|
||||||
let accounts = tasks.accountsReload ? [...(data.XAGENTS_WITH_IMG.XAGENTS || [])] : tasks.loadedAccounts;
|
|
||||||
//Инициализируем события
|
|
||||||
let newRows = [];
|
|
||||||
//Если есть события
|
|
||||||
if (events.rows) {
|
|
||||||
//Формируем структуру событий
|
|
||||||
newRows = [...(events.rows || [])].reduce(
|
|
||||||
(prev, cur) => [...prev, initTask(prev.length, cur, accounts.find(agent => agent.SAGNABBR === cur.SSENDER)?.BIMAGE)],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
//Возвращаем информацию
|
|
||||||
return { rows: [...newRows], loadedAccounts: accounts };
|
|
||||||
};
|
|
||||||
//Считывание данных
|
|
||||||
let getData = async () => {
|
|
||||||
//Считываем информацию о задачах
|
|
||||||
let eventTasks = await getTasks();
|
|
||||||
//Загружаем данные
|
|
||||||
setTasks(pv => ({
|
|
||||||
...pv,
|
|
||||||
loaded: true,
|
|
||||||
rows: eventTasks.rows,
|
|
||||||
loadedAccounts: eventTasks.loadedAccounts,
|
|
||||||
reload: false,
|
|
||||||
accountsReload: false
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
//Если необходимо загрузить данные и указан тип событий и загружены все необходимые вспомогательные данные
|
|
||||||
if (tasks.reload) {
|
|
||||||
//Загружаем данные
|
|
||||||
getData();
|
|
||||||
}
|
|
||||||
}, [SERV_DATA_TYPE_CLOB, executeStored, filterValues, ordersValues, tasks.accountsReload, tasks.loadedAccounts, tasks.reload]);
|
|
||||||
|
|
||||||
return [tasks, setTasks, onDragEnd];
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useTasksFunctions, useTasks };
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Панель мониторинга: Точка входа
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export const RootClass = ClntTaskBoard;
|
|
||||||
@ -1,303 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Дополнительная разметка и вёрстка клиентских элементов
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Перечисление "Состояние события"
|
|
||||||
export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" });
|
|
||||||
|
|
||||||
//Допустимые значение поля сортировки
|
|
||||||
export const sortAttrs = [
|
|
||||||
{ id: "SEVNSTAT_CODE", descr: "Мнемокод статуса" },
|
|
||||||
{ id: "SEVNSTAT_NAME", descr: "Наименование статуса" },
|
|
||||||
{ id: "SEVPOINT_DESCR", descr: "Описание точки маршрута" }
|
|
||||||
];
|
|
||||||
|
|
||||||
//Допустимые значения направления сортировки
|
|
||||||
export const sortDest = [];
|
|
||||||
sortDest[-1] = "desc";
|
|
||||||
sortDest[1] = "asc";
|
|
||||||
|
|
||||||
//Цвета статусов
|
|
||||||
export const COLORS = [
|
|
||||||
"mediumSlateBlue",
|
|
||||||
"lightSalmon",
|
|
||||||
"fireBrick",
|
|
||||||
"orange",
|
|
||||||
"gold",
|
|
||||||
"limeGreen",
|
|
||||||
"yellowGreen",
|
|
||||||
"mediumAquaMarine",
|
|
||||||
"paleTurquoise",
|
|
||||||
"steelBlue",
|
|
||||||
"skyBlue",
|
|
||||||
"tan"
|
|
||||||
];
|
|
||||||
|
|
||||||
//Перечисление "Цвет задачи"
|
|
||||||
export const TASK_COLORS = Object.freeze({ EXPIRED: "#ff0000", EXPIRES_SOON: "#ffdf00", LINKED: "#1e90ff" });
|
|
||||||
|
|
||||||
//Перечисление Доп. свойства "Значение по умолчанию"
|
|
||||||
export const DP_DEFAULT_VALUE = Object.freeze({ 0: "SDEFAULT_STR", 1: "NDEFAULT_NUM", 2: "DDEFAULT_DATE", 3: "NDEFAULT_NUM" });
|
|
||||||
//Перечисление Доп. свойства "Префикс формата данных"
|
|
||||||
export const DP_TYPE_PREFIX = Object.freeze({ 0: "S", 1: "N", 2: "D", 3: "N" });
|
|
||||||
//Перечисление Доп. свойства "Входящее значение дополнительного словаря"
|
|
||||||
export const DP_IN_VALUE = Object.freeze({ 0: "pos_str_value", 1: "pos_num_value", 2: "pos_date_value", 3: "pos_num_value" });
|
|
||||||
//Перечисление Доп. свойства "Исходящее значение дополнительного словаря"
|
|
||||||
export const DP_RETURN_VALUE = Object.freeze({ 0: "str_value", 1: "num_value", 2: "date_value", 3: "num_value" });
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Формирование массива из 0, 1 и более элементов
|
|
||||||
export const makeArray = arr => {
|
|
||||||
return arr ? (arr.length ? arr : [arr]) : [];
|
|
||||||
};
|
|
||||||
|
|
||||||
//Конвертация формата HEX в формат RGB
|
|
||||||
const convertHexToRGB = hex => {
|
|
||||||
let r = parseInt(hex.slice(1, 3), 16);
|
|
||||||
let g = parseInt(hex.slice(3, 5), 16);
|
|
||||||
let b = parseInt(hex.slice(5, 7), 16);
|
|
||||||
let a = 0.5;
|
|
||||||
r = Math.round((a * (r / 255) + a * (255 / 255)) * 255);
|
|
||||||
g = Math.round((a * (g / 255) + a * (255 / 255)) * 255);
|
|
||||||
b = Math.round((a * (b / 255) + a * (255 / 255)) * 255);
|
|
||||||
return "rgb(" + r + ", " + g + ", " + b + ")";
|
|
||||||
};
|
|
||||||
|
|
||||||
//Считывание заливки события по условию
|
|
||||||
export const getTaskBgColorByRule = (task, colorRule) => {
|
|
||||||
//Исходя из типа определяем наименование и возвращаем цвет заливки
|
|
||||||
switch (colorRule.STYPE) {
|
|
||||||
case "number":
|
|
||||||
return (!colorRule.fromValue || Number(task.docProps[`N${colorRule.SFIELD}`]) >= Number(colorRule.fromValue)) &&
|
|
||||||
(!colorRule.toValue || Number(task.docProps[`N${colorRule.SFIELD}`]) <= Number(colorRule.toValue))
|
|
||||||
? convertHexToRGB(colorRule.SCOLOR)
|
|
||||||
: null;
|
|
||||||
default:
|
|
||||||
return task.docProps[`S${colorRule.SFIELD}`] == colorRule.fromValue ? convertHexToRGB(colorRule.SCOLOR) : null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Индикация истечения срока отработки события
|
|
||||||
export const getTaskExpiredColor = task => {
|
|
||||||
//Определяем текущую дату
|
|
||||||
let sysDate = new Date();
|
|
||||||
//Определяем дату истечения срока события
|
|
||||||
let expireDate = task.dExpireDate ? new Date(task.dExpireDate) : null;
|
|
||||||
//Если дата истечения срока определена
|
|
||||||
if (expireDate) {
|
|
||||||
//Определяем разницу между датами
|
|
||||||
let daysDiff = ((expireDate.getTime() - sysDate.getTime()) / (1000 * 60 * 60 * 24)).toFixed(2);
|
|
||||||
//Если разница меньше 0 - срок истечен
|
|
||||||
if (daysDiff < 0) return TASK_COLORS.EXPIRED;
|
|
||||||
//Если разница меньше 4 - скоро истечет
|
|
||||||
if (daysDiff < 4) return TASK_COLORS.EXPIRES_SOON;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Цвет из hsl формата в rgba формат
|
|
||||||
const convertHslToRgba = (h, s, l) => {
|
|
||||||
s /= 100;
|
|
||||||
l /= 100;
|
|
||||||
const k = n => (n + h / 30) % 12;
|
|
||||||
const a = s * Math.min(l, 1 - l);
|
|
||||||
const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
||||||
return `rgba(${Math.floor(255 * f(0))},${Math.floor(255 * f(8))},${Math.floor(255 * f(4))},0.3)`;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Формирование случайного цвета
|
|
||||||
export const getRandomColor = index => {
|
|
||||||
const hue = index * 137.508;
|
|
||||||
return convertHslToRgba(hue, 50, 70);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Формат дополнительного свойства типа число (длина, точность)
|
|
||||||
const formatRegExpNum = (width, precision) =>
|
|
||||||
new RegExp("^(\\d{1," + (width - precision) + "}" + (precision > 0 ? "((\\.|,)\\d{1," + precision + "})?" : "") + ")?$");
|
|
||||||
|
|
||||||
//Формат дополнительного свойства типа строка (длина)
|
|
||||||
const formatRegExpStr = length => new RegExp("^.{0," + length + "}$");
|
|
||||||
|
|
||||||
//Проверка валидности числа
|
|
||||||
const isValidNum = (width, precision, value) => {
|
|
||||||
return formatRegExpNum(width, precision).test(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Проверка валидности строки
|
|
||||||
const isValidStr = (length, value) => {
|
|
||||||
return formatRegExpStr(length).test(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Признак ошибки валидации
|
|
||||||
export const validationError = (value = "", format, numWidth, numPrecision, strLength) => {
|
|
||||||
//Исходим от формата
|
|
||||||
switch (format) {
|
|
||||||
//Проверка строки
|
|
||||||
case 0:
|
|
||||||
return isValidStr(strLength, value);
|
|
||||||
//Проверка числа
|
|
||||||
case 1:
|
|
||||||
return isValidNum(numWidth, numPrecision, value);
|
|
||||||
//Остальное не проверяем
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Конвертация времени в привычный формат
|
|
||||||
export const formatSqlDate = timeStamp => {
|
|
||||||
//Если есть разделитель
|
|
||||||
if (timeStamp.indexOf(".") !== -1) {
|
|
||||||
//Определяем секунды
|
|
||||||
let seconds = 24 * 60 * 60 * timeStamp;
|
|
||||||
//Определяем часы
|
|
||||||
const hours = Math.trunc(seconds / (60 * 60));
|
|
||||||
//Переопределяем секунды
|
|
||||||
seconds = seconds % (60 * 60);
|
|
||||||
//Определяем минуты
|
|
||||||
const minutes = Math.trunc(seconds / 60);
|
|
||||||
//Определяем остаток секунд
|
|
||||||
seconds = Math.round(seconds % 60);
|
|
||||||
//Форматируем
|
|
||||||
const formattedTime = ("0" + hours).slice(-2) + ":" + ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2);
|
|
||||||
//Возвращаем результат
|
|
||||||
return formattedTime;
|
|
||||||
}
|
|
||||||
return timeStamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Считывание значений из локального хранилища
|
|
||||||
export const getLocalStorageValue = (sName, defaultValue = null) => localStorage.getItem(sName) || defaultValue;
|
|
||||||
|
|
||||||
//Форматирование фильтра в массив для отбора
|
|
||||||
export const convertFilterValuesToArray = filterValues => {
|
|
||||||
//Инициализируем значение "с" состояния ("Все", "Не аннулированные" - 0, "Аннулированые" - 1)
|
|
||||||
let nClosedFrom = filterValues.sState ? ([EVENT_STATES[0], EVENT_STATES[1]].includes(filterValues.sState) ? 0 : 1) : 0;
|
|
||||||
//Инициализируем значение "по" состояния ("Все", "Аннулированные" - 1, "Не аннулированные" - 0)
|
|
||||||
let nClosedTo = filterValues.sState ? ([EVENT_STATES[0], EVENT_STATES[2]].includes(filterValues.sState) ? 1 : 0) : 0;
|
|
||||||
//Формируем массив значений фильтра
|
|
||||||
let filterValuesArray = [
|
|
||||||
{ name: "NCLOSED", from: nClosedFrom, to: nClosedTo },
|
|
||||||
{ name: "SEVTYPE_CODE", from: filterValues.sType, to: null },
|
|
||||||
{ name: "NCRN", from: filterValues.sCrnRnList, to: null },
|
|
||||||
{ name: "SSEND_PERSON", from: filterValues.sSendPerson, to: null },
|
|
||||||
{ name: "SSEND_DIVISION", from: filterValues.sSendDivision, to: null },
|
|
||||||
{ name: "SSEND_USRGRP", from: filterValues.sSendUsrGrp, to: null },
|
|
||||||
{ name: "NLINKED_RN", from: filterValues.sDocLink, to: null }
|
|
||||||
];
|
|
||||||
return filterValuesArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Формирование массива действий карточки события
|
|
||||||
export const makeCardActionsArray = (onEdit, onEditClient, onDelete, onStateChange, onReturn, onSend, onNotesOpen, onFileLinksOpen, onMove) => {
|
|
||||||
//Формируем список действий карточки
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
method: "EDIT",
|
|
||||||
name: "Исправить",
|
|
||||||
icon: "edit",
|
|
||||||
visible: false,
|
|
||||||
delimiter: false,
|
|
||||||
tasksReload: false,
|
|
||||||
needAccountsReload: false,
|
|
||||||
disableClosed: false,
|
|
||||||
func: onEdit
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: "EDIT_CLIENT",
|
|
||||||
name: "Исправить в разделе",
|
|
||||||
icon: "edit_note",
|
|
||||||
visible: true,
|
|
||||||
delimiter: false,
|
|
||||||
tasksReload: false,
|
|
||||||
needAccountsReload: false,
|
|
||||||
disableClosed: false,
|
|
||||||
func: onEditClient
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: "MOVE",
|
|
||||||
name: "Переместить",
|
|
||||||
icon: "drive_file_move",
|
|
||||||
visible: true,
|
|
||||||
delimiter: false,
|
|
||||||
tasksReload: true,
|
|
||||||
needAccountsReload: false,
|
|
||||||
disableClosed: false,
|
|
||||||
func: onMove
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
name: "Удалить",
|
|
||||||
icon: "delete",
|
|
||||||
visible: true,
|
|
||||||
delimiter: true,
|
|
||||||
tasksReload: true,
|
|
||||||
needAccountsReload: false,
|
|
||||||
disableClosed: false,
|
|
||||||
func: onDelete
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: "TASK_STATE_CHANGE",
|
|
||||||
name: "Перейти",
|
|
||||||
icon: "turn_right",
|
|
||||||
visible: true,
|
|
||||||
delimiter: false,
|
|
||||||
tasksReload: true,
|
|
||||||
needAccountsReload: true,
|
|
||||||
disableClosed: true,
|
|
||||||
func: onStateChange
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: "TASK_RETURN",
|
|
||||||
name: "Выполнить возврат",
|
|
||||||
icon: "turn_left",
|
|
||||||
visible: true,
|
|
||||||
delimiter: false,
|
|
||||||
tasksReload: true,
|
|
||||||
needAccountsReload: true,
|
|
||||||
disableClosed: true,
|
|
||||||
func: onReturn
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: "TASK_SEND",
|
|
||||||
name: "Направить",
|
|
||||||
icon: "send",
|
|
||||||
visible: true,
|
|
||||||
delimiter: true,
|
|
||||||
tasksReload: true,
|
|
||||||
needAccountsReload: true,
|
|
||||||
disableClosed: true,
|
|
||||||
func: onSend
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: "NOTES",
|
|
||||||
name: "Примечания",
|
|
||||||
icon: "event_note",
|
|
||||||
visible: true,
|
|
||||||
delimiter: true,
|
|
||||||
tasksReload: false,
|
|
||||||
needAccountsReload: false,
|
|
||||||
disableClosed: false,
|
|
||||||
func: onNotesOpen
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: "FILE_LINKS",
|
|
||||||
name: "Присоединенные документы",
|
|
||||||
icon: "attach_file",
|
|
||||||
visible: true,
|
|
||||||
delimiter: false,
|
|
||||||
tasksReload: false,
|
|
||||||
needAccountsReload: false,
|
|
||||||
disableClosed: false,
|
|
||||||
func: onFileLinksOpen
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент: Общие стили
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Общие стили
|
|
||||||
export const COMMON_STYLES = {
|
|
||||||
TASK_FORM_TEXT_FIELD: (widthVal, greyDisabled = false) => ({
|
|
||||||
margin: "4px",
|
|
||||||
...(widthVal ? { width: widthVal } : {}),
|
|
||||||
...(greyDisabled
|
|
||||||
? {
|
|
||||||
"& .MuiInputBase-input.Mui-disabled": {
|
|
||||||
WebkitTextFillColor: "rgba(0, 0, 0, 0.87)"
|
|
||||||
},
|
|
||||||
"& .MuiInputLabel-root.Mui-disabled": {
|
|
||||||
WebkitTextFillColor: "rgba(0, 0, 0, 0.6)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
}),
|
|
||||||
BOX_WITH_LEGEND: { border: "1px solid #939393", marginBottom: "1px" },
|
|
||||||
BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" },
|
|
||||||
LEGEND: { textAlign: "left" },
|
|
||||||
SELECT_MENU: width => {
|
|
||||||
return { overflowY: "auto", ...APP_STYLES.SCROLL, width: width ? width : null };
|
|
||||||
},
|
|
||||||
STACK_DOCLINKS: { alignItems: "baseline" },
|
|
||||||
SCROLL: { ...APP_STYLES.SCROLL, overflowY: "auto" },
|
|
||||||
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" },
|
|
||||||
DIALOG_CLOSE_BUTTON: {
|
|
||||||
position: "absolute",
|
|
||||||
right: 8,
|
|
||||||
top: 8,
|
|
||||||
color: theme => theme.palette.grey[500]
|
|
||||||
},
|
|
||||||
ZERO_PADDING: { padding: 0 }
|
|
||||||
};
|
|
||||||
@ -1,176 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
|
||||||
Компонент панели: Диалог формы события
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState, useCallback, useContext, useEffect } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { useClientEvent } from "./hooks/task_dialog_hooks"; //Хук для события
|
|
||||||
import { useDocsProps } from "./hooks/task_dialog_hooks"; //Хук для получения доп. свойств раздела "События"
|
|
||||||
import { TaskForm } from "./components/task_form"; //Форма события
|
|
||||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
|
|
||||||
import { hasValue } from "../../core/utils"; //Вспомогательные процедуры и функции
|
|
||||||
import { P8PDialog } from "../../components/p8p_dialog"; //Типовой диалог
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Диалог формы события
|
|
||||||
const TaskDialog = ({ taskRn, taskType, editable, onTasksReload, onClose }) => {
|
|
||||||
//Собственное состояние
|
|
||||||
const [task, setTask] = useClientEvent(taskRn, taskType);
|
|
||||||
|
|
||||||
//Состояние допустимых дополнительных свойств
|
|
||||||
const [docProps] = useDocsProps(taskType);
|
|
||||||
|
|
||||||
//Состояние заполненности всех обязательных доп. свойств
|
|
||||||
const [docPropsReady, setDocPropsReady] = useState(false);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
|
||||||
|
|
||||||
//При изменении информации о задаче
|
|
||||||
const handleTaskChange = useCallback(
|
|
||||||
newTaskValues => {
|
|
||||||
setTask(pv => ({ ...pv, ...newTaskValues }));
|
|
||||||
},
|
|
||||||
[setTask]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При добавлении события
|
|
||||||
const handleInsertTask = async callBack => {
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INSERT",
|
|
||||||
args: {
|
|
||||||
SCRN: task.sCrn,
|
|
||||||
SPREF: task.sPrefix,
|
|
||||||
SNUMB: task.sNumber,
|
|
||||||
STYPE: task.sType,
|
|
||||||
SSTATUS: task.sStatus,
|
|
||||||
SPLAN_DATE: task.dPlanDate,
|
|
||||||
SINIT_PERSON: task.sInitClnperson,
|
|
||||||
SCLIENT_CLIENT: task.sClntClients,
|
|
||||||
SCLIENT_PERSON: task.sClntClnperson,
|
|
||||||
SDESCRIPTION: task.sDescription,
|
|
||||||
SREASON: task.sInitReason,
|
|
||||||
CPROPS: {
|
|
||||||
VALUE: object2Base64XML(
|
|
||||||
[
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(task.docProps)
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
.filter(([_, v]) => v != (null || ""))
|
|
||||||
)
|
|
||||||
],
|
|
||||||
{
|
|
||||||
arrayNodeName: "props"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
callBack();
|
|
||||||
};
|
|
||||||
|
|
||||||
//При исправлении события
|
|
||||||
const handleUpdateEvent = async callBack => {
|
|
||||||
await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_UPDATE",
|
|
||||||
args: {
|
|
||||||
NCLNEVENTS: task.nRn,
|
|
||||||
SCLIENT_CLIENT: task.sClntClients,
|
|
||||||
SCLIENT_PERSON: task.sClntClnperson,
|
|
||||||
SDESCRIPTION: task.sDescription,
|
|
||||||
CPROPS: {
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
VALUE: object2Base64XML([Object.fromEntries(Object.entries(task.docProps).filter(([_, v]) => v != (null || "")))], {
|
|
||||||
arrayNodeName: "props"
|
|
||||||
}),
|
|
||||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
callBack();
|
|
||||||
};
|
|
||||||
|
|
||||||
//При считывании следующего номера события
|
|
||||||
const handleEventNextNumbGet = useCallback(async () => {
|
|
||||||
//Считываем данные
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_NEXTNUMB_GET",
|
|
||||||
args: {
|
|
||||||
SPREFIX: task.sPrefix
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//Если данные есть
|
|
||||||
if (data) {
|
|
||||||
//Устанавливаем номер
|
|
||||||
setTask(pv => ({ ...pv, sNumber: data.SEVENT_NUMB }));
|
|
||||||
}
|
|
||||||
}, [executeStored, setTask, task.sPrefix]);
|
|
||||||
|
|
||||||
//Проверка заполненности всех обязательных доп. свойств
|
|
||||||
useEffect(() => {
|
|
||||||
//Если доп. свойства загрузились
|
|
||||||
if (docProps.loaded) {
|
|
||||||
//Проверяем остались ли обязательные незаполненные свойства
|
|
||||||
let notFilled = docProps.props.some(docProp => docProp.BREQUIRE === true && !hasValue(task.docProps[docProp.SFORMATTED_ID]));
|
|
||||||
//Если незаполненных обязательных доп. свойств не осталось - доп. свойства готовы, иначе не готовы
|
|
||||||
setDocPropsReady(!notFilled);
|
|
||||||
} else {
|
|
||||||
//Доп. свойства не готовы
|
|
||||||
setDocPropsReady(false);
|
|
||||||
}
|
|
||||||
}, [docProps.loaded, docProps.props, task.docProps]);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!task.init && docProps.loaded && (
|
|
||||||
<P8PDialog
|
|
||||||
title={task.nRn ? `Исправление события${task.nClosed ? " [аннулировано]" : ""}` : "Добавление события"}
|
|
||||||
fullWidth={true}
|
|
||||||
onOk={() => (taskRn ? handleUpdateEvent(onClose).then(onTasksReload) : handleInsertTask(onClose).then(onTasksReload))}
|
|
||||||
onClose={onClose ? onClose : null}
|
|
||||||
okDisabled={taskRn ? task.updateDisabled || !editable || !docPropsReady : task.insertDisabled || !docPropsReady}
|
|
||||||
scrollContent={false}
|
|
||||||
>
|
|
||||||
<TaskForm
|
|
||||||
task={task}
|
|
||||||
taskType={taskType}
|
|
||||||
editable={!taskRn || editable ? true : false}
|
|
||||||
docProps={docProps.props}
|
|
||||||
onTaskChange={handleTaskChange}
|
|
||||||
onEventNextNumbGet={handleEventNextNumbGet}
|
|
||||||
/>
|
|
||||||
</P8PDialog>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Диалог формы события
|
|
||||||
TaskDialog.propTypes = {
|
|
||||||
taskRn: PropTypes.number,
|
|
||||||
taskType: PropTypes.string.isRequired,
|
|
||||||
editable: PropTypes.bool,
|
|
||||||
onTasksReload: PropTypes.func.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { TaskDialog };
|
|
||||||
@ -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
|
||||||
}));
|
}));
|
||||||
@ -213,7 +214,7 @@ const EqsPrfrm = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (data.NIDENT) {
|
if (data.NIDENT) {
|
||||||
if (type == 0) pOnlineShowUnit({ unitCode: "EquipTechServices", inputParameters: [{ name: "in_Ident", value: data.NIDENT }] });
|
if (type == 0) pOnlineShowUnit({ unitCode: "EquipTechServices", inputParameters: [{ name: "in_SelectList_Ident", value: data.NIDENT }] });
|
||||||
else pOnlineShowUnit({ unitCode: "EquipRepairSheets", inputParameters: [{ name: "in_Ident", value: data.NIDENT }] });
|
else pOnlineShowUnit({ unitCode: "EquipRepairSheets", inputParameters: [{ name: "in_Ident", value: data.NIDENT }] });
|
||||||
} else showMsgErr(TEXTS.NO_DATA_FOUND);
|
} else showMsgErr(TEXTS.NO_DATA_FOUND);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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,7 +240,6 @@ 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}
|
||||||
@ -263,11 +260,6 @@ const MechRecAssemblyMon = () => {
|
|||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<Typography variant="h4" sx={STYLES.MAIN_TITLE}>
|
|
||||||
В каталоге планов отсутствуют записи подходящих первичных документов
|
|
||||||
</Typography>
|
|
||||||
)
|
|
||||||
) : (
|
) : (
|
||||||
<Typography variant="h4" sx={STYLES.MAIN_TITLE}>
|
<Typography variant="h4" sx={STYLES.MAIN_TITLE}>
|
||||||
Укажите каталог планов для отображения спецификаций
|
Укажите каталог планов для отображения спецификаций
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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" }
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 };
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
|
|
||||||
Панель мониторинга: Точка входа
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { MechRecCostJobs } from "./mech_rec_cost_jobs_manage_mp"; //Корневая панель выдачи сменного задания на участок
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export const RootClass = MechRecCostJobs;
|
|
||||||
@ -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 };
|
|
||||||
@ -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 };
|
|
||||||
@ -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
|
||||||
|
|||||||
@ -36,60 +36,43 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardActions,
|
CardActions
|
||||||
Tooltip
|
|
||||||
} from "@mui/material"; //Интерфейсные элементы
|
} from "@mui/material"; //Интерфейсные элементы
|
||||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
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";
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
//Склонения для документов
|
//Склонения для документов
|
||||||
const PLANS_DECLINATIONS = ["план", "плана", "планов"];
|
const DECLINATIONS = ["план", "плана", "планов"];
|
||||||
const SPEC_DECLINATIONS = ["элемент", "элемента", "элементов"];
|
|
||||||
|
|
||||||
//Поля сортировки
|
//Поля сортировки
|
||||||
const SORT_REP_DATE = "DREP_DATE";
|
const SORT_REP_DATE = "DREP_DATE";
|
||||||
const SORT_REP_DATE_TO = "DREP_DATE_TO";
|
const SORT_REP_DATE_TO = "DREP_DATE_TO";
|
||||||
|
|
||||||
//Максимальное количество элементов
|
|
||||||
const MAX_TASKS = 10000;
|
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
|
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
|
||||||
PLANS_CHECKBOX_HAVEDOCS: { alignContent: "space-around" },
|
PLANS_CHECKBOX_HAVEDOCS: { alignContent: "space-around" },
|
||||||
PLANS_LIST_CONTAINER: { height: "100%", display: "flex", flexDirection: "column", justifyContent: "space-between" },
|
|
||||||
PLANS_LIST_ITEM_ZERODOCS: { backgroundColor: "#ebecec" },
|
PLANS_LIST_ITEM_ZERODOCS: { backgroundColor: "#ebecec" },
|
||||||
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
|
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
|
||||||
PLANS_LIST_ITEM_SECONDARY: { wordWrap: "break-word", fontSize: "0.6rem", textTransform: "uppercase" },
|
PLANS_LIST_ITEM_SECONDARY: { wordWrap: "break-word", fontSize: "0.6rem", textTransform: "uppercase" },
|
||||||
PLANS_LIST_ITEM_PLAN: {
|
|
||||||
backgroundColor: "#c7e7f1",
|
|
||||||
"&:hover": { backgroundColor: `#c7e7f1`, filter: "brightness(0.92) !important" }
|
|
||||||
},
|
|
||||||
PLANS_LIST_ITEM_PLAN_FIELD: {
|
|
||||||
marginLeft: "15px"
|
|
||||||
},
|
|
||||||
PLANS_LIST_FILTER_CONTAINER: { height: "calc(100% - 55px)", overflowY: "auto" },
|
|
||||||
PLANS_LIST_BUTTONS_CONTAINER: { display: "flex", justifyContent: "space-around", paddingBottom: "10px", height: "45px" },
|
|
||||||
PLANS_LIST_BUTTON: { minWidth: "125px", height: "35px" },
|
|
||||||
PLANS_BUTTON: { position: "absolute", top: `calc(${APP_BAR_HEIGHT} + 16px)`, left: "16px" },
|
PLANS_BUTTON: { position: "absolute", top: `calc(${APP_BAR_HEIGHT} + 16px)`, left: "16px" },
|
||||||
PLANS_DRAWER: {
|
PLANS_DRAWER: {
|
||||||
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" },
|
||||||
@ -100,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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
@ -133,13 +97,11 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
//Форматирование для отображения количества документов
|
//Форматирование для отображения количества документов
|
||||||
const formatCountDocs = (nCountDocs, nType = 0) => {
|
const formatCountDocs = nCountDocs => {
|
||||||
//Склонение документов
|
|
||||||
let DECLINATIONS = nType === 0 ? PLANS_DECLINATIONS : SPEC_DECLINATIONS;
|
|
||||||
//Получаем последнюю цифру в значении
|
//Получаем последнюю цифру в значении
|
||||||
let num = (nCountDocs % 100) % 10;
|
let num = (nCountDocs % 100) % 10;
|
||||||
//Документов
|
//Документов
|
||||||
@ -152,43 +114,11 @@ const formatCountDocs = (nCountDocs, nType = 0) => {
|
|||||||
return `${nCountDocs} ${DECLINATIONS[2]}`;
|
return `${nCountDocs} ${DECLINATIONS[2]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
//Изменение информации об отмеченных планах
|
|
||||||
const updateCtlgPlanInfo = (selectedPlans, plan) => {
|
|
||||||
//Результат изменений
|
|
||||||
let res = { selectedPlans: [...selectedPlans] || [], selectedPlansElements: plan.NCOUNT_DOCS };
|
|
||||||
//Определяем наличие в отмеченных планах
|
|
||||||
let selectedIndex = res.selectedPlans.indexOf(plan.NRN);
|
|
||||||
//Если уже есть в отмеченных - удаляем, нет - добавляем
|
|
||||||
if (selectedIndex > -1) {
|
|
||||||
//Удаляем план из выбранных
|
|
||||||
res.selectedPlans.splice(selectedIndex, 1);
|
|
||||||
//Переворачиваем сумму документов
|
|
||||||
res.selectedPlansElements = res.selectedPlansElements * -1;
|
|
||||||
} else {
|
|
||||||
//Добавляем план в выбранные
|
|
||||||
res.selectedPlans.push(plan.NRN);
|
|
||||||
}
|
|
||||||
//Возвращаем результат
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Список каталогов планов
|
//Список каталогов планов
|
||||||
const PlanCtlgsList = ({
|
const PlanCtlgsList = ({ planCtlgs = [], selectedPlanCtlg, filter, setFilter, onClick } = {}) => {
|
||||||
planCtlgs = [],
|
|
||||||
selectedPlans = [],
|
|
||||||
selectedPlanCtlg,
|
|
||||||
selectedPlansElements,
|
|
||||||
filter,
|
|
||||||
setFilter,
|
|
||||||
onCtlgClick,
|
|
||||||
onCtlgPlanClick,
|
|
||||||
onCtlgPlansOk,
|
|
||||||
onCtlgPlansCancel
|
|
||||||
} = {}) => {
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<Box sx={STYLES.PLANS_LIST_CONTAINER}>
|
<div>
|
||||||
<Box sx={STYLES.PLANS_LIST_FILTER_CONTAINER}>
|
|
||||||
<TextField
|
<TextField
|
||||||
sx={STYLES.PLANS_FINDER}
|
sx={STYLES.PLANS_FINDER}
|
||||||
name="planFilter"
|
name="planFilter"
|
||||||
@ -202,96 +132,37 @@ const PlanCtlgsList = ({
|
|||||||
></TextField>
|
></TextField>
|
||||||
<FormGroup sx={STYLES.PLANS_CHECKBOX_HAVEDOCS}>
|
<FormGroup sx={STYLES.PLANS_CHECKBOX_HAVEDOCS}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={<Checkbox checked={filter.haveDocs} onChange={event => setFilter(pv => ({ ...pv, haveDocs: event.target.checked }))} />}
|
||||||
<Checkbox checked={filter.haveDocs} onChange={event => setFilter(pv => ({ ...pv, haveDocs: event.target.checked }))} />
|
|
||||||
}
|
|
||||||
label="Только с планами"
|
label="Только с планами"
|
||||||
labelPlacement="end"
|
labelPlacement="end"
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<List>
|
<List>
|
||||||
{planCtlgs.map(ctlg => (
|
{planCtlgs.map(p => (
|
||||||
<Box key={ctlg.NRN}>
|
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
sx={ctlg.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : null}
|
sx={p.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : null}
|
||||||
key={ctlg.NRN}
|
key={p.NRN}
|
||||||
selected={ctlg.NRN === selectedPlanCtlg}
|
selected={p.NRN === selectedPlanCtlg}
|
||||||
onClick={() => (onCtlgClick ? onCtlgClick(ctlg) : null)}
|
onClick={() => (onClick ? onClick(p) : null)}
|
||||||
disabled={ctlg.NCOUNT_DOCS == 0}
|
|
||||||
>
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{ctlg.SNAME}</Typography>}
|
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{p.SNAME}</Typography>}
|
||||||
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(ctlg.NCOUNT_DOCS, 0)}</Typography>}
|
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(p.NCOUNT_DOCS)}</Typography>}
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
{ctlg.NRN === selectedPlanCtlg && ctlg.XCRN_PLANS.length > 1
|
|
||||||
? ctlg.XCRN_PLANS.map(plan => (
|
|
||||||
<ListItemButton
|
|
||||||
sx={plan.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : STYLES.PLANS_LIST_ITEM_PLAN}
|
|
||||||
key={plan.NRN}
|
|
||||||
disabled={plan.NCOUNT_DOCS == 0}
|
|
||||||
onClick={() => (onCtlgPlanClick ? onCtlgPlanClick(plan) : null)}
|
|
||||||
>
|
|
||||||
<ListItemText
|
|
||||||
sx={STYLES.PLANS_LIST_ITEM_PLAN_FIELD}
|
|
||||||
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{plan.SNAME}</Typography>}
|
|
||||||
secondary={
|
|
||||||
<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>
|
|
||||||
{formatCountDocs(plan.NCOUNT_DOCS, 1)}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{plan.NCOUNT_DOCS !== 0 ? <Checkbox checked={selectedPlans.includes(plan.NRN)} /> : null}
|
|
||||||
</ListItemButton>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</Box>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</Box>
|
</div>
|
||||||
<Box sx={STYLES.PLANS_LIST_BUTTONS_CONTAINER}>
|
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
!selectedPlanCtlg
|
|
||||||
? "Не выбран каталог планов"
|
|
||||||
: selectedPlans.length === 0
|
|
||||||
? "Не выбраны планы каталога"
|
|
||||||
: selectedPlansElements > MAX_TASKS
|
|
||||||
? `Выбранные планы превышают максимум элементов (выбрано: ${selectedPlansElements}, максимум: ${MAX_TASKS})`
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Button
|
|
||||||
sx={STYLES.PLANS_LIST_BUTTON}
|
|
||||||
variant="contained"
|
|
||||||
disabled={selectedPlans.length === 0 || selectedPlansElements > MAX_TASKS}
|
|
||||||
onClick={onCtlgPlansOk}
|
|
||||||
>
|
|
||||||
Применить
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
<Button sx={STYLES.PLANS_LIST_BUTTON} variant="contained" onClick={onCtlgPlansCancel}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Контроль свойств - Список каталогов планов
|
//Контроль свойств - Список каталогов планов
|
||||||
PlanCtlgsList.propTypes = {
|
PlanCtlgsList.propTypes = {
|
||||||
planCtlgs: PropTypes.array,
|
planCtlgs: PropTypes.array,
|
||||||
selectedPlans: PropTypes.array,
|
|
||||||
selectedPlanCtlg: PropTypes.number,
|
selectedPlanCtlg: PropTypes.number,
|
||||||
selectedPlansElements: PropTypes.number,
|
onClick: PropTypes.func,
|
||||||
onCtlgClick: PropTypes.func,
|
|
||||||
onCtlgPlanClick: PropTypes.func,
|
|
||||||
filter: PropTypes.object,
|
filter: PropTypes.object,
|
||||||
setFilter: PropTypes.func,
|
setFilter: PropTypes.func
|
||||||
onCtlgPlansOk: PropTypes.func,
|
|
||||||
onCtlgPlansCancel: PropTypes.func
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Генерация диалога задачи
|
//Генерация диалога задачи
|
||||||
@ -360,19 +231,14 @@ const MechRecCostProdPlans = () => {
|
|||||||
showPlanList: false,
|
showPlanList: false,
|
||||||
planCtlgs: [],
|
planCtlgs: [],
|
||||||
planCtlgsLoaded: false,
|
planCtlgsLoaded: false,
|
||||||
selectedPlans: [],
|
|
||||||
selectedPlansElements: 0,
|
|
||||||
selectedPlanCtlgSpecsLoaded: false,
|
selectedPlanCtlgSpecsLoaded: false,
|
||||||
selectedPlanCtlg: null,
|
selectedPlanCtlg: null,
|
||||||
selectedPlanCtlgMaxLevel: null,
|
selectedPlanCtlgMaxLevel: null,
|
||||||
selectedPlanCtlgLevel: null,
|
selectedPlanCtlgLevel: null,
|
||||||
selectedPlanCtlgOutOfLimit: 0,
|
|
||||||
selectedPlanCtlgSort: null,
|
selectedPlanCtlgSort: null,
|
||||||
selectedPlanCtlgMenuItems: null,
|
selectedPlanCtlgMenuItems: null,
|
||||||
loadedCtlg: null,
|
selectedPlanCtlgGanttDef: {},
|
||||||
loadedPlans: [],
|
selectedPlanCtlgSpecs: [],
|
||||||
loadedElements: 0,
|
|
||||||
gantt: {},
|
|
||||||
selectedTaskDetail: null,
|
selectedTaskDetail: null,
|
||||||
selectedTaskDetailType: null,
|
selectedTaskDetailType: null,
|
||||||
planSpec: null
|
planSpec: null
|
||||||
@ -387,14 +253,11 @@ const MechRecCostProdPlans = () => {
|
|||||||
const { InlineMsgInfo } = useContext(MessagingСtx);
|
const { InlineMsgInfo } = useContext(MessagingСtx);
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Подключение к контексту навигации
|
//Подключение к контексту навигации
|
||||||
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) {
|
||||||
@ -402,101 +265,77 @@ const MechRecCostProdPlans = () => {
|
|||||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_PP_CTLG_INIT",
|
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_PP_CTLG_INIT",
|
||||||
args: {},
|
args: {},
|
||||||
respArg: "COUT",
|
respArg: "COUT",
|
||||||
isArray: name => ["XFCPRODPLAN_CRNS", "XCRN_PLANS"].includes(name)
|
isArray: name => name === "XFCPRODPLAN_CRNS"
|
||||||
});
|
});
|
||||||
setState(pv => ({ ...pv, init: true, planCtlgs: [...(data?.XFCPRODPLAN_CRNS || [])], planCtlgsLoaded: true }));
|
setState(pv => ({ ...pv, init: true, planCtlgs: [...(data?.XFCPRODPLAN_CRNS || [])], planCtlgsLoaded: true }));
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [state.init, executeStored]);
|
}, [state.init, executeStored]);
|
||||||
|
|
||||||
|
//Выбор каталога планов
|
||||||
|
const selectPlan = project => {
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
selectedPlanCtlg: project,
|
||||||
|
selectedPlanCtlgSpecsLoaded: false,
|
||||||
|
selectedPlanCtlgMaxLevel: null,
|
||||||
|
selectedPlanCtlgLevel: null,
|
||||||
|
selectedPlanCtlgSort: null,
|
||||||
|
selectedPlanCtlgMenuItems: null,
|
||||||
|
selectedPlanCtlgSpecs: [],
|
||||||
|
selectedPlanCtlgGanttDef: {},
|
||||||
|
showPlanList: false,
|
||||||
|
selectedTaskDetail: null,
|
||||||
|
selectedTaskDetailType: null
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Сброс выбора каталога планов
|
||||||
|
const unselectPlan = () =>
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
selectedPlanCtlgSpecsLoaded: false,
|
||||||
|
selectedPlanCtlg: null,
|
||||||
|
selectedPlanCtlgMaxLevel: null,
|
||||||
|
selectedPlanCtlgLevel: null,
|
||||||
|
selectedPlanCtlgSort: null,
|
||||||
|
selectedPlanCtlgMenuItems: null,
|
||||||
|
selectedPlanCtlgSpecs: [],
|
||||||
|
selectedPlanCtlgGanttDef: {},
|
||||||
|
showPlanList: false,
|
||||||
|
selectedTaskDetail: null,
|
||||||
|
selectedTaskDetailType: null
|
||||||
|
}));
|
||||||
|
|
||||||
//Загрузка списка спецификаций каталога планов
|
//Загрузка списка спецификаций каталога планов
|
||||||
const loadPlanCtglSpecs = useCallback(
|
const loadPlanCtglSpecs = useCallback(
|
||||||
async (level = null, sort = null) => {
|
async (level = null, sort = null) => {
|
||||||
const data = await executeStored({
|
const data = await executeStored({
|
||||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_GET",
|
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_GET",
|
||||||
args: {
|
args: { NCRN: state.selectedPlanCtlg, NLEVEL: level, SSORT_FIELD: sort, NFCPRODPLANSP: state.planSpec }
|
||||||
NCRN: state.selectedPlanCtlg,
|
|
||||||
CFCPRODPLANS: {
|
|
||||||
VALUE: state.selectedPlans.length > 0 ? state.selectedPlans.join(";") : null,
|
|
||||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
|
||||||
},
|
|
||||||
NLEVEL: level,
|
|
||||||
SSORT_FIELD: sort,
|
|
||||||
NFCPRODPLANSP: state.planSpec
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
let doc = await parseProdPlanSpXML(data.COUT);
|
let doc = await parseProdPlanSpXML(data.COUT);
|
||||||
setState(pv => ({
|
setState(pv => ({
|
||||||
...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: [...Array(data.NMAX_LEVEL).keys()].map(el => el + 1),
|
selectedPlanCtlgMenuItems: state.selectedPlanCtlgMenuItems
|
||||||
|
? state.selectedPlanCtlgMenuItems
|
||||||
|
: [...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 } : {},
|
||||||
loadedCtlg: state.selectedPlanCtlg,
|
selectedPlanCtlgSpecs: [...(doc?.XGANTT_TASKS || [])]
|
||||||
loadedPlans: [...state.selectedPlans],
|
|
||||||
loadedElements: state.selectedPlansElements,
|
|
||||||
showPlanList: false
|
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[executeStored, state.selectedPlanCtlg, state.selectedPlans, state.planSpec]
|
[executeStored, state.ident, state.selectedPlanCtlg, state.planSpec]
|
||||||
);
|
);
|
||||||
|
|
||||||
//Обработка нажатия на элемент в списке каталогов планов
|
//Обработка нажатия на элемент в списке каталогов планов
|
||||||
const handleCtlgClick = project => {
|
const handleProjectClick = project => {
|
||||||
//Если этот каталог не был выбран
|
if (state.selectedPlanCtlg != project.NRN) selectPlan(project.NRN);
|
||||||
if (state.selectedPlanCtlg != project.NRN) {
|
else unselectPlan();
|
||||||
//Если выбран уже загруженный - укажем информацию о том, как он загружен
|
|
||||||
if (project.NRN === state.loadedCtlg) {
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
selectedPlanCtlg: project.NRN,
|
|
||||||
selectedPlans: [...pv.loadedPlans],
|
|
||||||
selectedPlansElements: pv.loadedElements
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
selectedPlanCtlg: project.NRN,
|
|
||||||
selectedPlans: project.XCRN_PLANS.length === 1 ? [project.XCRN_PLANS[0].NRN] : [],
|
|
||||||
selectedPlansElements: 0
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setState(pv => ({ ...pv, selectedPlanCtlg: null, selectedPlans: [], selectedPlansElements: 0 }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Обработка нажатия на элемент в списке планов каталога
|
|
||||||
const handleCtlgPlanClick = plan => {
|
|
||||||
//Считываем обновленную информацию об отмеченных планах
|
|
||||||
let newPlansInfo = updateCtlgPlanInfo(state.selectedPlans, plan);
|
|
||||||
//Обновляем список отмеченных планов
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
selectedPlans: [...newPlansInfo.selectedPlans],
|
|
||||||
selectedPlansElements: pv.selectedPlansElements + newPlansInfo.selectedPlansElements
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
//Обработка нажатия "ОК" при отборе планов
|
|
||||||
const handleSelectedPlansOk = () => {
|
|
||||||
//Загружаем диаграмму
|
|
||||||
loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Обработка нажатия "Отмена" при отборе планов
|
|
||||||
const handleSelectedPlansCancel = () => {
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
selectedPlanCtlg: pv.loadedCtlg,
|
|
||||||
selectedPlans: [...pv.loadedPlans] || [],
|
|
||||||
selectedPlansElements: pv.loadedElements,
|
|
||||||
showPlanList: false
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//При подключении компонента к странице
|
//При подключении компонента к странице
|
||||||
@ -509,8 +348,8 @@ const MechRecCostProdPlans = () => {
|
|||||||
|
|
||||||
//При смене выбранного каталога плана или при явном указании позиции спецификации плана
|
//При смене выбранного каталога плана или при явном указании позиции спецификации плана
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.planSpec) loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
|
if (state.selectedPlanCtlg || state.planSpec) loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
|
||||||
}, [state.planSpec, loadPlanCtglSpecs]);
|
}, [state.selectedPlanCtlg, state.planSpec, loadPlanCtglSpecs]);
|
||||||
|
|
||||||
//Выбор уровня
|
//Выбор уровня
|
||||||
const handleChangeSelectLevel = selectedLevel => {
|
const handleChangeSelectLevel = selectedLevel => {
|
||||||
@ -534,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>
|
||||||
@ -553,18 +381,18 @@ const MechRecCostProdPlans = () => {
|
|||||||
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
|
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
|
||||||
Каталоги планов
|
Каталоги планов
|
||||||
</Fab>
|
</Fab>
|
||||||
<Drawer anchor={"left"} open={state.showPlanList} onClose={handleSelectedPlansCancel} sx={STYLES.PLANS_DRAWER}>
|
<Drawer
|
||||||
|
anchor={"left"}
|
||||||
|
open={state.showPlanList}
|
||||||
|
onClose={() => setState(pv => ({ ...pv, showPlanList: false }))}
|
||||||
|
sx={STYLES.PLANS_DRAWER}
|
||||||
|
>
|
||||||
<PlanCtlgsList
|
<PlanCtlgsList
|
||||||
planCtlgs={filteredPlanCtgls}
|
planCtlgs={filteredPlanCtgls}
|
||||||
selectedPlans={state.selectedPlans}
|
|
||||||
selectedPlanCtlg={state.selectedPlanCtlg}
|
selectedPlanCtlg={state.selectedPlanCtlg}
|
||||||
selectedPlansElements={state.selectedPlansElements}
|
|
||||||
filter={filter}
|
filter={filter}
|
||||||
setFilter={setFilter}
|
setFilter={setFilter}
|
||||||
onCtlgClick={handleCtlgClick}
|
onClick={handleProjectClick}
|
||||||
onCtlgPlanClick={handleCtlgPlanClick}
|
|
||||||
onCtlgPlansOk={handleSelectedPlansOk}
|
|
||||||
onCtlgPlansCancel={handleSelectedPlansCancel}
|
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</>
|
</>
|
||||||
@ -573,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}
|
||||||
@ -609,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}
|
||||||
@ -639,21 +459,22 @@ 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>
|
||||||
)
|
)
|
||||||
) : !state.loadedCtlg ? (
|
) : !state.selectedPlanCtlg ? (
|
||||||
<Box pt={3}>
|
<Box pt={3}>
|
||||||
<InlineMsgInfo
|
<InlineMsgInfo
|
||||||
okBtn={false}
|
okBtn={false}
|
||||||
text={
|
text={
|
||||||
state.planSpec
|
state.planSpec
|
||||||
? "Загружаю график для выбранной позиции плана..."
|
? "Загружаю график для выбранной позиции плана..."
|
||||||
: "Укажите каталог планов или планы для отображения их спецификаций"
|
: "Укажите каталог планов для отображения их спецификаций"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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" },
|
||||||
|
|||||||
@ -1,51 +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"; //Хуки компонентов
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Редактор свойств компонента панели
|
|
||||||
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>
|
|
||||||
{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 };
|
|
||||||
@ -1,71 +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"; //Хуки компонентов
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Представление компонента панели
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Компоненты: График (редактор настроек)
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
|
|
||||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
|
||||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//График (редактор настроек)
|
|
||||||
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 (
|
|
||||||
<P8PEditorBox title={"Параметры графика"} onSave={handleSave}>
|
|
||||||
<P8PEditorSubHeader title={"Источник данных"} />
|
|
||||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
|
||||||
</P8PEditorBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - График (редактор настроек)
|
|
||||||
ChartEditor.propTypes = {
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
|
||||||
valueProviders: PropTypes.object,
|
|
||||||
onSettingsChange: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export default ChartEditor;
|
|
||||||
@ -1,82 +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 { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
|
||||||
import {
|
|
||||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
|
||||||
P8P_COMPONENT_INLINE_MESSAGE,
|
|
||||||
P8PComponentInlineMessage
|
|
||||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Иконка компонента
|
|
||||||
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] = useDataSource({ 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} />
|
|
||||||
) : (
|
|
||||||
<P8PComponentInlineMessage
|
|
||||||
icon={COMPONENT_ICON}
|
|
||||||
name={COMPONENT_NAME}
|
|
||||||
message={!haveConfing ? P8P_COMPONENT_INLINE_MESSAGE.NO_SETTINGS : error ? error : P8P_COMPONENT_INLINE_MESSAGE.NO_DATA_FOUND}
|
|
||||||
type={error ? P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR : P8P_COMPONENT_INLINE_MESSAGE_TYPE.COMMON}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - График (представление)
|
|
||||||
Chart.propTypes = {
|
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
|
||||||
values: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export default Chart;
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Компоненты: Описание
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
const COMPONETNS = [
|
|
||||||
{
|
|
||||||
name: "Форма",
|
|
||||||
path: "form",
|
|
||||||
settings2: {
|
|
||||||
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",
|
|
||||||
settings2: {
|
|
||||||
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 };
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Компоненты: Хуки компонентов
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useEffect } from "react"; //Классы React
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Отложенная загрузка модуля компонента (как альтернативу можно применять 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];
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useComponentModule };
|
|
||||||
@ -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"
|
|
||||||
};
|
|
||||||
@ -1,309 +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 { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
|
|
||||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
|
||||||
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Диалог настройки
|
|
||||||
import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_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 (
|
|
||||||
<P8PConfigDialog 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>
|
|
||||||
</P8PConfigDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - редактор элемента
|
|
||||||
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 && (
|
|
||||||
<P8PEditorBox title={"Параметры формы"} onSave={handleSave}>
|
|
||||||
{itemEditor.display && (
|
|
||||||
<ItemEditor
|
|
||||||
item={itemEditor.index !== null ? { ...settings.items[itemEditor.index] } : null}
|
|
||||||
onCancel={handleItemCancel}
|
|
||||||
onOk={handleItemSave}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<P8PEditorSubHeader 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={"Автоподтверждение"}
|
|
||||||
/>
|
|
||||||
<P8PEditorSubHeader 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>
|
|
||||||
</P8PEditorBox>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Форма (редактор настроек)
|
|
||||||
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;
|
|
||||||
@ -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 { P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
|
||||||
import { ITEM_SHAPE, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Иконка компонента
|
|
||||||
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>
|
|
||||||
) : (
|
|
||||||
<P8PComponentInlineMessage icon={COMPONENT_ICON} name={COMPONENT_NAME} message={P8P_COMPONENT_INLINE_MESSAGE.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;
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Компоненты: Индикатор (редактор настроек)
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
|
|
||||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
|
||||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Индикатор (редактор настроек)
|
|
||||||
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 (
|
|
||||||
<P8PEditorBox title={"Параметры индикатора"} onSave={handleSave}>
|
|
||||||
<P8PEditorSubHeader title={"Источник данных"} />
|
|
||||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
|
||||||
</P8PEditorBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Индикатор (редактор настроек)
|
|
||||||
IndicatorEditor.propTypes = {
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
|
||||||
valueProviders: PropTypes.object,
|
|
||||||
onSettingsChange: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export default IndicatorEditor;
|
|
||||||
@ -1,87 +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 { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
|
||||||
import {
|
|
||||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
|
||||||
P8P_COMPONENT_INLINE_MESSAGE,
|
|
||||||
P8PComponentInlineMessage
|
|
||||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Иконка компонента
|
|
||||||
const COMPONENT_ICON = "speed";
|
|
||||||
|
|
||||||
//Наименование компонента
|
|
||||||
const COMPONENT_NAME = "Индикатор";
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CONTAINER: { height: "100%" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Индикатор (представление)
|
|
||||||
const Indicator = ({ dataSource = null, values = {} } = {}) => {
|
|
||||||
//Собственное состояние - данные
|
|
||||||
const [data, error] = useDataSource({ 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} />
|
|
||||||
) : (
|
|
||||||
<P8PComponentInlineMessage
|
|
||||||
icon={COMPONENT_ICON}
|
|
||||||
name={COMPONENT_NAME}
|
|
||||||
message={!haveConfing ? P8P_COMPONENT_INLINE_MESSAGE.NO_SETTINGS : error ? error : P8P_COMPONENT_INLINE_MESSAGE.NO_DATA_FOUND}
|
|
||||||
type={error ? P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR : P8P_COMPONENT_INLINE_MESSAGE_TYPE.COMMON}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Индикатор (представление)
|
|
||||||
Indicator.propTypes = {
|
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
|
||||||
values: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export default Indicator;
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Компоненты: Таблица (редактор настроек)
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
|
|
||||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
|
||||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Таблица (редактор настроек)
|
|
||||||
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 (
|
|
||||||
<P8PEditorBox title={"Параметры таблицы"} onSave={handleSave}>
|
|
||||||
<P8PEditorSubHeader title={"Источник данных"} />
|
|
||||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
|
||||||
</P8PEditorBox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Таблица (редактор настроек)
|
|
||||||
TableEditor.propTypes = {
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
|
||||||
valueProviders: PropTypes.object,
|
|
||||||
onSettingsChange: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export default TableEditor;
|
|
||||||
@ -1,99 +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 { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
|
||||||
import {
|
|
||||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
|
||||||
P8P_COMPONENT_INLINE_MESSAGE,
|
|
||||||
P8PComponentInlineMessage
|
|
||||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Иконка компонента
|
|
||||||
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] = useDataSource({ 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 }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<P8PComponentInlineMessage
|
|
||||||
icon={COMPONENT_ICON}
|
|
||||||
name={COMPONENT_NAME}
|
|
||||||
message={!haveConfing ? P8P_COMPONENT_INLINE_MESSAGE.NO_SETTINGS : error ? error : P8P_COMPONENT_INLINE_MESSAGE.NO_DATA_FOUND}
|
|
||||||
type={error ? P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR : P8P_COMPONENT_INLINE_MESSAGE_TYPE.COMMON}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Таблица (представление)
|
|
||||||
Table.propTypes = {
|
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
|
||||||
values: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export default Table;
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Редактор панелей: точка входа
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { PanelsEditor } from "./panels_editor"; //Корневая панель редактора
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export const RootClass = PanelsEditor;
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Элемент макета
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
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 };
|
|
||||||
@ -1,29 +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-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;
|
|
||||||
}
|
|
||||||
@ -1,253 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Корневой компонент
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useEffect, useState, useContext } from "react"; //Классы React
|
|
||||||
import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
|
|
||||||
import { Box, Grid, Menu, MenuItem, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
|
||||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
|
|
||||||
import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
|
|
||||||
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"; //Стили для адаптивного макета
|
|
||||||
import "./panels_editor.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 = (
|
|
||||||
<P8PEditorToolBar
|
|
||||||
items={[
|
|
||||||
{ icon: "play_arrow", title: "Запустить", onClick: toggleEditMode },
|
|
||||||
{
|
|
||||||
icon: "add",
|
|
||||||
title: "Добавить элемент",
|
|
||||||
onClick: handleAddClick
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
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 };
|
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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 })}
|
||||||
|
|||||||
@ -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 =>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
|
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
|
||||||
import { Grid, Box } from "@mui/material"; //Интерфейсные элементы
|
import { Grid, Box } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
|
||||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
|
||||||
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 { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
@ -23,7 +21,7 @@ import { dataCellRender, groupCellRender } from "./layouts"; //Дополнит
|
|||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
DATA_GRID_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})`, width: "100vw", ...APP_STYLES.SCROLL }
|
DATA_GRID_CONTAINER: { minWidth: "95vw", maxWidth: "95vw", minHeight: "87vh", maxHeight: "87vh" }
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -55,10 +53,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
|
||||||
}));
|
}));
|
||||||
@ -72,9 +71,10 @@ const PrjGraph = () => {
|
|||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<Grid container>
|
<div>
|
||||||
|
<Grid container spacing={1}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box display="flex" justifyContent="center" alignItems="center">
|
<Box pt={1} display="flex" justifyContent="center" alignItems="center">
|
||||||
{dataGrid.dataLoaded ? (
|
{dataGrid.dataLoaded ? (
|
||||||
<P8PDataGrid
|
<P8PDataGrid
|
||||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||||
@ -88,7 +88,7 @@ const PrjGraph = () => {
|
|||||||
dataCellRender={prms => dataCellRender({ ...prms, pOnlineShowDocument })}
|
dataCellRender={prms => dataCellRender({ ...prms, pOnlineShowDocument })}
|
||||||
groupCellRender={prms => groupCellRender({ ...prms, pOnlineShowDocument })}
|
groupCellRender={prms => groupCellRender({ ...prms, pOnlineShowDocument })}
|
||||||
containerComponentProps={{
|
containerComponentProps={{
|
||||||
elevation: 0,
|
elevation: 3,
|
||||||
sx: STYLES.DATA_GRID_CONTAINER
|
sx: STYLES.DATA_GRID_CONTAINER
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -96,6 +96,7 @@ const PrjGraph = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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>: {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 };
|
|
||||||
@ -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 };
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
|
|
||||||
Панель мониторинга: точка входа
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { PrjInfo } from "./prj_info"; //Корневая панель информации о проектах
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export const RootClass = PrjInfo;
|
|
||||||
@ -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 };
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user