Compare commits
No commits in common. "main" and "EqsPrfrm" have entirely different histories.
@ -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"
|
|
||||||
};
|
|
||||||
|
|||||||
10
app/app.js
10
app/app.js
@ -10,7 +10,7 @@
|
|||||||
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { createHashRouter, RouterProvider, useRouteError } from "react-router-dom"; //Роутер
|
import { createHashRouter, RouterProvider, useRouteError } from "react-router-dom"; //Роутер
|
||||||
import { ApplicationCtx } from "./context/application"; //Контекст приложения
|
import { ApplicationСtx } from "./context/application"; //Контекст приложения
|
||||||
import { NavigationContext, NavigationCtx, getRootLocation } from "./context/navigation"; //Контекст навигации
|
import { NavigationContext, NavigationCtx, getRootLocation } from "./context/navigation"; //Контекст навигации
|
||||||
import { P8PAppErrorPage } from "./components/p8p_app_error_page"; //Страница с ошибкой
|
import { P8PAppErrorPage } from "./components/p8p_app_error_page"; //Страница с ошибкой
|
||||||
import { P8PAppWorkspace } from "./components/p8p_app_workspace"; //Рабочее пространство панели
|
import { P8PAppWorkspace } from "./components/p8p_app_workspace"; //Рабочее пространство панели
|
||||||
@ -54,7 +54,7 @@ const MainMenu = ({ panels = [] } = {}) => {
|
|||||||
const { navigatePanel, isNavigationSearch, getNavigationSearch } = useContext(NavigationCtx);
|
const { navigatePanel, isNavigationSearch, getNavigationSearch } = useContext(NavigationCtx);
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { configUrlBase, pOnlineShowTab } = useContext(ApplicationCtx);
|
const { configUrlBase, pOnlineShowTab } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//Получим параметры запроса из адресной строки
|
//Получим параметры запроса из адресной строки
|
||||||
const qS = isNavigationSearch() ? getNavigationSearch() : null;
|
const qS = isNavigationSearch() ? getNavigationSearch() : null;
|
||||||
@ -86,9 +86,6 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
|
|||||||
//Подключение к контексту навигации
|
//Подключение к контексту навигации
|
||||||
const { navigateRoot, navigatePanel } = useContext(NavigationCtx);
|
const { navigateRoot, navigatePanel } = useContext(NavigationCtx);
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
|
||||||
const { appState } = useContext(ApplicationCtx);
|
|
||||||
|
|
||||||
//Отработка действия навигации домой
|
//Отработка действия навигации домой
|
||||||
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}
|
||||||
>
|
>
|
||||||
@ -130,7 +126,7 @@ const App = () => {
|
|||||||
const [routes, setRoutes] = useState([]);
|
const [routes, setRoutes] = useState([]);
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { appState } = useContext(ApplicationCtx);
|
const { appState } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//Инициализация роутера
|
//Инициализация роутера
|
||||||
const content = routes.length > 0 ? <RouterProvider router={createHashRouter(routes)}></RouterProvider> : null;
|
const content = routes.length > 0 ? <RouterProvider router={createHashRouter(routes)}></RouterProvider> : null;
|
||||||
|
|||||||
@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Действия
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { P8PCAEditor } from "./p8p_component_action/editor"; //Редактор действия
|
|
||||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
|
||||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL, P8P_CA_TYPE } from "./p8p_component_action/common"; //Общие ресурсы действий
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
const getActionChipText = (type, params) => {
|
|
||||||
//Определяем от типа
|
|
||||||
switch (type) {
|
|
||||||
//Открыть раздел
|
|
||||||
case P8P_CA_TYPE.openUnit.code:
|
|
||||||
return params.unitName;
|
|
||||||
//Открыть панель
|
|
||||||
case P8P_CA_TYPE.openPanel.code:
|
|
||||||
return params.panelValue.value;
|
|
||||||
//Установить переменную
|
|
||||||
case P8P_CA_TYPE.setVariable.code:
|
|
||||||
return params.reduce((prev, cur) => [...prev, cur.variableSource], []).join(", ");
|
|
||||||
//Для всех остальных
|
|
||||||
default:
|
|
||||||
return "Не определен";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Действия
|
|
||||||
const P8PActions = ({ actions = P8P_CAS_INITIAL, valueProviders = {}, areas = [], valueTypes = [], onChange = null } = {}) => {
|
|
||||||
//Собственное состояние - редактор действий
|
|
||||||
const [actionEditor, setActionEditor] = useState({ display: false, index: null });
|
|
||||||
|
|
||||||
//При изменении действий
|
|
||||||
const handleActionsChange = actions => onChange && onChange(actions);
|
|
||||||
|
|
||||||
//При добавлении действия
|
|
||||||
const handleActionAdd = () => setActionEditor({ display: true, index: null });
|
|
||||||
|
|
||||||
//При нажатии на действие
|
|
||||||
const handleActionClick = i => setActionEditor({ display: true, index: i });
|
|
||||||
|
|
||||||
//При удалении действия
|
|
||||||
const handleActionDelete = i => {
|
|
||||||
const newActions = [...actions];
|
|
||||||
newActions.splice(i, 1);
|
|
||||||
handleActionsChange(newActions);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При отмене сохранения изменений действия
|
|
||||||
const handleActionCancel = () => setActionEditor({ display: false, index: null });
|
|
||||||
|
|
||||||
//При сохранении изменений действия
|
|
||||||
const handleActionSave = action => {
|
|
||||||
const newActions = [...actions];
|
|
||||||
actionEditor.index == null ? newActions.push({ ...action }) : (newActions[actionEditor.index] = { ...action });
|
|
||||||
handleActionsChange(newActions);
|
|
||||||
setActionEditor({ display: false, index: null });
|
|
||||||
};
|
|
||||||
|
|
||||||
//Определяем структуру действий для отображения
|
|
||||||
const actionChips = actions.map(item => {
|
|
||||||
//Собираем текст действия
|
|
||||||
let text = getActionChipText(item.type, item.params);
|
|
||||||
//Формируем структуру для отображения карточки действия
|
|
||||||
return { text: text, title: text, icon: P8P_CA_TYPE[item.type]?.icon, iconTitle: P8P_CA_TYPE[item.type]?.name };
|
|
||||||
});
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{actionEditor.display && (
|
|
||||||
<P8PCAEditor
|
|
||||||
areas={areas}
|
|
||||||
valueTypes={valueTypes}
|
|
||||||
action={actionEditor.index !== null ? { ...actions[actionEditor.index] } : null}
|
|
||||||
onCancel={handleActionCancel}
|
|
||||||
onOk={handleActionSave}
|
|
||||||
valueProviders={valueProviders}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<P8PChipList items={actionChips} onClick={handleActionClick} onDelete={handleActionDelete} />
|
|
||||||
<Button startIcon={<Icon>add</Icon>} onClick={handleActionAdd}>
|
|
||||||
Добавить действие
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - действия
|
|
||||||
P8PActions.propTypes = {
|
|
||||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
|
||||||
valueProviders: PropTypes.object,
|
|
||||||
areas: PropTypes.array,
|
|
||||||
valueTypes: PropTypes.array,
|
|
||||||
onChange: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PActions };
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Список элементов
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Chip, Typography, Stack, Icon } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { STYLES as COMMON_STYLES } from "./p8p_editors_common"; //Общие ресурсы редакторов
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CHIP_ITEM: { ...COMMON_STYLES.CHIP(true, false) },
|
|
||||||
STACK_ITEM: { alignItems: "center" },
|
|
||||||
TYPOGRAPHY_INFO: { overflow: "hidden" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Список элементов
|
|
||||||
const P8PChipList = ({ items, labelRender, onClick, onDelete }) => {
|
|
||||||
//При нажатии на элемент
|
|
||||||
const handleClick = index => onClick && onClick(index);
|
|
||||||
|
|
||||||
//При удалении элемента
|
|
||||||
const handleDelete = index => onDelete && onDelete(index);
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{Array.isArray(items) &&
|
|
||||||
items.length > 0 &&
|
|
||||||
items.map((item, i) => (
|
|
||||||
<Chip
|
|
||||||
key={i}
|
|
||||||
label={
|
|
||||||
labelRender ? (
|
|
||||||
labelRender(item)
|
|
||||||
) : (
|
|
||||||
<Stack direction="row" spacing={1} sx={STYLES.STACK_ITEM}>
|
|
||||||
{item.icon ? (
|
|
||||||
<>
|
|
||||||
<Icon sx={{ color: "grey" }} title={item?.iconTitle}>
|
|
||||||
{item.icon}
|
|
||||||
</Icon>
|
|
||||||
<Typography variant="body2">-</Typography>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
<Typography variant="body2" title={item?.title} sx={STYLES.TYPOGRAPHY_INFO}>
|
|
||||||
{item.text}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
variant={"outlined"}
|
|
||||||
onClick={onClick && !item.disableClick ? () => handleClick(i) : null}
|
|
||||||
onDelete={onDelete && !item.disableDelete ? () => handleDelete(i) : null}
|
|
||||||
deleteIcon={item.deleteIcon ? <Icon>{item.deleteIcon}</Icon> : null}
|
|
||||||
sx={STYLES.CHIP_ITEM}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - список элементов
|
|
||||||
P8PChipList.propTypes = {
|
|
||||||
items: PropTypes.array,
|
|
||||||
labelRender: PropTypes.func,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
onDelete: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PChipList };
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Общие ресурсы компонента "Редактор действия"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Значения типа по умолчанию
|
|
||||||
const P8P_CA_DEF_TYPE_VALUE = {
|
|
||||||
TEXT_VALUE: "Значение",
|
|
||||||
VARIABLE: "Переменная"
|
|
||||||
};
|
|
||||||
|
|
||||||
//Типы значений по умолчанию
|
|
||||||
const P8P_CA_DEF_VALUE_TYPES = [...Object.values(P8P_CA_DEF_TYPE_VALUE)];
|
|
||||||
|
|
||||||
//Типы действия
|
|
||||||
const P8P_CA_TYPE = {
|
|
||||||
openUnit: { code: "openUnit", name: "Открыть раздел", icon: "article" },
|
|
||||||
openPanel: { code: "openPanel", name: "Открыть панель", icon: "storage" },
|
|
||||||
setVariable: { code: "setVariable", name: "Установить переменную", icon: "mediation" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//Список полей связываемых с проводником значений
|
|
||||||
const P8P_CA_SOURCE_FIELDS = ["variableSource"];
|
|
||||||
|
|
||||||
//Список полей хранящих объект типа {"Тип", "Значение"}, где может храниться связь с проводником значений
|
|
||||||
const P8P_CA_OBJECT_SOURCE_FIELDS = ["resultValue", "tabValue", "panelValue"];
|
|
||||||
|
|
||||||
//Структура значения с типом
|
|
||||||
const P8P_CA_OBJECT_VALUE_SHAPE = PropTypes.shape({
|
|
||||||
type: PropTypes.string,
|
|
||||||
value: PropTypes.string
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура входных параметров метода вызова
|
|
||||||
const P8P_CA_INPUT_PARAM_SHAPE = PropTypes.shape({
|
|
||||||
parameter: PropTypes.string,
|
|
||||||
inputParameter: PropTypes.string,
|
|
||||||
resultValue: P8P_CA_OBJECT_VALUE_SHAPE
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура параметров действия "Открыть раздел"
|
|
||||||
const P8P_CA_OPEN_UNIT_PARAMS_SHAPE = PropTypes.shape({
|
|
||||||
unitCode: PropTypes.string,
|
|
||||||
unitName: PropTypes.string,
|
|
||||||
showMethod: PropTypes.string,
|
|
||||||
showMethodName: PropTypes.string,
|
|
||||||
inputParams: PropTypes.arrayOf(P8P_CA_INPUT_PARAM_SHAPE),
|
|
||||||
modal: PropTypes.bool
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура параметров действия "Открыть панель"
|
|
||||||
const P8P_CA_OPEN_PANEL_PARAMS_SHAPE = PropTypes.shape({
|
|
||||||
tabValue: P8P_CA_OBJECT_VALUE_SHAPE,
|
|
||||||
panelValue: P8P_CA_OBJECT_VALUE_SHAPE
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура связи переменной действия "Установить переменную"
|
|
||||||
const P8P_CA_VARIABLE_LINK_SHAPE = PropTypes.shape({
|
|
||||||
variableSource: PropTypes.string,
|
|
||||||
resultValue: P8P_CA_OBJECT_VALUE_SHAPE
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура действия
|
|
||||||
const P8P_CA_SHAPE = PropTypes.shape({
|
|
||||||
type: PropTypes.oneOf(Object.keys(P8P_CA_TYPE)).isRequired,
|
|
||||||
area: PropTypes.string,
|
|
||||||
element: PropTypes.string,
|
|
||||||
params: PropTypes.oneOfType([P8P_CA_OPEN_UNIT_PARAMS_SHAPE, P8P_CA_OPEN_PANEL_PARAMS_SHAPE, PropTypes.arrayOf(P8P_CA_VARIABLE_LINK_SHAPE)])
|
|
||||||
});
|
|
||||||
|
|
||||||
//Начальное состояние объекта значения с типом
|
|
||||||
const P8P_CA_OBJECT_VALUE_INITIAL = {
|
|
||||||
type: P8P_CA_DEF_TYPE_VALUE.TEXT_VALUE,
|
|
||||||
value: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
//Начальное состояние входного параметра метода вызова
|
|
||||||
const P8P_CA_INPUT_PARAM_INITIAL = {
|
|
||||||
parameter: "",
|
|
||||||
inputParameter: "",
|
|
||||||
resultValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
|
||||||
};
|
|
||||||
|
|
||||||
//Начальное состояние параметров действия "Открыть раздел"
|
|
||||||
const P8P_CA_OPEN_UNIT_INITIAL = {
|
|
||||||
unitCode: "",
|
|
||||||
unitName: "",
|
|
||||||
showMethod: "",
|
|
||||||
showMethodName: "",
|
|
||||||
inputParams: [],
|
|
||||||
modal: true
|
|
||||||
};
|
|
||||||
|
|
||||||
//Начальное состояние параметров действия "Открыть панель"
|
|
||||||
const P8P_CA_OPEN_PANEL_INITIAL = {
|
|
||||||
tabValue: { ...P8P_CA_OBJECT_VALUE_INITIAL },
|
|
||||||
panelValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
|
||||||
};
|
|
||||||
|
|
||||||
//Начальное состояние параметра связи действия "Установить переменную"
|
|
||||||
const P8P_CA_VARIABLE_LINK_INITIAL = {
|
|
||||||
variableSource: "",
|
|
||||||
resultValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
|
||||||
};
|
|
||||||
|
|
||||||
//Начальное состояние действия
|
|
||||||
const P8P_CA_INITIAL = {
|
|
||||||
type: P8P_CA_TYPE.openUnit.code,
|
|
||||||
area: "",
|
|
||||||
element: "",
|
|
||||||
params: { ...P8P_CA_OPEN_UNIT_INITIAL }
|
|
||||||
};
|
|
||||||
|
|
||||||
//Начальное состояние действий
|
|
||||||
const P8P_CAS_INITIAL = [];
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
P8P_CA_DEF_TYPE_VALUE,
|
|
||||||
P8P_CA_DEF_VALUE_TYPES,
|
|
||||||
P8P_CA_TYPE,
|
|
||||||
P8P_CA_SOURCE_FIELDS,
|
|
||||||
P8P_CA_OBJECT_SOURCE_FIELDS,
|
|
||||||
P8P_CA_OBJECT_VALUE_SHAPE,
|
|
||||||
P8P_CA_OPEN_UNIT_PARAMS_SHAPE,
|
|
||||||
P8P_CA_OPEN_PANEL_PARAMS_SHAPE,
|
|
||||||
P8P_CA_VARIABLE_LINK_SHAPE,
|
|
||||||
P8P_CA_SHAPE,
|
|
||||||
P8P_CA_INPUT_PARAM_INITIAL,
|
|
||||||
P8P_CA_OPEN_UNIT_INITIAL,
|
|
||||||
P8P_CA_OPEN_PANEL_INITIAL,
|
|
||||||
P8P_CA_VARIABLE_LINK_INITIAL,
|
|
||||||
P8P_CA_INITIAL,
|
|
||||||
P8P_CAS_INITIAL
|
|
||||||
};
|
|
||||||
@ -1,159 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Редактор действия "Открыть панель"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Box } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { P8P_CA_DEF_TYPE_VALUE, P8P_CA_OPEN_PANEL_PARAMS_SHAPE } from "./common"; //Общие ресурсы действий
|
|
||||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
|
||||||
import { PanelsManager } from "../../../panels/panels_editor/components/panels_manager/panels_manager"; //Менеджер панелей
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CONTAINER_UNIT: { display: "flex", flexDirection: "column", gap: "10px" },
|
|
||||||
BOX_SETTINGS: { display: "flex", flexDirection: "row", gap: "15px" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//Ключ значений о странице
|
|
||||||
const SPROP_TAB = "tabValue";
|
|
||||||
//Ключа значений о панели
|
|
||||||
const SPROP_PANEL = "panelValue";
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Редактор действия "Открыть панель"
|
|
||||||
const P8PCAPanelOpenOptions = ({ panel, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
|
||||||
//Собственное состояние - Отображение менеджера панелей
|
|
||||||
const [openPanelsManager, setOpenPanelsManager] = useState(false);
|
|
||||||
|
|
||||||
//При открытии менеджера панелей
|
|
||||||
const handlePanelsManagerOpen = () => setOpenPanelsManager(true);
|
|
||||||
|
|
||||||
//При закрытии менеджера панелей
|
|
||||||
const handlePanelsManagerClose = () => setOpenPanelsManager(false);
|
|
||||||
|
|
||||||
//При выборе панели в менеджере панелей
|
|
||||||
const handlePanelSelect = selectedPanel => {
|
|
||||||
//Устанавливаем мнемокод выбранной панели
|
|
||||||
onStateChange({ ...panel, panelValue: { ...panel.panelValue, value: selectedPanel.code } });
|
|
||||||
//Закрываем менеджер панелей
|
|
||||||
handlePanelsManagerClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении типа значения
|
|
||||||
const handleValueTypeChange = (value, keyProp) =>
|
|
||||||
onStateChange({
|
|
||||||
[keyProp]: { type: value, value: "" }
|
|
||||||
});
|
|
||||||
|
|
||||||
//При изменении значения
|
|
||||||
const handleValueChange = (value, keyProp) =>
|
|
||||||
onStateChange({
|
|
||||||
[keyProp]: { ...panel[keyProp], value: value }
|
|
||||||
});
|
|
||||||
|
|
||||||
//При очистке значения
|
|
||||||
const handleClearValueClick = keyProp =>
|
|
||||||
onStateChange({
|
|
||||||
[keyProp]: { ...panel[keyProp], value: "" }
|
|
||||||
});
|
|
||||||
|
|
||||||
//При изменении переменной
|
|
||||||
const handleValueSourceChange = (valueSource, keyProp) =>
|
|
||||||
onStateChange({
|
|
||||||
[keyProp]: { ...panel[keyProp], value: valueSource }
|
|
||||||
});
|
|
||||||
|
|
||||||
//При нажатии на выбор переменной
|
|
||||||
const handleValueSourceSelect = (e, keyProp) =>
|
|
||||||
isValues
|
|
||||||
? onValueSourceMenuClick &&
|
|
||||||
onValueSourceMenuClick(e, valueSource => {
|
|
||||||
handleValueSourceChange(valueSource, keyProp);
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Box sx={STYLES.CONTAINER_UNIT}>
|
|
||||||
{openPanelsManager && (
|
|
||||||
<PanelsManager
|
|
||||||
current={panel.panelValue.value}
|
|
||||||
isEditable={false}
|
|
||||||
onPanelSelect={handlePanelSelect}
|
|
||||||
onCancel={handlePanelsManagerClose}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Box sx={STYLES.BOX_SETTINGS}>
|
|
||||||
<P8PCAFieldWithType
|
|
||||||
valueTypes={valueTypes}
|
|
||||||
name={SPROP_TAB}
|
|
||||||
valueLabel={"Наименование вкладки"}
|
|
||||||
groupLabel={"Настройки вкладки"}
|
|
||||||
item={panel.tabValue}
|
|
||||||
isValueReadOnly={panel.tabValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
|
||||||
onTypeChange={e => handleValueTypeChange(e.target.value, SPROP_TAB)}
|
|
||||||
onValueChange={e => handleValueChange(e.target.value, SPROP_TAB)}
|
|
||||||
endAdornments={[
|
|
||||||
{ icon: "clear", onClick: () => handleClearValueClick(SPROP_TAB), isDisabled: false },
|
|
||||||
{
|
|
||||||
icon: "settings_ethernet",
|
|
||||||
onClick: e => handleValueSourceSelect(e, SPROP_TAB),
|
|
||||||
isDisabled: panel.tabValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<P8PCAFieldWithType
|
|
||||||
valueTypes={valueTypes}
|
|
||||||
name={SPROP_PANEL}
|
|
||||||
valueLabel={"Панель"}
|
|
||||||
groupLabel={"Настройки панели"}
|
|
||||||
item={panel.panelValue}
|
|
||||||
isValueReadOnly={panel.panelValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
|
||||||
onTypeChange={e => handleValueTypeChange(e.target.value, SPROP_PANEL)}
|
|
||||||
onValueChange={e => handleValueChange(e.target.value, SPROP_PANEL)}
|
|
||||||
endAdornments={[
|
|
||||||
{ icon: "clear", onClick: () => handleClearValueClick(SPROP_PANEL), isDisabled: false },
|
|
||||||
{
|
|
||||||
icon: "list",
|
|
||||||
onClick: handlePanelsManagerOpen,
|
|
||||||
isDisabled: panel.panelValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "settings_ethernet",
|
|
||||||
onClick: e => handleValueSourceSelect(e, SPROP_PANEL),
|
|
||||||
isDisabled: panel.panelValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - редактор действия "Открыть панель"
|
|
||||||
P8PCAPanelOpenOptions.propTypes = {
|
|
||||||
panel: P8P_CA_OPEN_PANEL_PARAMS_SHAPE,
|
|
||||||
valueTypes: PropTypes.array,
|
|
||||||
isValues: PropTypes.bool.isRequired,
|
|
||||||
onStateChange: PropTypes.func.isRequired,
|
|
||||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PCAPanelOpenOptions };
|
|
||||||
@ -1,285 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Редактор действия "Открыть раздел"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useContext } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { TextField, Box, InputAdornment, IconButton, Icon, FormControlLabel, Checkbox } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { ApplicationCtx } from "../../../context/application"; //Контекст приложения
|
|
||||||
import { P8P_CA_OPEN_UNIT_PARAMS_SHAPE, P8P_CA_INPUT_PARAM_INITIAL, P8P_CA_DEF_TYPE_VALUE } from "./common"; //Общие ресурсы действий
|
|
||||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
|
||||||
import { P8PCATablePropValues } from "./table_prop_values"; //Таблица значений свойств действия
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CONTAINER_UNIT: { display: "flex", flexDirection: "column", gap: "10px" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Редактор действия "Открыть раздел"
|
|
||||||
const P8PCAUnitOpenOptions = ({ unit, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
|
||||||
//Подключение к контексту приложения
|
|
||||||
const { pOnlineShowDictionary } = useContext(ApplicationCtx);
|
|
||||||
|
|
||||||
//При изменении параметра открытия
|
|
||||||
const handleModalChange = () => onStateChange({ modal: !unit.modal });
|
|
||||||
|
|
||||||
//При нажатии на очистку раздела
|
|
||||||
const handleClearUnitClick = () =>
|
|
||||||
onStateChange({
|
|
||||||
unitCode: "",
|
|
||||||
unitName: "",
|
|
||||||
showMethod: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
//При нажатии на выбор раздела
|
|
||||||
const handleSelectUnitClick = () => {
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "Units",
|
|
||||||
showMethod: "methods",
|
|
||||||
inputParameters: [
|
|
||||||
{ name: "pos_unit_name", value: unit.unitName },
|
|
||||||
{ name: "pos_method_name", value: unit.showMethodName }
|
|
||||||
],
|
|
||||||
callBack: res =>
|
|
||||||
res.success &&
|
|
||||||
onStateChange({
|
|
||||||
unitCode: res.outParameters.unit_code,
|
|
||||||
unitName: res.outParameters.unit_name,
|
|
||||||
showMethod: res.outParameters.method_code,
|
|
||||||
showMethodName: res.outParameters.method_name
|
|
||||||
})
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//При добавлении связи входного параметра
|
|
||||||
const handleInputParameAdd = () => {
|
|
||||||
onStateChange({
|
|
||||||
inputParams: [...unit.inputParams, { ...P8P_CA_INPUT_PARAM_INITIAL }]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//При удалении связи входного параметра
|
|
||||||
const handleInputParamDelete = index => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newInputParams = [...unit.inputParams];
|
|
||||||
//Удаляем по индексу
|
|
||||||
newInputParams.splice(index, 1);
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange({ inputParams: [...newInputParams] });
|
|
||||||
};
|
|
||||||
|
|
||||||
//При очистке входного параметра
|
|
||||||
const handleClearInputParameterClick = index => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newInputParams = [...unit.inputParams];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newInputParams[index]["parameter"] = "";
|
|
||||||
newInputParams[index]["inputParameter"] = "";
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange({ inputParams: [...newInputParams] });
|
|
||||||
};
|
|
||||||
|
|
||||||
//При нажатии на выбор параметра метода вызова
|
|
||||||
const handleSelectUnitParameterClick = index => {
|
|
||||||
unit.unitCode &&
|
|
||||||
unit.showMethod &&
|
|
||||||
pOnlineShowDictionary({
|
|
||||||
unitCode: "UnitParams",
|
|
||||||
showMethod: "main",
|
|
||||||
inputParameters: [
|
|
||||||
{ name: "in_UNITCODE", value: unit.unitCode },
|
|
||||||
{ name: "in_PARENT_METHOD_CODE", value: unit.showMethod },
|
|
||||||
{ name: "in_PARAMNAME", value: unit.inputParams[index].parameter }
|
|
||||||
],
|
|
||||||
callBack: res => {
|
|
||||||
if (res.success) {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newInputParams = [...unit.inputParams];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newInputParams[index]["parameter"] = res.outParameters.out_PARAMNAME;
|
|
||||||
newInputParams[index]["inputParameter"] = res.outParameters.out_IN_CODE;
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange({ inputParams: [...newInputParams] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении типа значения входного параметра
|
|
||||||
const handleInputParamValueTypeChange = (value, index) => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newInputParams = [...unit.inputParams];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, type: value, value: "" };
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange({ inputParams: [...newInputParams] });
|
|
||||||
};
|
|
||||||
|
|
||||||
//При очистке значения параметра
|
|
||||||
const handleClearInputParamValueClick = index => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newInputParams = [...unit.inputParams];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: "" };
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange({ inputParams: [...newInputParams] });
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении значения входного параметра
|
|
||||||
const handleInputParamValueChange = (value, index) => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newInputParams = [...unit.inputParams];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: value };
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange({ inputParams: [...newInputParams] });
|
|
||||||
};
|
|
||||||
|
|
||||||
//При связывании значения входного параметра с переменной
|
|
||||||
const handleInputParamValueSourceChange = (valueSource, index) => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newInputParams = [...unit.inputParams];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: valueSource };
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange({ inputParams: [...newInputParams] });
|
|
||||||
};
|
|
||||||
|
|
||||||
//При нажатии на выбор переменной
|
|
||||||
const handleValueSourceSelect = (e, index) =>
|
|
||||||
isValues
|
|
||||||
? onValueSourceMenuClick &&
|
|
||||||
onValueSourceMenuClick(e, valueSource => {
|
|
||||||
handleInputParamValueSourceChange(valueSource, index);
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
|
|
||||||
//Рендер отображения колонки свойства
|
|
||||||
const handlePropertyCellRender = (item, index) => {
|
|
||||||
//Значение первой колонки
|
|
||||||
return (
|
|
||||||
<TextField
|
|
||||||
id={`parameter_${index}`}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={item.parameter}
|
|
||||||
name={"parameter"}
|
|
||||||
fullWidth
|
|
||||||
InputProps={{
|
|
||||||
readOnly: true,
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={() => handleClearInputParameterClick(index)}>
|
|
||||||
<Icon>clear</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton onClick={() => handleSelectUnitParameterClick(index)} disabled={!unit.showMethodName}>
|
|
||||||
<Icon>list</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Рендер отображения колонки значения
|
|
||||||
const handleValueCellRender = (item, index) => {
|
|
||||||
//Формирование представления колонки значения
|
|
||||||
return (
|
|
||||||
<P8PCAFieldWithType
|
|
||||||
valueTypes={valueTypes}
|
|
||||||
name={`inputValue_${index}`}
|
|
||||||
item={item.resultValue}
|
|
||||||
isValueReadOnly={item.resultValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
|
||||||
onTypeChange={e => handleInputParamValueTypeChange(e.target.value, index)}
|
|
||||||
onValueChange={e => handleInputParamValueChange(e.target.value, index)}
|
|
||||||
endAdornments={[
|
|
||||||
{ icon: "clear", onClick: () => handleClearInputParamValueClick(index), isDisabled: false },
|
|
||||||
{
|
|
||||||
icon: "settings_ethernet",
|
|
||||||
onClick: e => handleValueSourceSelect(e, index),
|
|
||||||
isDisabled: item.resultValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Box sx={STYLES.CONTAINER_UNIT}>
|
|
||||||
<TextField
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={unit.unitName}
|
|
||||||
label={"Раздел"}
|
|
||||||
InputLabelProps={{ shrink: unit.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>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
required={true}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={unit.showMethodName}
|
|
||||||
label={"Метод вызова"}
|
|
||||||
InputLabelProps={{ shrink: unit.showMethodName ? true : false }}
|
|
||||||
InputProps={{
|
|
||||||
readOnly: true
|
|
||||||
}}
|
|
||||||
required={true}
|
|
||||||
/>
|
|
||||||
<P8PCATablePropValues
|
|
||||||
name={"Входные параметры"}
|
|
||||||
items={unit.inputParams}
|
|
||||||
propertyCellName={"Входной параметр"}
|
|
||||||
valueCellName={"Значение"}
|
|
||||||
onPropertyCellRender={(item, index) => handlePropertyCellRender(item, index)}
|
|
||||||
onValueCellRender={(item, index) => handleValueCellRender(item, index)}
|
|
||||||
onAddRow={handleInputParameAdd}
|
|
||||||
onRowDelete={index => handleInputParamDelete(index)}
|
|
||||||
/>
|
|
||||||
<FormControlLabel control={<Checkbox checked={unit.modal} onChange={handleModalChange} />} label="Открывать модально" />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - редактор действия "Открыть раздел"
|
|
||||||
P8PCAUnitOpenOptions.propTypes = {
|
|
||||||
unit: P8P_CA_OPEN_UNIT_PARAMS_SHAPE,
|
|
||||||
valueTypes: PropTypes.array,
|
|
||||||
isValues: PropTypes.bool.isRequired,
|
|
||||||
onStateChange: PropTypes.func.isRequired,
|
|
||||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PCAUnitOpenOptions };
|
|
||||||
@ -1,202 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Редактор действия "Установить переменную"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { TextField, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { P8P_CA_VARIABLE_LINK_INITIAL, P8P_CA_VARIABLE_LINK_SHAPE, P8P_CA_DEF_TYPE_VALUE } from "./common"; //Общие ресурсы действий
|
|
||||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
|
||||||
import { P8PCATablePropValues } from "./table_prop_values"; //Таблица значений свойств действия
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Редактор действия "Установить переменную"
|
|
||||||
const P8PCAVarSetOptions = ({ variables, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
|
||||||
//При добавлении связи переменной
|
|
||||||
const handleVariableAdd = () => {
|
|
||||||
onStateChange([...variables, { ...P8P_CA_VARIABLE_LINK_INITIAL }]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При удалении связи переменной
|
|
||||||
const handleVariableDelete = index => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newVariables = [...variables];
|
|
||||||
//Удаляем по индексу
|
|
||||||
newVariables.splice(index, 1);
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange([...newVariables]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При очистке значения переменной
|
|
||||||
const handleClearVariableClick = index => {
|
|
||||||
//Копируем текущее состояние связываемых переменных
|
|
||||||
let newParams = [...variables];
|
|
||||||
//Очищаем значения у элемента по индексу
|
|
||||||
newParams[index].variableSource = "";
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange([...newParams]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При связывании переменной
|
|
||||||
const handleVariablesSourceChange = (valueSource, index) => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newVariables = [...variables];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newVariables[index].variableSource = valueSource;
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange([...newVariables]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении типа значения
|
|
||||||
const handleResultValueTypeChange = (value, index) => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newVariables = [...variables];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, type: value, value: "" };
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange([...newVariables]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При очистке значения
|
|
||||||
const handleClearResultValueClick = index => {
|
|
||||||
//Копируем текущее состояние связываемых переменных
|
|
||||||
let newVariables = [...variables];
|
|
||||||
//Очищаем значения у элемента по индексу
|
|
||||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: "" };
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange([...newVariables]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При ручном изменении значения
|
|
||||||
const handleResultValueChange = (value, index) => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newVariables = [...variables];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: value };
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange([...newVariables]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При связывании значения
|
|
||||||
const handleResultValueSourceChange = (valueSource, index) => {
|
|
||||||
//Копируем связываемые переменные
|
|
||||||
let newVariables = [...variables];
|
|
||||||
//Очищаем значение по индексу
|
|
||||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: valueSource };
|
|
||||||
//Устанавливаем новый массив
|
|
||||||
onStateChange([...newVariables]);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При нажатии на выбор переменной (значение)
|
|
||||||
const handleValueSourceSelect = (e, index) =>
|
|
||||||
isValues
|
|
||||||
? onValueSourceMenuClick &&
|
|
||||||
onValueSourceMenuClick(e, valueSource => {
|
|
||||||
handleResultValueSourceChange(valueSource, index);
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
|
|
||||||
//При нажатии на выбор переменной (переменная)
|
|
||||||
const handleVariableSourceSelect = (e, index) =>
|
|
||||||
isValues
|
|
||||||
? onValueSourceMenuClick &&
|
|
||||||
onValueSourceMenuClick(e, valueSource => {
|
|
||||||
handleVariablesSourceChange(valueSource, index);
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
|
|
||||||
//Рендер отображения колонки свойства
|
|
||||||
const handlePropertyCellRender = (item, index) => {
|
|
||||||
//Формирование представления колонки свойства
|
|
||||||
return (
|
|
||||||
<TextField
|
|
||||||
id={`variableSource_${index}`}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={item.variableSource}
|
|
||||||
name={"variableSource"}
|
|
||||||
fullWidth
|
|
||||||
InputProps={{
|
|
||||||
readOnly: true,
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={() => handleClearVariableClick(index)}>
|
|
||||||
<Icon>clear</Icon>
|
|
||||||
</IconButton>
|
|
||||||
{isValues && (
|
|
||||||
<IconButton onClick={e => handleVariableSourceSelect(e, index)}>
|
|
||||||
<Icon>settings_ethernet</Icon>
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Рендер отображения колонки значения
|
|
||||||
const handleValueCellRender = (item, index) => {
|
|
||||||
//Формирование представления колонки значения
|
|
||||||
return (
|
|
||||||
<P8PCAFieldWithType
|
|
||||||
valueTypes={valueTypes}
|
|
||||||
name={`resultValue_${index}`}
|
|
||||||
item={item.resultValue}
|
|
||||||
isValueReadOnly={item.resultValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
|
||||||
onTypeChange={e => handleResultValueTypeChange(e.target.value, index)}
|
|
||||||
onValueChange={e => handleResultValueChange(e.target.value, index)}
|
|
||||||
endAdornments={[
|
|
||||||
{ icon: "clear", onClick: () => handleClearResultValueClick(index), isDisabled: false },
|
|
||||||
{
|
|
||||||
icon: "settings_ethernet",
|
|
||||||
onClick: e => handleValueSourceSelect(e, index),
|
|
||||||
isDisabled: item.resultValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<P8PCATablePropValues
|
|
||||||
name={"Список параметров"}
|
|
||||||
items={variables}
|
|
||||||
propertyCellName={"Переменная"}
|
|
||||||
valueCellName={"Значение"}
|
|
||||||
onPropertyCellRender={(item, index) => handlePropertyCellRender(item, index)}
|
|
||||||
onValueCellRender={(item, index) => handleValueCellRender(item, index)}
|
|
||||||
onAddRow={handleVariableAdd}
|
|
||||||
onRowDelete={index => handleVariableDelete(index)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - редактор действия "Установить переменную"
|
|
||||||
P8PCAVarSetOptions.propTypes = {
|
|
||||||
variables: PropTypes.arrayOf(P8P_CA_VARIABLE_LINK_SHAPE),
|
|
||||||
valueTypes: PropTypes.array,
|
|
||||||
isValues: PropTypes.bool.isRequired,
|
|
||||||
onStateChange: PropTypes.func.isRequired,
|
|
||||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PCAVarSetOptions };
|
|
||||||
@ -1,250 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Редактор действия
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { TextField, Select, Menu, MenuItem, FormControl, InputLabel, Stack, Box } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { TITLES } from "../../../../app.text"; //Общие текстовые ресурсы
|
|
||||||
import { P8PConfigDialog } from "../p8p_config_dialog"; //Диалог настройки
|
|
||||||
import { deepCopyObject } from "../../../core/utils"; //Вспомогательные функции
|
|
||||||
import { P8P_CA_DEF_VALUE_TYPES, P8P_CA_TYPE, P8P_CA_SHAPE, P8P_CA_INPUT_PARAM_INITIAL, P8P_CA_OPEN_UNIT_INITIAL, P8P_CA_INITIAL } from "./common"; //Общие ресурсы действий
|
|
||||||
import { P8PCAUnitOpenOptions } from "./config_unit_open"; //Компонент редактора действия "Открыть раздел"
|
|
||||||
import { P8PCAPanelOpenOptions } from "./config_panel_open"; //Компонент редактора действия "Открыть панель"
|
|
||||||
import { P8PCAVarSetOptions } from "./config_var_set"; //Компонент редактора действия "Установить переменную"
|
|
||||||
import { isActionOkDisabled, getActionTypeParams } from "./util"; //Вспомогательные ресурсы действий
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CONTAINER: { display: "flex", flexDirection: "column", gap: "10px", minWidth: "300px" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Редактор действия
|
|
||||||
const P8PCAEditor = ({ areas = [], valueTypes = [], action = null, onOk = null, onCancel = null, valueProviders = {} }) => {
|
|
||||||
//Собственное состояние - признак инициализиации
|
|
||||||
const [init, setInit] = useState(true);
|
|
||||||
|
|
||||||
//Собственное состояние - параметры действия
|
|
||||||
const [state, setState] = useState({});
|
|
||||||
|
|
||||||
//Собственное состояние - доступные типы значений компонента
|
|
||||||
const [availableValueTypes, setAvailableValueTypes] = useState([]);
|
|
||||||
|
|
||||||
//Собственное состояние - доступные области компонента
|
|
||||||
const [availableAreas, setAvailableAreas] = useState([]);
|
|
||||||
|
|
||||||
//Собственное состояние - доступность поля "Элемент"
|
|
||||||
const [hasElement, setHasElement] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - элемент привязки меню выбора источника
|
|
||||||
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState({
|
|
||||||
target: null,
|
|
||||||
onChange: null
|
|
||||||
});
|
|
||||||
|
|
||||||
//Открытие/сокрытие меню выбора источника
|
|
||||||
const toggleValueProvidersMenu = (target, onChange) =>
|
|
||||||
setValueProvidersMenuAnchorEl(target instanceof Element ? { target, onChange } : { target: null, onChange: null });
|
|
||||||
|
|
||||||
//При отображении меню связывания значения с поставщиком данных
|
|
||||||
const handleValueSourceLinkMenuClick = (e, onChange) => setValueProvidersMenuAnchorEl({ target: e.currentTarget, onChange });
|
|
||||||
|
|
||||||
//При закрытии редактора с сохранением
|
|
||||||
const handleOk = () => onOk && onOk({ ...state });
|
|
||||||
|
|
||||||
//При закрытии редактора с отменой
|
|
||||||
const handleCancel = () => onCancel && onCancel();
|
|
||||||
|
|
||||||
//При изменении типа действия
|
|
||||||
const handleTypeChange = e => {
|
|
||||||
//Иницилизируем параметры типа
|
|
||||||
let newParams = getActionTypeParams(e.target.value);
|
|
||||||
//Изменяем тип действия с изменение структуры параметров
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
type: e.target.value,
|
|
||||||
params: Array.isArray(newParams) ? [...newParams] : { ...newParams }
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При ручном изменении общего параметра действия
|
|
||||||
const handleChange = e => setState(pv => ({ ...pv, [e.target.name]: e.target.value }));
|
|
||||||
|
|
||||||
//При изменении области действия
|
|
||||||
const handleAreaChange = e => {
|
|
||||||
//Устанавливаем значение
|
|
||||||
setState(pv => ({ ...pv, [e.target.name]: e.target.value, name: "" }));
|
|
||||||
//Устанавливаем доступность "Элемент"
|
|
||||||
setHasElement(availableAreas.find(item => item.area === e.target.value).hasElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении полей параметров действия
|
|
||||||
const handleParamsChange = params => {
|
|
||||||
setState(pv => ({ ...pv, params: { ...pv.params, ...params } }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении переменных параметров действия "Установить переменную"
|
|
||||||
const handleVariablesParamsChange = variables => {
|
|
||||||
setState(pv => ({ ...pv, params: [...variables] }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//Список значений
|
|
||||||
const values = Object.keys(valueProviders);
|
|
||||||
|
|
||||||
//Наличие значений
|
|
||||||
const isValues = values && values.length > 0 ? true : false;
|
|
||||||
|
|
||||||
//Меню привязки к поставщикам значений
|
|
||||||
const valueProvidersMenu = isValues && (
|
|
||||||
<Menu anchorEl={valueProvidersMenuAnchorEl.target} open={Boolean(valueProvidersMenuAnchorEl.target)} onClose={toggleValueProvidersMenu}>
|
|
||||||
{values.map((value, i) => (
|
|
||||||
<MenuItem
|
|
||||||
key={i}
|
|
||||||
onClick={() => {
|
|
||||||
//Выполняем выбор параметра
|
|
||||||
valueProvidersMenuAnchorEl.onChange(value);
|
|
||||||
//Закрываем меню выбора переменной
|
|
||||||
toggleValueProvidersMenu();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
//При инициализации действия
|
|
||||||
useEffect(() => {
|
|
||||||
//Если это иницализация
|
|
||||||
if (init) {
|
|
||||||
//Если это открытие действия - берем его параметры, иначе - инициализируем изначальными
|
|
||||||
const initAction = action
|
|
||||||
? action
|
|
||||||
: {
|
|
||||||
...P8P_CA_INITIAL,
|
|
||||||
area: areas[0].area,
|
|
||||||
params: { ...P8P_CA_OPEN_UNIT_INITIAL, inputParams: [{ ...P8P_CA_INPUT_PARAM_INITIAL }] }
|
|
||||||
};
|
|
||||||
//Устанавливаем параметры действия
|
|
||||||
setState(deepCopyObject(initAction));
|
|
||||||
//Заполняем доступные области
|
|
||||||
setAvailableAreas([...areas]);
|
|
||||||
//Определяем доступность "Элемент"
|
|
||||||
setHasElement(areas.find(item => item.area === initAction.area)?.hasElement || false);
|
|
||||||
//Заполняем доступные типы значений
|
|
||||||
setAvailableValueTypes([...P8P_CA_DEF_VALUE_TYPES, ...valueTypes]);
|
|
||||||
//Сбрасываем признак инициализации
|
|
||||||
setInit(false);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [init]);
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!init ? (
|
|
||||||
<P8PConfigDialog
|
|
||||||
title={`${action ? TITLES.UPDATE : TITLES.INSERT} действия`}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={handleCancel}
|
|
||||||
width={"xl"}
|
|
||||||
okDisabled={isActionOkDisabled(state)}
|
|
||||||
>
|
|
||||||
<Stack direction={"column"} spacing={1}>
|
|
||||||
{valueProvidersMenu}
|
|
||||||
<Box sx={STYLES.CONTAINER}>
|
|
||||||
{availableAreas.length !== 0 ? (
|
|
||||||
<FormControl variant={"standard"} fullWidth>
|
|
||||||
<InputLabel id={"areaLabel-label"}>Область</InputLabel>
|
|
||||||
<Select name={"area"} value={state.area} labelId={"area-label"} label={"Область"} onChange={handleAreaChange}>
|
|
||||||
{availableAreas.map((item, index) => (
|
|
||||||
<MenuItem value={item.area} key={index}>
|
|
||||||
{item.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
) : null}
|
|
||||||
{hasElement && (
|
|
||||||
<TextField
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={state.element}
|
|
||||||
label={"Элемент"}
|
|
||||||
name={"element"}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<FormControl variant={"standard"} fullWidth>
|
|
||||||
<InputLabel id={"type-label"}>Тип</InputLabel>
|
|
||||||
<Select name={"type"} value={state.type} labelId={"type-label"} label={"Тип"} onChange={handleTypeChange}>
|
|
||||||
{Object.keys(P8P_CA_TYPE).map((item, index) => (
|
|
||||||
<MenuItem value={P8P_CA_TYPE[item].code} key={index}>
|
|
||||||
{P8P_CA_TYPE[item].name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Box>
|
|
||||||
{state.type === P8P_CA_TYPE.openUnit.code && (
|
|
||||||
<P8PCAUnitOpenOptions
|
|
||||||
unit={state.params}
|
|
||||||
valueTypes={availableValueTypes}
|
|
||||||
isValues={isValues}
|
|
||||||
onStateChange={handleParamsChange}
|
|
||||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{state.type === P8P_CA_TYPE.openPanel.code && (
|
|
||||||
<P8PCAPanelOpenOptions
|
|
||||||
panel={state.params}
|
|
||||||
valueTypes={availableValueTypes}
|
|
||||||
isValues={isValues}
|
|
||||||
onStateChange={handleParamsChange}
|
|
||||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{state.type === P8P_CA_TYPE.setVariable.code && (
|
|
||||||
<P8PCAVarSetOptions
|
|
||||||
variables={state.params}
|
|
||||||
valueTypes={availableValueTypes}
|
|
||||||
isValues={isValues}
|
|
||||||
onStateChange={handleVariablesParamsChange}
|
|
||||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</P8PConfigDialog>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Редактор условия
|
|
||||||
P8PCAEditor.propTypes = {
|
|
||||||
areas: PropTypes.array,
|
|
||||||
valueTypes: PropTypes.array,
|
|
||||||
action: P8P_CA_SHAPE,
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
onCancel: PropTypes.func.isRequired,
|
|
||||||
valueProviders: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PCAEditor };
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Поле с выбором типа значения
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { TextField, Select, MenuItem, FormControl, Box, InputAdornment, IconButton, Icon, FormLabel } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { P8P_CA_OBJECT_VALUE_SHAPE } from "./common"; //Общие ресурсы действий
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CONTAINER: { display: "flex", flexDirection: "column", gap: "10px", alignItems: "center" },
|
|
||||||
LEGEND_SETTINGS: { paddingTop: "10px", marginBottom: "-5px" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Поле с выбором типа значения
|
|
||||||
const P8PCAFieldWithType = ({
|
|
||||||
valueTypes,
|
|
||||||
item,
|
|
||||||
name,
|
|
||||||
valueLabel = null,
|
|
||||||
groupLabel = null,
|
|
||||||
isValueReadOnly = false,
|
|
||||||
onTypeChange,
|
|
||||||
onValueChange = null,
|
|
||||||
endAdornments = []
|
|
||||||
}) => {
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Box sx={STYLES.CONTAINER}>
|
|
||||||
{groupLabel ? (
|
|
||||||
<FormLabel component="legend" sx={STYLES.LEGEND_SETTINGS}>
|
|
||||||
{groupLabel}
|
|
||||||
</FormLabel>
|
|
||||||
) : null}
|
|
||||||
<FormControl variant={"standard"} fullWidth>
|
|
||||||
<Select name={`${name}_type`} value={item.type} labelId={`${name}-type-label`} onChange={onTypeChange} fullWidth>
|
|
||||||
{valueTypes.map((type, index) => (
|
|
||||||
<MenuItem value={type} key={index}>
|
|
||||||
{type}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<TextField
|
|
||||||
id={`${name}_value`}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={item.value}
|
|
||||||
name={`${name}`}
|
|
||||||
label={valueLabel}
|
|
||||||
onChange={e => {
|
|
||||||
onValueChange && onValueChange(e);
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
InputProps={{
|
|
||||||
readOnly: isValueReadOnly,
|
|
||||||
endAdornment:
|
|
||||||
endAdornments.length !== 0 ? (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
{endAdornments.map((item, index) => (
|
|
||||||
<IconButton onClick={e => item.onClick(e)} key={index} disabled={item.isDisabled}>
|
|
||||||
<Icon>{item.icon}</Icon>
|
|
||||||
</IconButton>
|
|
||||||
))}
|
|
||||||
</InputAdornment>
|
|
||||||
) : null
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - поле с выбором типа значения
|
|
||||||
P8PCAFieldWithType.propTypes = {
|
|
||||||
valueTypes: PropTypes.array.isRequired,
|
|
||||||
item: P8P_CA_OBJECT_VALUE_SHAPE.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
valueLabel: PropTypes.string,
|
|
||||||
groupLabel: PropTypes.string,
|
|
||||||
isValueReadOnly: PropTypes.bool,
|
|
||||||
onTypeChange: PropTypes.func.isRequired,
|
|
||||||
onValueChange: PropTypes.func,
|
|
||||||
endAdornments: PropTypes.arrayOf(PropTypes.object)
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PCAFieldWithType };
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Таблица значений свойств действия
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Box, IconButton, Icon, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Typography } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CONTAINER_VARIABLES: { maxHeight: "240px", overflow: "auto", ...APP_STYLES.SCROLL },
|
|
||||||
BOX_VARIABLES_INFO: { display: "flex", justifyContent: "space-between", alignItems: "center", paddingTop: "10px" },
|
|
||||||
TABLE_HEAD_CELL_PROPERTY: { width: "45%", padding: "8px" },
|
|
||||||
TABLE_HEAD_CELL_VALUE: { width: "45%", padding: "8px" },
|
|
||||||
TABLE_HEAD_CELL_DELETE: { width: "10%", padding: "8px" },
|
|
||||||
TABLE_VARIABLES: { maxWidth: "700px", minWidth: "700px" },
|
|
||||||
TABLE_ROW_VARIABLES: { "&:last-child td, &:last-child th": { border: 0 } },
|
|
||||||
TABLE_CELL_VARIABLES: { padding: "8px 16px" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Таблица значений свойств действия
|
|
||||||
const P8PCATablePropValues = ({ name, items, propertyCellName, valueCellName, onPropertyCellRender, onValueCellRender, onAddRow, onRowDelete }) => {
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Box sx={STYLES.BOX_VARIABLES_INFO}>
|
|
||||||
<Typography pl={1}>{name}</Typography>
|
|
||||||
<IconButton onClick={onAddRow}>
|
|
||||||
<Icon>add</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
<TableContainer component={Paper} sx={STYLES.CONTAINER_VARIABLES}>
|
|
||||||
<Table sx={STYLES.TABLE_VARIABLES} aria-label="simple table">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_PROPERTY}>
|
|
||||||
{propertyCellName}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_VALUE}>
|
|
||||||
{valueCellName}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_DELETE}></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{Array.isArray(items) &&
|
|
||||||
items.map((item, i) => (
|
|
||||||
<TableRow key={i} sx={STYLES.TABLE_ROW_VARIABLES}>
|
|
||||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
|
||||||
{onPropertyCellRender ? onPropertyCellRender(item, i) : null}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
|
||||||
{onValueCellRender ? onValueCellRender(item, i) : null}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
|
||||||
<IconButton onClick={() => onRowDelete(i)}>
|
|
||||||
<Icon>clear</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - таблица значений свойств действия
|
|
||||||
P8PCATablePropValues.propTypes = {
|
|
||||||
name: PropTypes.string,
|
|
||||||
items: PropTypes.array,
|
|
||||||
propertyCellName: PropTypes.string,
|
|
||||||
valueCellName: PropTypes.string,
|
|
||||||
onPropertyCellRender: PropTypes.func,
|
|
||||||
onValueCellRender: PropTypes.func,
|
|
||||||
onAddRow: PropTypes.func,
|
|
||||||
onRowDelete: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PCATablePropValues };
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Вспомогательные ресурсы компонента "Редактор действия"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { genUID } from "../../../core/utils"; //Вспомогательные функции
|
|
||||||
import {
|
|
||||||
P8P_CA_DEF_TYPE_VALUE,
|
|
||||||
P8P_CA_TYPE,
|
|
||||||
P8P_CA_SOURCE_FIELDS,
|
|
||||||
P8P_CA_OBJECT_SOURCE_FIELDS,
|
|
||||||
P8P_CA_OPEN_UNIT_INITIAL,
|
|
||||||
P8P_CA_INPUT_PARAM_INITIAL,
|
|
||||||
P8P_CA_OPEN_PANEL_INITIAL,
|
|
||||||
P8P_CA_VARIABLE_LINK_INITIAL
|
|
||||||
} from "./common"; //Общие ресурсы действий
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//Доступность сохранения настроек действия
|
|
||||||
const isActionOkDisabled = action => {
|
|
||||||
//Если область или тип не указаны
|
|
||||||
if (!action.area || !action.type) return true;
|
|
||||||
//Исходим от типа
|
|
||||||
switch (action.type) {
|
|
||||||
//Открыть раздел
|
|
||||||
case P8P_CA_TYPE.openUnit.code:
|
|
||||||
return !action.params.unitCode || !action.params.showMethod;
|
|
||||||
//Открыть панель
|
|
||||||
case P8P_CA_TYPE.openPanel.code:
|
|
||||||
return !action.params.tabValue.type || !action.params.tabValue.value || !action.params.panelValue.type || !action.params.panelValue.value;
|
|
||||||
//Установить переменную
|
|
||||||
case P8P_CA_TYPE.setVariable.code:
|
|
||||||
return action.params.length === 0 || action.params.some(item => !item.variableSource);
|
|
||||||
//Для всех остальных
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Считывание параметров типа действия
|
|
||||||
const getActionTypeParams = type => {
|
|
||||||
//Определяем от типа
|
|
||||||
switch (type) {
|
|
||||||
//Открыть раздел
|
|
||||||
case P8P_CA_TYPE.openUnit.code:
|
|
||||||
return { ...P8P_CA_OPEN_UNIT_INITIAL, inputParams: [{ ...P8P_CA_INPUT_PARAM_INITIAL }] };
|
|
||||||
//Открыть панель
|
|
||||||
case P8P_CA_TYPE.openPanel.code:
|
|
||||||
return { ...P8P_CA_OPEN_PANEL_INITIAL };
|
|
||||||
//Установить переменную
|
|
||||||
case P8P_CA_TYPE.setVariable.code:
|
|
||||||
return [{ ...P8P_CA_VARIABLE_LINK_INITIAL }];
|
|
||||||
//Для всех остальных
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Определение значения действия по типу
|
|
||||||
const getActionValueByType = (type, value, values, prms = {}, getCustomTypeValue = null) => {
|
|
||||||
//Исходим от типа действия
|
|
||||||
switch (type) {
|
|
||||||
//Значение (стандартное)
|
|
||||||
case P8P_CA_DEF_TYPE_VALUE.TEXT_VALUE:
|
|
||||||
return value;
|
|
||||||
//Переменная (стандартное)
|
|
||||||
case P8P_CA_DEF_TYPE_VALUE.VARIABLE:
|
|
||||||
return values[value];
|
|
||||||
//Кастомный тип
|
|
||||||
default:
|
|
||||||
return getCustomTypeValue ? getCustomTypeValue({ type, value, values, prms }) : null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Определения функции действия
|
|
||||||
const getActionFunction = (item, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue = null) => {
|
|
||||||
//Выполняем исходя из типа действия
|
|
||||||
switch (item.type) {
|
|
||||||
//Открыть раздел
|
|
||||||
case P8P_CA_TYPE.openUnit.code: {
|
|
||||||
return ({ event, values, prms }) => {
|
|
||||||
onOpenUnit({
|
|
||||||
unitCode: item.params.unitCode,
|
|
||||||
showMethod: item.params.showMethod,
|
|
||||||
inputParameters: item.params.inputParams.reduce(
|
|
||||||
(prev, cur) =>
|
|
||||||
cur.inputParameter
|
|
||||||
? [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
name: cur.inputParameter,
|
|
||||||
value: getActionValueByType(cur.resultValue.type, cur.resultValue.value, values, prms, getCustomTypeValue)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: [...prev],
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
modal: item.params.modal
|
|
||||||
});
|
|
||||||
event?.stopPropagation && event.stopPropagation();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//Открыть панель
|
|
||||||
case P8P_CA_TYPE.openPanel.code: {
|
|
||||||
return ({ event, values, prms }) => {
|
|
||||||
onOpenPanel({
|
|
||||||
id: genUID(),
|
|
||||||
url: `${configUrlBase}panels_editor?SCODE=${getActionValueByType(
|
|
||||||
item.params.panelValue.type,
|
|
||||||
item.params.panelValue.value,
|
|
||||||
values,
|
|
||||||
prms,
|
|
||||||
getCustomTypeValue
|
|
||||||
)}`,
|
|
||||||
caption:
|
|
||||||
getActionValueByType(item.params.tabValue.type, item.params.tabValue.value, values, prms, getCustomTypeValue) ||
|
|
||||||
"Редактор панелей"
|
|
||||||
});
|
|
||||||
event?.stopPropagation && event.stopPropagation();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//Установить переменную
|
|
||||||
case P8P_CA_TYPE.setVariable.code: {
|
|
||||||
//Устанавливаем новые значения проводников
|
|
||||||
return ({ event, values, prms }) => {
|
|
||||||
//Собираем объект новых значений проводников
|
|
||||||
let changedValues = item.params.reduce(
|
|
||||||
(prev, cur) => ({
|
|
||||||
...prev,
|
|
||||||
...{
|
|
||||||
[cur.variableSource]: getActionValueByType(cur.resultValue.type, cur.resultValue.value, values, prms, getCustomTypeValue)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
onProviderChange({ ...changedValues });
|
|
||||||
event?.stopPropagation && event.stopPropagation();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Выполнение действий компонента
|
|
||||||
const getHandlersByActions = (actions, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue = null) => {
|
|
||||||
//Инициализируем обработчики
|
|
||||||
const handlers = actions.reduce((prev, cur) => {
|
|
||||||
//Ключ обработчика
|
|
||||||
let key = `${cur.area}.${cur.element}`;
|
|
||||||
//Если уже был добавлен - не добавляем, иначе добавляем
|
|
||||||
return !prev[key] ? { ...prev, [key]: { area: cur.area, element: cur.element, fn: null } } : { ...prev };
|
|
||||||
}, {});
|
|
||||||
//Обходим уникальные обработчики
|
|
||||||
for (const handler in handlers) {
|
|
||||||
//Считываем действия области
|
|
||||||
let areaActions = actions.filter(item => item.area === handlers[handler].area && item.element === handlers[handler].element);
|
|
||||||
//Собираем массив обработчиков области
|
|
||||||
let areaFunctions = areaActions.reduce(
|
|
||||||
(fns, action) => [...fns, getActionFunction(action, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue)],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
//Устанавливаем области последовательный вызов обработчиков
|
|
||||||
handlers[handler].fn = ({ event, values, prms }) => {
|
|
||||||
areaFunctions.map(fn => fn({ event, values, prms }));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//Возвращаем обработчики
|
|
||||||
return handlers;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Считывание всех связанных переменных рекурсивно
|
|
||||||
const getActionsVariablesRecursive = (objValue, propName = null) => {
|
|
||||||
//Если это значение
|
|
||||||
if (!(typeof objValue === "object") && !Array.isArray(objValue)) {
|
|
||||||
//Если поле из списка хранящих ссылку - берем его
|
|
||||||
return P8P_CA_SOURCE_FIELDS.includes(propName) && objValue ? [objValue] : [];
|
|
||||||
}
|
|
||||||
//Если это объект хранящий тип и значение - может хранить ссылку на переменную
|
|
||||||
if (P8P_CA_OBJECT_SOURCE_FIELDS.includes(propName)) {
|
|
||||||
//Если тип "Переменная" - указываем значение
|
|
||||||
return objValue?.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE && objValue?.value ? [objValue.value] : [];
|
|
||||||
}
|
|
||||||
//Если это массив
|
|
||||||
if (Array.isArray(objValue)) {
|
|
||||||
return objValue.reduce((prev, cur) => [...prev, ...getActionsVariablesRecursive(cur)], []);
|
|
||||||
} else {
|
|
||||||
//Если это объект
|
|
||||||
return Object.keys(objValue).reduce((prev, cur) => [...prev, ...getActionsVariablesRecursive(objValue[cur], cur)], []);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Считывание всех связанных переменных действий
|
|
||||||
const getActionsVariables = actions => {
|
|
||||||
//Считываем связанные переменные действий рекурсивно
|
|
||||||
return getActionsVariablesRecursive(actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { isActionOkDisabled, getActionTypeParams, getActionsVariables, getHandlersByActions };
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Общие ресурсы компонента "Редактор условия"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Доступные операторы условия
|
|
||||||
const P8P_CC_OPERATORS = [
|
|
||||||
{ name: "==", value: "equal" },
|
|
||||||
{ name: "!=", value: "notEqual" },
|
|
||||||
{ name: "<=", value: "lessEqual" },
|
|
||||||
{ name: "<", value: "less" },
|
|
||||||
{ name: ">=", value: "greaterEqual" },
|
|
||||||
{ name: ">", value: "greater" },
|
|
||||||
{ name: "in", value: "in" }
|
|
||||||
];
|
|
||||||
|
|
||||||
//Структура параметра поля условия
|
|
||||||
const P8P_CC_FIELD_PRM_SHAPE = PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
value: PropTypes.string.isRequired,
|
|
||||||
hasElement: PropTypes.bool,
|
|
||||||
icon: PropTypes.string
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура поля условия
|
|
||||||
const P8P_CC_FIELD_SHAPE = PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
value: PropTypes.string.isRequired
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура оператора условия
|
|
||||||
const P8P_CC_OPERATOR_SHAPE = PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
value: PropTypes.string.isRequired
|
|
||||||
});
|
|
||||||
|
|
||||||
//Структура условия
|
|
||||||
const P8P_CC_SHAPE = PropTypes.shape({
|
|
||||||
condField: P8P_CC_FIELD_SHAPE.isRequired,
|
|
||||||
condOperator: P8P_CC_OPERATOR_SHAPE.isRequired,
|
|
||||||
condElement: PropTypes.string, //Пока
|
|
||||||
condValue: PropTypes.string.isRequired,
|
|
||||||
resField: P8P_CC_FIELD_SHAPE.isRequired,
|
|
||||||
resElement: PropTypes.string, //Пока
|
|
||||||
resValue: PropTypes.string.isRequired
|
|
||||||
});
|
|
||||||
|
|
||||||
//Начальное состояние поля условия
|
|
||||||
const P8P_CC_FIELD_INITIAL = {
|
|
||||||
name: "",
|
|
||||||
value: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
//Начальное состояние условия
|
|
||||||
const P8P_CC_INITIAL = {
|
|
||||||
condField: { ...P8P_CC_FIELD_INITIAL },
|
|
||||||
condOperator: { ...P8P_CC_OPERATORS[0] },
|
|
||||||
condElement: "",
|
|
||||||
condValue: "",
|
|
||||||
resField: { ...P8P_CC_FIELD_INITIAL },
|
|
||||||
resElement: "",
|
|
||||||
resValue: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
//Начальное состояние условий
|
|
||||||
const P8P_CCS_INITIAL = [];
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8P_CC_OPERATORS, P8P_CC_FIELD_PRM_SHAPE, P8P_CC_FIELD_SHAPE, P8P_CC_SHAPE, P8P_CC_INITIAL, P8P_CCS_INITIAL };
|
|
||||||
@ -1,239 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Редактор условия
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { TextField, Select, MenuItem, FormControl, InputLabel, Stack, Box } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { TITLES } from "../../../../app.text"; //Общие текстовые ресурсы
|
|
||||||
import { P8PConfigDialog } from "../p8p_config_dialog"; //Диалог настройки
|
|
||||||
import { P8P_CC_INITIAL, P8P_CC_SHAPE, P8P_CC_OPERATORS, P8P_CC_FIELD_PRM_SHAPE } from "./common"; //Общие ресурсы условий
|
|
||||||
import { deepCopyObject } from "../../../core/utils"; //Вспомогательные функции
|
|
||||||
import { isConditionOkDisabled } from "./util"; //Вспомогательные ресурсы условий
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
BOX_COND_GROUP: { display: "flex", flexDirection: "row", gap: "10px", paddingBottom: "10px" },
|
|
||||||
BOX_COND_ELEMENT: width => ({ minWidth: width })
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Редактор условия
|
|
||||||
const P8PCCEditor = ({ condition = null, onOk = null, onCancel = null, condFields = [], resFields = [] }) => {
|
|
||||||
//Собственное состояние - признак инициализиации
|
|
||||||
const [init, setInit] = useState(true);
|
|
||||||
|
|
||||||
//Собственное состояние - параметры элемента формы
|
|
||||||
const [state, setState] = useState({});
|
|
||||||
|
|
||||||
//При закрытии редактора с сохранением
|
|
||||||
const handleOk = () => onOk && onOk({ ...state });
|
|
||||||
|
|
||||||
//Собственное состояние - доступность поля "Элемент условия"
|
|
||||||
const [hasCondElement, setHasCondElement] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - доступность поля "Элемент результата"
|
|
||||||
const [hasResElement, setHasResElement] = useState(false);
|
|
||||||
|
|
||||||
//При закрытии редактора с отменой
|
|
||||||
const handleCancel = () => onCancel && onCancel();
|
|
||||||
|
|
||||||
//При изменении параметра элемента
|
|
||||||
const handleChange = e => setState(pv => ({ ...pv, [e.target.name]: e.target.value }));
|
|
||||||
|
|
||||||
//При изменении поля условия
|
|
||||||
const handleCondFieldChange = e => {
|
|
||||||
//Считываем нужный элемент
|
|
||||||
const newCondField = condFields.find(item => item.name === e.target.value);
|
|
||||||
//Обновляем поле (объект)
|
|
||||||
setState(pv => ({ ...pv, [e.target.name]: { name: newCondField.name, value: newCondField.value } }));
|
|
||||||
//Определяем доступность поля "Элемент условия"
|
|
||||||
setHasCondElement(newCondField?.hasElement || false);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении поля результата
|
|
||||||
const handleResFieldChange = e => {
|
|
||||||
//Считываем нужный элемент
|
|
||||||
const newResField = resFields.find(item => item.name === e.target.value);
|
|
||||||
//Обновляем поле результата
|
|
||||||
setState(pv => ({ ...pv, [e.target.name]: { name: newResField.name, value: newResField.value } }));
|
|
||||||
//Определяем доступность поля "Элемент результата"
|
|
||||||
setHasResElement(newResField?.hasElement || false);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении оператора условия
|
|
||||||
const handleOperatorChange = e => {
|
|
||||||
//Считываем нужный элемент
|
|
||||||
const newOperator = P8P_CC_OPERATORS.find(item => item.name === e.target.value);
|
|
||||||
//Обновляем оператор
|
|
||||||
setState(pv => ({ ...pv, [e.target.name]: { ...newOperator } }));
|
|
||||||
};
|
|
||||||
|
|
||||||
//При инициализации условия
|
|
||||||
useEffect(() => {
|
|
||||||
//Если это иницализация
|
|
||||||
if (init) {
|
|
||||||
//Если это открытие условия - берем его параметры, иначе - инициализируем изначальными
|
|
||||||
const initCondition = condition
|
|
||||||
? condition
|
|
||||||
: {
|
|
||||||
...P8P_CC_INITIAL,
|
|
||||||
condField: { name: condFields[0].name, value: condFields[0].value },
|
|
||||||
resField: { name: resFields[0].name, value: resFields[0].value }
|
|
||||||
};
|
|
||||||
//Устанавливаем параметры действия
|
|
||||||
setState(deepCopyObject(initCondition));
|
|
||||||
//Определяем доступность "Элемент условия"
|
|
||||||
setHasCondElement(condFields.find(item => item.name === initCondition.condField.name)?.hasElement || false);
|
|
||||||
//Определяем доступность "Элемент результата"
|
|
||||||
setHasResElement(resFields.find(item => item.name === initCondition.resField.name)?.hasElement || false);
|
|
||||||
//Сбрасываем признак инициализации
|
|
||||||
setInit(false);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [init]);
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!init ? (
|
|
||||||
<P8PConfigDialog
|
|
||||||
title={`${condition ? TITLES.UPDATE : TITLES.INSERT} условия`}
|
|
||||||
onOk={handleOk}
|
|
||||||
onCancel={handleCancel}
|
|
||||||
okDisabled={isConditionOkDisabled(state)}
|
|
||||||
>
|
|
||||||
<Stack direction={"column"} spacing={1}>
|
|
||||||
<Box>
|
|
||||||
<Box sx={STYLES.BOX_COND_GROUP}>
|
|
||||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("160px")}>
|
|
||||||
<InputLabel id={"condField-label"}>Поле</InputLabel>
|
|
||||||
<Select
|
|
||||||
name={"condField"}
|
|
||||||
value={state.condField.name}
|
|
||||||
labelId={"condField-label"}
|
|
||||||
label={"Поле"}
|
|
||||||
onChange={handleCondFieldChange}
|
|
||||||
>
|
|
||||||
{condFields.map((item, index) => (
|
|
||||||
<MenuItem value={item.name} key={index}>
|
|
||||||
{item.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("70px")}>
|
|
||||||
<InputLabel id={"condOperator-label"}>Оператор</InputLabel>
|
|
||||||
<Select
|
|
||||||
name={"condOperator"}
|
|
||||||
value={state.condOperator.name}
|
|
||||||
labelId={"condOperator-label"}
|
|
||||||
label={"Оператор"}
|
|
||||||
onChange={handleOperatorChange}
|
|
||||||
>
|
|
||||||
{P8P_CC_OPERATORS.map((item, index) => (
|
|
||||||
<MenuItem value={item.name} key={index}>
|
|
||||||
{item.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<TextField
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={state.condValue}
|
|
||||||
label={"Значение"}
|
|
||||||
name={"condValue"}
|
|
||||||
onChange={handleChange}
|
|
||||||
sx={STYLES.BOX_COND_ELEMENT("160px")}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
{hasCondElement ? (
|
|
||||||
<TextField
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
sx={{ pb: 1.25 }}
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={state.condElement}
|
|
||||||
label={"Элемент условия"}
|
|
||||||
name={"condElement"}
|
|
||||||
onChange={handleChange}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Box sx={STYLES.BOX_COND_GROUP}>
|
|
||||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("160px")}>
|
|
||||||
<InputLabel id={"resField-label"}>Поле результата</InputLabel>
|
|
||||||
<Select
|
|
||||||
name={"resField"}
|
|
||||||
value={state.resField.name}
|
|
||||||
labelId={"resField-label"}
|
|
||||||
label={"Поле результата"}
|
|
||||||
onChange={handleResFieldChange}
|
|
||||||
>
|
|
||||||
{resFields.map((item, index) => (
|
|
||||||
<MenuItem value={item.name} key={index}>
|
|
||||||
{item.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<TextField
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={state.resValue}
|
|
||||||
label={"Результат"}
|
|
||||||
name={"resValue"}
|
|
||||||
onChange={handleChange}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
{hasResElement ? (
|
|
||||||
<TextField
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
sx={{ pb: 1.25 }}
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={state.resElement}
|
|
||||||
label={"Элемент результата"}
|
|
||||||
name={"resElement"}
|
|
||||||
onChange={handleChange}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</P8PConfigDialog>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Редактор условия
|
|
||||||
P8PCCEditor.propTypes = {
|
|
||||||
condition: P8P_CC_SHAPE,
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
onCancel: PropTypes.func.isRequired,
|
|
||||||
condFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
|
||||||
resFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PCCEditor };
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Вспомогательные ресурсы компонента "Редактор условия"
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Функции операторов условий
|
|
||||||
const P8P_CC_OPERATOR_FUNC = {
|
|
||||||
equal: (objValue, value) => objValue == value,
|
|
||||||
notEqual: (objValue, value) => objValue != value,
|
|
||||||
lessEqual: (objValue, value) => objValue <= value,
|
|
||||||
less: (objValue, value) => objValue < value,
|
|
||||||
greaterEqual: (objValue, value) => objValue >= value,
|
|
||||||
greater: (objValue, value) => objValue > value,
|
|
||||||
in: (objValue, value) =>
|
|
||||||
value
|
|
||||||
.split(",")
|
|
||||||
.map(item => item.trim())
|
|
||||||
.includes(objValue + "")
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//Загрузка условий в объект
|
|
||||||
const loadConditionsToObject = (obj, conditions) => {
|
|
||||||
//Инициализируем новый объект
|
|
||||||
let newObj = { ...obj };
|
|
||||||
//Инициализируем функцию оператора
|
|
||||||
let operatorFunc;
|
|
||||||
//Если изначальный индикатор загружен
|
|
||||||
if (Object.keys(newObj).length !== 0) {
|
|
||||||
//Обходим условия
|
|
||||||
conditions.map(item => {
|
|
||||||
//Функция оператора
|
|
||||||
operatorFunc = P8P_CC_OPERATOR_FUNC[item.condOperator.value];
|
|
||||||
//Проверяем условие
|
|
||||||
if (operatorFunc && operatorFunc(newObj[item.condField.value], item.condValue)) {
|
|
||||||
//Устанавливаем поле в новое значение
|
|
||||||
newObj[item.resField.value] = item.resValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Возвращаем новый объект
|
|
||||||
return newObj;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Считывание результатов условия
|
|
||||||
const getConditionsValues = (obj, conditions, element = "") => {
|
|
||||||
//Инициализируем функцию оператора
|
|
||||||
let operatorFunc;
|
|
||||||
//Инициализируем значение поля
|
|
||||||
let condFieldValue = "";
|
|
||||||
//Инициализируем результат
|
|
||||||
let resObject = {};
|
|
||||||
//Обходим условия
|
|
||||||
conditions.map(item => {
|
|
||||||
//Определяем значение поля условия
|
|
||||||
condFieldValue = item.condElement ? obj[item.condElement] : obj[item.condField.value];
|
|
||||||
//Функция оператора
|
|
||||||
operatorFunc = P8P_CC_OPERATOR_FUNC[item.condOperator.value];
|
|
||||||
//Проверяем условие
|
|
||||||
if (operatorFunc && operatorFunc(condFieldValue, item.condValue)) {
|
|
||||||
//Если в условии нет элемента результата или он равен текущему элементу
|
|
||||||
if (!item.resElement || (element && item.resElement === element)) {
|
|
||||||
resObject[item.resField.value] = item.resValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//Возвращаем новый объект
|
|
||||||
return resObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Доступность сохранения настроек условия
|
|
||||||
const isConditionOkDisabled = condition => (!condition.condField.value || !condition.condOperator.value || !condition.resField.value ? true : false);
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { loadConditionsToObject, getConditionsValues, isConditionOkDisabled };
|
|
||||||
@ -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,41 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Дополнительные настройки источников
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Наименование параметра данных компонента, полученных с сервера
|
|
||||||
const P8P_COMPONENT_SETTINGS_RESP_ARGS = {
|
|
||||||
INDICATOR: "XINDICATOR",
|
|
||||||
CHART: "XCHART",
|
|
||||||
TABLE: "XDATA_GRID"
|
|
||||||
};
|
|
||||||
|
|
||||||
//Наименование путей компонента
|
|
||||||
const P8P_COMPONENT_SETTINGS_PATHS = {
|
|
||||||
INDICATOR: "indicator",
|
|
||||||
CHART: "chart",
|
|
||||||
TABLE: "table",
|
|
||||||
FORM: "form"
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8P_COMPONENT_SETTINGS_RESP_ARGS, P8P_COMPONENT_SETTINGS_PATHS };
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Условия
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { P8PCCEditor } from "./p8p_component_condition/editor"; //Редактор условия
|
|
||||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
|
||||||
import { P8P_CCS_INITIAL, P8P_CC_SHAPE, P8P_CC_FIELD_PRM_SHAPE } from "./p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Условия
|
|
||||||
const P8PConditions = ({ conditions = P8P_CCS_INITIAL, condFields, resFields, onChange = null } = {}) => {
|
|
||||||
//Собственное состояние - редактор условий
|
|
||||||
const [conditionEditor, setConditionEditor] = useState({ display: false, index: null });
|
|
||||||
|
|
||||||
//При изменении условий
|
|
||||||
const handleConditionsChange = conditions => onChange && onChange(conditions);
|
|
||||||
|
|
||||||
//При добавлении нового условия
|
|
||||||
const handleConditionAdd = () => setConditionEditor({ display: true, index: null });
|
|
||||||
|
|
||||||
//При нажатии на условие
|
|
||||||
const handleConditionClick = i => setConditionEditor({ display: true, index: i });
|
|
||||||
|
|
||||||
//При удалении условия
|
|
||||||
const handleConditionDelete = i => {
|
|
||||||
const newConditions = [...conditions];
|
|
||||||
newConditions.splice(i, 1);
|
|
||||||
handleConditionsChange(newConditions);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При отмене сохранения изменений условия
|
|
||||||
const handleConditionCancel = () => setConditionEditor({ display: false, index: null });
|
|
||||||
|
|
||||||
//При сохранении изменений условия
|
|
||||||
const handleConditionSave = condition => {
|
|
||||||
const newConditions = [...conditions];
|
|
||||||
conditionEditor.index == null ? newConditions.push({ ...condition }) : (newConditions[conditionEditor.index] = { ...condition });
|
|
||||||
handleConditionsChange(newConditions);
|
|
||||||
setConditionEditor({ display: false, index: null });
|
|
||||||
};
|
|
||||||
|
|
||||||
//Определяем структуру условий для отображения
|
|
||||||
const conditionChips = conditions.map(item => {
|
|
||||||
//Собираем текст условия
|
|
||||||
let text = `${item.condField.name} ${item.condOperator.name} ${item.condValue}`;
|
|
||||||
//Считываем поле результата
|
|
||||||
let resField = resFields.find(field => field.name === item.resField.name);
|
|
||||||
//Формируем структуру для отображения карточки действия
|
|
||||||
return { text: text, title: text, icon: resField?.icon, iconTitle: resField?.name };
|
|
||||||
});
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{conditionEditor.display && (
|
|
||||||
<P8PCCEditor
|
|
||||||
condition={conditionEditor.index !== null ? { ...conditions[conditionEditor.index] } : null}
|
|
||||||
onCancel={handleConditionCancel}
|
|
||||||
onOk={handleConditionSave}
|
|
||||||
condFields={condFields}
|
|
||||||
resFields={resFields}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<P8PChipList items={conditionChips} onClick={handleConditionClick} onDelete={handleConditionDelete} />
|
|
||||||
<Button startIcon={<Icon>add</Icon>} onClick={handleConditionAdd}>
|
|
||||||
Добавить условие
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - условия
|
|
||||||
P8PConditions.propTypes = {
|
|
||||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
|
||||||
condFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
|
||||||
resFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
|
||||||
onChange: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PConditions };
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Диалог настройки
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { P8PDialog, P8P_DIALOG_WIDTH } from "../p8p_dialog"; //Типовой диалог
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Диалог настройки
|
|
||||||
const P8PConfigDialog = ({ title, children, width, onOk, onCancel, okDisabled = false }) => {
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<P8PDialog title={title} onOk={onOk} onCancel={onCancel} width={width} okDisabled={okDisabled}>
|
|
||||||
{children}
|
|
||||||
</P8PDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Диалог настройки
|
|
||||||
P8PConfigDialog.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
|
||||||
width: PropTypes.oneOf(Object.values(P8P_DIALOG_WIDTH)),
|
|
||||||
onOk: PropTypes.func,
|
|
||||||
onCancel: PropTypes.func,
|
|
||||||
okDisabled: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PConfigDialog };
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Источник данных
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Stack, IconButton, Icon, Typography, Button, Card, CardContent, CardActions, CardActionArea } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { BUTTONS, TEXTS } from "../../../app.text"; //Общие текстовые ресурсы
|
|
||||||
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"; //Диалог настройки источника данных
|
|
||||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Источник данных
|
|
||||||
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 argChips =
|
|
||||||
configured && dataSource.arguments.map(argument => ({ text: `:${argument.name} = ${argument.valueSource || argument.value || "NULL"}` }));
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
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}>
|
|
||||||
<P8PChipList items={argChips} />
|
|
||||||
</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,87 +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: "",
|
|
||||||
query: "",
|
|
||||||
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,253 +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 { ApplicationCtx } 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"; //Хуки источников данных
|
|
||||||
import { P8PDataSourceQuerySelector } from "./p8p_data_source_query_selector"; //Диалог выбора записи редактора запросов
|
|
||||||
import { hasValue } from "../../core/utils"; //Вспомогательные функции
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Диалог настройки источника данных
|
|
||||||
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 [openQuerySelector, setOpenQuerySelector] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - доступность полей выбора источника
|
|
||||||
const [disabledFields, setDisabledFields] = useState({ query: false, userProc: false });
|
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
|
||||||
const { pOnlineShowDictionary } = useContext(ApplicationCtx);
|
|
||||||
|
|
||||||
//Установка значения/привязки аргумента
|
|
||||||
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, "");
|
|
||||||
|
|
||||||
//Открытие диалога выбора запроса
|
|
||||||
const handleOpenQuerySelector = () => setOpenQuerySelector(true);
|
|
||||||
|
|
||||||
//Закрытие диалога выбора запроса
|
|
||||||
const handleCancelQuerySelector = () => setOpenQuerySelector(false);
|
|
||||||
|
|
||||||
//При нажатии на очистку мнемокода запроса
|
|
||||||
const handleQueryClearClick = () => setState({ ...P8P_DATA_SOURCE_INITIAL });
|
|
||||||
|
|
||||||
//При нажатии на выбор запроса в качестве источника данных
|
|
||||||
const handleQuerySelectClick = query => {
|
|
||||||
setState(pv => ({ ...pv, type: P8P_DATA_SOURCE_TYPE.QUERY, query: query.code }));
|
|
||||||
handleCancelQuerySelector();
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении описания пользовательской процедуры
|
|
||||||
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]);
|
|
||||||
|
|
||||||
//При изменении источника
|
|
||||||
useEffect(() => {
|
|
||||||
/*
|
|
||||||
Если выбран запрос - блокируем выбор процедуры
|
|
||||||
Если выбрана процедура - блокируем выбор запроса
|
|
||||||
Ничего не выбрано - доступны все варианты
|
|
||||||
*/
|
|
||||||
hasValue(state.query)
|
|
||||||
? setDisabledFields({ query: false, userProc: true })
|
|
||||||
: hasValue(state.userProc)
|
|
||||||
? setDisabledFields({ query: true, userProc: false })
|
|
||||||
: setDisabledFields({ query: false, userProc: false });
|
|
||||||
}, [state.query, state.userProc]);
|
|
||||||
|
|
||||||
//Список значений
|
|
||||||
const values = Object.keys(valueProviders);
|
|
||||||
|
|
||||||
//Наличие значений
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
|
|
||||||
//Доступность сохранения настройки источника данных
|
|
||||||
const okDisabled = !state.query && !state.userProc ? true : false;
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{openQuerySelector ? (
|
|
||||||
<P8PDataSourceQuerySelector current={state.query} onSelect={handleQuerySelectClick} onCancel={handleCancelQuerySelector} />
|
|
||||||
) : null}
|
|
||||||
<P8PConfigDialog title={TITLES.DATA_SOURCE_CONFIG} onOk={handleOk} onCancel={handleCancel} okDisabled={okDisabled}>
|
|
||||||
<Stack direction={"column"} spacing={1}>
|
|
||||||
{valueProvidersMenu}
|
|
||||||
{/* ДОРАБАТЫВАТЬ ПОСЛЕ РЕАЛИЗАЦИИ ЗАПРОСОВ */}
|
|
||||||
<TextField
|
|
||||||
type={"text"}
|
|
||||||
variant={"standard"}
|
|
||||||
value={state.query}
|
|
||||||
label={CAPTIONS.QUERY}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
InputProps={{
|
|
||||||
readOnly: true,
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton onClick={handleQueryClearClick} disabled={disabledFields.query}>
|
|
||||||
<Icon>clear</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton onClick={handleOpenQuerySelector} disabled={disabledFields.query}>
|
|
||||||
<Icon>list</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
disabled={disabledFields.query}
|
|
||||||
/>
|
|
||||||
<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} disabled={disabledFields.userProc}>
|
|
||||||
<Icon>clear</Icon>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton onClick={handleUserProcSelectClick} disabled={disabledFields.userProc}>
|
|
||||||
<Icon>list</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
disabled={disabledFields.userProc}
|
|
||||||
/>
|
|
||||||
{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,229 +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 { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE } from "./p8p_data_source_common"; //Общие ресурсы источника данных
|
|
||||||
import { getConditionsValues } from "./p8p_component_condition/util"; //Вспомогательные ресурсы условий
|
|
||||||
import { getHandlersByActions } from "./p8p_component_action/util"; //Вспомогательные ресурсы действий
|
|
||||||
import { ApplicationCtx } from "../../context/application"; //Контекст приложения
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Описание пользовательской процедуры
|
|
||||||
const useUserProcDesc = ({ code, refresh }) => {
|
|
||||||
//Собственное состояние - флаг загрузки
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - данные
|
|
||||||
const [data, setData] = useState(null);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
|
||||||
|
|
||||||
//При необходимости обновить данные компонента
|
|
||||||
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, componentRespArg = "" }) => {
|
|
||||||
//Контроллер для прерывания запросов
|
|
||||||
const abortController = useRef(null);
|
|
||||||
|
|
||||||
//Собственное состояние - параметры исполнения
|
|
||||||
const [state, setState] = useState({ stored: null, storedArgs: [], respArg: null, reqSet: false });
|
|
||||||
|
|
||||||
//Собственное состояние - флаг загрузки
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - данные
|
|
||||||
const [data, setData] = useState({ componentData: {}, init: false });
|
|
||||||
|
|
||||||
//Собственное состояние - ошибка получения данных
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
|
|
||||||
//Собственное состояние - наличие настроек
|
|
||||||
const [haveConfing, setHaveConfig] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - наличие данных
|
|
||||||
const [haveData, setHaveData] = useState(false);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
|
||||||
|
|
||||||
//При необходимости обновление информации о наличии данных
|
|
||||||
useEffect(() => {
|
|
||||||
setHaveData(data.init === true && !error ? true : false);
|
|
||||||
}, [data.init, error]);
|
|
||||||
|
|
||||||
//При необходимости обновить данные
|
|
||||||
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({ componentData: { ...data[componentRespArg] }, init: true });
|
|
||||||
} catch (e) {
|
|
||||||
if (e.message !== client.ERR_ABORTED) {
|
|
||||||
setError(formatErrorMessage(e.message).text);
|
|
||||||
setData({ componentData: {}, init: false });
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (state.reqSet) {
|
|
||||||
if (state.stored) loadData();
|
|
||||||
} else setData({ componentData: {}, init: false });
|
|
||||||
return () => abortController.current?.abort?.();
|
|
||||||
}, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored, componentRespArg]);
|
|
||||||
|
|
||||||
//При изменении свойств
|
|
||||||
useEffect(() => {
|
|
||||||
//Устанавливаем признак наличия настроек
|
|
||||||
setHaveConfig(dataSource?.stored ? true : false);
|
|
||||||
//Устанавливаем параметры исполнения
|
|
||||||
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({ componentData: {}, init: false });
|
|
||||||
}
|
|
||||||
return { stored, respArg, storedArgs, reqSet };
|
|
||||||
} else return pv;
|
|
||||||
} else return pv;
|
|
||||||
});
|
|
||||||
}, [dataSource, values]);
|
|
||||||
|
|
||||||
//Возвращаем интерфейс хука
|
|
||||||
return [data.componentData, error, haveConfing, haveData, isLoading];
|
|
||||||
};
|
|
||||||
|
|
||||||
//Изменение данных компонента с учетом условий
|
|
||||||
const useConditions = ({ componentData, conditions }) => {
|
|
||||||
//Собственное состояние - текущие условия компонента
|
|
||||||
const [currentConditions, setCurrentConditions] = useState([]);
|
|
||||||
|
|
||||||
//Собственное состояние - данные
|
|
||||||
const [data, setData] = useState();
|
|
||||||
|
|
||||||
//При обновлении условий компонента
|
|
||||||
useEffect(() => {
|
|
||||||
//Если условия изменились
|
|
||||||
if (JSON.stringify(currentConditions) != JSON.stringify(conditions)) {
|
|
||||||
//Устанавливаем новые условия
|
|
||||||
setCurrentConditions(conditions);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [conditions]);
|
|
||||||
|
|
||||||
//При обновлении данных или условий компонента
|
|
||||||
useEffect(() => {
|
|
||||||
//Если есть текущие условия
|
|
||||||
if (currentConditions.length !== 0) {
|
|
||||||
//Устанавливаем данные с учетом условий
|
|
||||||
setData({ ...componentData, ...getConditionsValues(componentData, currentConditions) });
|
|
||||||
} else {
|
|
||||||
//Оставляем данные компонента
|
|
||||||
setData({ ...componentData });
|
|
||||||
}
|
|
||||||
}, [currentConditions, componentData]);
|
|
||||||
|
|
||||||
//Возвращаем интерфейс хука
|
|
||||||
return [data];
|
|
||||||
};
|
|
||||||
|
|
||||||
//Получение обработчиков компонента
|
|
||||||
const useComponentHandlers = ({ actions = [], onValuesChange = null, getCustomTypeValue = null }) => {
|
|
||||||
//Контроллер для текущего состояния действий
|
|
||||||
const currentActions = useRef([]);
|
|
||||||
|
|
||||||
//Собственное состояние - обработчики компонента
|
|
||||||
const [handlers, setHandlers] = useState({});
|
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
|
||||||
const { configUrlBase, pOnlineShowTab, pOnlineShowUnit } = useContext(ApplicationCtx);
|
|
||||||
|
|
||||||
//При необходимости обновления информации об обработчиках
|
|
||||||
useEffect(() => {
|
|
||||||
//Если изменились действия или параметры
|
|
||||||
if (JSON.stringify(currentActions.current) != JSON.stringify(actions)) {
|
|
||||||
//Считываем обработчики компонента
|
|
||||||
setHandlers(getHandlersByActions(actions, pOnlineShowUnit, configUrlBase, pOnlineShowTab, onValuesChange, getCustomTypeValue));
|
|
||||||
//Устанавливаем контроллер текущих действий
|
|
||||||
currentActions.current = actions;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [actions]);
|
|
||||||
|
|
||||||
//Возвращаем интерфейс хука
|
|
||||||
return [handlers];
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useUserProcDesc, useDataSource, useConditions, useComponentHandlers };
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Диалог выбора записи редактора запросов
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { Stack, List, ListItem, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
LIST: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto" }
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Диалог выбора записи редактора запросов
|
|
||||||
const P8PDataSourceQuerySelector = ({ current = null, onSelect, onCancel }) => {
|
|
||||||
//!!! НА РЕАЛИЗАЦИИ
|
|
||||||
const test = [{ code: "TestCode", name: "TestName" }];
|
|
||||||
//При выборе элемента списка
|
|
||||||
const handleSelectClick = query => {
|
|
||||||
onSelect && onSelect(query);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<P8PConfigDialog title={"Запросы"} onCancel={onCancel}>
|
|
||||||
<List sx={STYLES.LIST}>
|
|
||||||
{test.map((query, i) => {
|
|
||||||
const selected = query.code === current;
|
|
||||||
return (
|
|
||||||
<ListItem key={i}>
|
|
||||||
<ListItemButton onClick={() => handleSelectClick(query)} selected={selected}>
|
|
||||||
<ListItemText
|
|
||||||
primary={query.code}
|
|
||||||
secondaryTypographyProps={{ component: "div" }}
|
|
||||||
secondary={
|
|
||||||
<Stack direction={"column"}>
|
|
||||||
<Typography variant={"caption"}>{`${query.code}, ${query.name}`}</Typography>
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItemButton>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
</P8PConfigDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Диалог выбора записи редактора запросов
|
|
||||||
P8PDataSourceQuerySelector.propTypes = {
|
|
||||||
current: PropTypes.string,
|
|
||||||
onSelect: PropTypes.func.isRequired,
|
|
||||||
onCancel: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PDataSourceQuerySelector };
|
|
||||||
@ -1,62 +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, allowClose = true }) => {
|
|
||||||
//При нажатии на "Сохранить"
|
|
||||||
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>
|
|
||||||
{allowClose ? (
|
|
||||||
<IconButton onClick={() => handleSaveClick(true)} title={BUTTONS.SAVE}>
|
|
||||||
<Icon>done_all</Icon>
|
|
||||||
</IconButton>
|
|
||||||
) : null}
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Контейнер редактора
|
|
||||||
P8PEditorBox.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
|
||||||
onSave: PropTypes.func,
|
|
||||||
allowClose: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
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,62 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Компонент: Панель инструментов редактора
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import React from "react"; //Классы React
|
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
|
||||||
import { IconButton, Icon, Stack, Grid } 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,
|
|
||||||
customRenderer: PropTypes.func
|
|
||||||
});
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Панель инструментов редактора
|
|
||||||
const P8PEditorToolBar = ({ items = [] }) => {
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Stack direction={"row"} p={1}>
|
|
||||||
<Grid container columns={items.length}>
|
|
||||||
{items.map((item, i) => (
|
|
||||||
<Grid item size={{ xs: 2, sm: 4, md: 4 }} key={i}>
|
|
||||||
{item.customRenderer ? (
|
|
||||||
item.customRenderer({ icon: item.icon, title: item.title, disabled: item?.disabled === true, onClick: item.onClick })
|
|
||||||
) : (
|
|
||||||
<IconButton onClick={item.onClick} title={item.title} disabled={item?.disabled === true}>
|
|
||||||
<Icon>{item.icon}</Icon>
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - Панель инструментов редактора
|
|
||||||
P8PEditorToolBar.propTypes = {
|
|
||||||
items: PropTypes.arrayOf(P8P_EDITOR_TOOL_BAR_ITEM_SHAPE)
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8PEditorToolBar };
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
|
||||||
Общие ресурсы редакторов
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Стили
|
|
||||||
const STYLES = {
|
|
||||||
CHIP: (fullWidth = false, multiLine = false) => ({
|
|
||||||
...(multiLine ? { height: "auto" } : {}),
|
|
||||||
"& .MuiChip-label": {
|
|
||||||
...(multiLine
|
|
||||||
? {
|
|
||||||
display: "block",
|
|
||||||
whiteSpace: "normal"
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
...(fullWidth ? { width: "100%" } : {})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//Проверка корректности наименования элемента формы
|
|
||||||
const isElementNameCorrect = elementName => {
|
|
||||||
return new RegExp(/^[\w\d_]*$/).test(elementName);
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { STYLES, isElementNameCorrect };
|
|
||||||
@ -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={e => (onOk ? onOk(e) : null)}>
|
|
||||||
<DialogTitle>{title}</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: hint }}></div>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={e => (onOk ? onOk(e) : 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,10 +7,9 @@
|
|||||||
//Подключение библиотек
|
//Подключение библиотек
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
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"; //Диаграммы и графики
|
||||||
import { useP8PChart } from "./p8p_chart_hooks"; //Хук для графика
|
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -38,26 +37,23 @@ const P8P_CHART_DATASET_SHAPE = PropTypes.shape({
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//График
|
//График
|
||||||
const P8PChart = ({ type, title, legendPosition, options = {}, labels = [], datasets = [], onClick, style }) => {
|
const P8PChart = ({ type, title, legendPosition, options, labels, datasets, onClick, style }) => {
|
||||||
//Ссылки на DOM
|
//Ссылки на DOM
|
||||||
const chartCanvasRef = useRef(null);
|
const chartCanvasRef = useRef(null);
|
||||||
const chartRef = useRef(null);
|
const chartRef = useRef(null);
|
||||||
|
|
||||||
//Обработка нажатия на элемент графика
|
//Обработка нажатия на элемент графика
|
||||||
const handleClick = useCallback(
|
const handleClick = e => {
|
||||||
e => {
|
const bar = chartRef.current.getElementsAtEventForMode(e, "nearest", { intersect: true }, true)[0];
|
||||||
const bar = chartRef.current.getElementsAtEventForMode(e, "nearest", { intersect: true }, true)[0];
|
if (onClick && bar)
|
||||||
if (onClick && bar)
|
onClick({
|
||||||
onClick({
|
datasetIndex: bar.datasetIndex,
|
||||||
datasetIndex: bar.datasetIndex,
|
itemIndex: bar.index,
|
||||||
itemIndex: bar.index,
|
item: chartRef.current.data.datasets[bar.datasetIndex].items
|
||||||
item: chartRef.current.data.datasets[bar.datasetIndex].items
|
? chartRef.current.data.datasets[bar.datasetIndex].items[bar.index]
|
||||||
? chartRef.current.data.datasets[bar.datasetIndex].items[bar.index]
|
: null
|
||||||
: null
|
});
|
||||||
});
|
};
|
||||||
},
|
|
||||||
[onClick]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При подключении к старнице
|
//При подключении к старнице
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -93,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 (
|
||||||
@ -112,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
|
||||||
@ -122,4 +117,4 @@ P8PChart.propTypes = {
|
|||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export { P8P_CHART_TYPE, P8PChart, useP8PChart };
|
export { P8P_CHART_TYPE, P8PChart };
|
||||||
|
|||||||
@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга
|
|
||||||
Хуки для графиков
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React
|
|
||||||
import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Константы - значения по умолчанию
|
|
||||||
const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию о графике
|
|
||||||
const CHART_NODE_NAME_DEF = "XCHART"; //Наименование узла, содержащего информацию о графике
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук для P8PChart
|
|
||||||
const useP8PChart = ({
|
|
||||||
stored,
|
|
||||||
respArg = RESP_ARG_DEF,
|
|
||||||
contentNodeName = CHART_NODE_NAME_DEF,
|
|
||||||
storedArgs = {},
|
|
||||||
executeStoredArgs = {},
|
|
||||||
allowDataLoad = () => true
|
|
||||||
}) => {
|
|
||||||
//Собственное состояние - график
|
|
||||||
const [chart, setChart] = useState({
|
|
||||||
type: null,
|
|
||||||
title: null,
|
|
||||||
legendPosition: null,
|
|
||||||
labels: [],
|
|
||||||
datasets: []
|
|
||||||
});
|
|
||||||
|
|
||||||
//Собственное состояние - признак загрузки данных
|
|
||||||
const [isDataLoaded, setIsDataLoaded] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - флаг загрузки
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - необходимость обновления данных
|
|
||||||
const [reload, setReload] = useState(true);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные агрументы
|
|
||||||
const refStoredArgs = useRef(storedArgs);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные параметры вызова процедуры
|
|
||||||
const refExecuteStoredArgs = useRef(executeStoredArgs);
|
|
||||||
|
|
||||||
//Признак допустимости обновления данных
|
|
||||||
const isAllowDataLoad = useMemo(() => {
|
|
||||||
return allowDataLoad();
|
|
||||||
}, [allowDataLoad]);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored, isRespErr } = useContext(BackEndCtx);
|
|
||||||
|
|
||||||
//Загрузка данных графика с сервера
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const data = await executeStored({ stored, respArg, args: { ...refStoredArgs.current }, ...refExecuteStoredArgs.current });
|
|
||||||
setChart(pv => ({ ...pv, ...data[contentNodeName] }));
|
|
||||||
//Устанавливаем признак загрузки данных с учетом возможных ошибок
|
|
||||||
setIsDataLoaded(!isRespErr(data));
|
|
||||||
} catch (e) {
|
|
||||||
//Если произошла ошибка - данные не загружены
|
|
||||||
setIsDataLoaded(false);
|
|
||||||
} finally {
|
|
||||||
//Сбрасываем признаки загрузки и перезагрузки данных
|
|
||||||
setLoading(false);
|
|
||||||
setReload(false);
|
|
||||||
}
|
|
||||||
}, [contentNodeName, executeStored, isRespErr, respArg, stored]);
|
|
||||||
|
|
||||||
//При необходимости обновления графика
|
|
||||||
const doReload = useCallback(() => {
|
|
||||||
setReload(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//Проверка изменений параметров
|
|
||||||
const isArgsChanged = useCallback(
|
|
||||||
(currentArgs, args) => {
|
|
||||||
//Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера)
|
|
||||||
return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args);
|
|
||||||
},
|
|
||||||
[isLoading]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refStoredArgs.current, storedArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refStoredArgs.current = storedArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [storedArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров вызова процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refExecuteStoredArgs.current = executeStoredArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [executeStoredArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При необходимости обновить данные графика
|
|
||||||
useEffect(() => {
|
|
||||||
//Если необходимо перезагрузить данные и это допустимо
|
|
||||||
if (isAllowDataLoad && reload) {
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
}, [isAllowDataLoad, reload, loadData]);
|
|
||||||
|
|
||||||
//Возвращаем данные графика
|
|
||||||
return { chart, isDataLoaded, isLoading, doReload };
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useP8PChart };
|
|
||||||
@ -1,820 +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"; //Вспомогательный функции
|
|
||||||
import { useP8PCyclogram } from "./p8p_cyclogram_hooks"; //Хук для циклограммы
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Уровни масштаба
|
|
||||||
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, useP8PCyclogram };
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга
|
|
||||||
Хуки для циклограмм
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React
|
|
||||||
import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { formatDateJSONDateOnly } from "../core/utils"; //Вспомогательные функции
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Константы - значения по умолчанию
|
|
||||||
const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию о циклограмме
|
|
||||||
const CG_NODE_NAME_DEF = "XCYCLOGRAM"; //Наименование узла, содержащего информацию о циклограмме
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук для P8PCyclogram
|
|
||||||
const useP8PCyclogram = ({
|
|
||||||
stored,
|
|
||||||
respArg = RESP_ARG_DEF,
|
|
||||||
contentNodeName = CG_NODE_NAME_DEF,
|
|
||||||
storedArgs = {},
|
|
||||||
executeStoredArgs = {},
|
|
||||||
allowDataLoad = () => true
|
|
||||||
}) => {
|
|
||||||
//Собственное состояние - циклограмма
|
|
||||||
const [cyclogram, setCyclogram] = useState({
|
|
||||||
columns: [],
|
|
||||||
groups: [],
|
|
||||||
tasks: [],
|
|
||||||
taskAttributes: [],
|
|
||||||
title: null,
|
|
||||||
lineHeight: 0,
|
|
||||||
zoom: 1,
|
|
||||||
zoomBar: true
|
|
||||||
});
|
|
||||||
|
|
||||||
//Собственное состояние - признак загрузки данных
|
|
||||||
const [isDataLoaded, setIsDataLoaded] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - флаг загрузки
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - необходимость обновления данных
|
|
||||||
const [reload, setReload] = useState(true);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные агрументы
|
|
||||||
const refStoredArgs = useRef(storedArgs);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные параметры вызова процедуры
|
|
||||||
const refExecuteStoredArgs = useRef(executeStoredArgs);
|
|
||||||
|
|
||||||
//Признак допустимости обновления данных
|
|
||||||
const isAllowDataLoad = useMemo(() => {
|
|
||||||
return allowDataLoad();
|
|
||||||
}, [allowDataLoad]);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored, isRespErr } = useContext(BackEndCtx);
|
|
||||||
|
|
||||||
//Загрузка данных циклограммы с сервера
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const data = await executeStored({
|
|
||||||
stored,
|
|
||||||
args: { ...refStoredArgs.current },
|
|
||||||
attributeValueProcessor: (name, val) => (["ddate_start", "ddate_end"].includes(name) ? formatDateJSONDateOnly(val) : val),
|
|
||||||
respArg,
|
|
||||||
...refExecuteStoredArgs.current
|
|
||||||
});
|
|
||||||
setCyclogram(pv => ({ ...pv, ...data[contentNodeName] }));
|
|
||||||
//Устанавливаем признак загрузки данных с учетом возможных ошибок
|
|
||||||
setIsDataLoaded(!isRespErr(data));
|
|
||||||
} catch (e) {
|
|
||||||
//Если произошла ошибка - данные не загружены
|
|
||||||
setIsDataLoaded(false);
|
|
||||||
} finally {
|
|
||||||
//Сбрасываем признаки загрузки и перезагрузки данных
|
|
||||||
setLoading(false);
|
|
||||||
setReload(false);
|
|
||||||
}
|
|
||||||
}, [contentNodeName, executeStored, isRespErr, respArg, stored]);
|
|
||||||
|
|
||||||
//При необходимости обновления циклограммы
|
|
||||||
const doReload = useCallback(() => {
|
|
||||||
setReload(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//Проверка изменений параметров
|
|
||||||
const isArgsChanged = useCallback(
|
|
||||||
(currentArgs, args) => {
|
|
||||||
//Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера)
|
|
||||||
return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args);
|
|
||||||
},
|
|
||||||
[isLoading]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refStoredArgs.current, storedArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refStoredArgs.current = storedArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [storedArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров вызова процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refExecuteStoredArgs.current = executeStoredArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [executeStoredArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При необходимости обновить данные циклограммы
|
|
||||||
useEffect(() => {
|
|
||||||
if (isAllowDataLoad && reload) {
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
}, [isAllowDataLoad, reload, loadData]);
|
|
||||||
|
|
||||||
//Возвращаем данные циклограммы
|
|
||||||
return { cyclogram, isDataLoaded, isLoading, doReload };
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useP8PCyclogram };
|
|
||||||
@ -9,17 +9,7 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from "react"; //Классы React
|
import React, { useState, useEffect } from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import {
|
import { P8PTable, P8P_TABLE_SIZE, P8P_TABLE_DATA_TYPE, P8P_TABLE_FILTER_SHAPE, P8P_TABLE_MORE_HEIGHT, P8P_TABLE_FILTERS_HEIGHT } from "./p8p_table"; //Таблица
|
||||||
P8PTable,
|
|
||||||
P8P_TABLE_SIZE,
|
|
||||||
P8P_TABLE_DATA_TYPE,
|
|
||||||
P8P_TABLE_FILTER_SHAPE,
|
|
||||||
P8P_TABLE_MORE_HEIGHT,
|
|
||||||
P8P_TABLE_FILTERS_HEIGHT,
|
|
||||||
P8P_TABLE_PAGINATOR_ALIGN,
|
|
||||||
P8P_TABLE_PAGINATOR_POSITION
|
|
||||||
} from "./p8p_table"; //Таблица
|
|
||||||
import { useP8PDataGrid } from "./p8p_data_grid_hooks"; //Хук для таблицы данных
|
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -40,33 +30,21 @@ const P8P_DATA_GRID_MORE_HEIGHT = P8P_TABLE_MORE_HEIGHT;
|
|||||||
//Высота фильтров таблицы
|
//Высота фильтров таблицы
|
||||||
const P8P_DATA_GRID_FILTERS_HEIGHT = P8P_TABLE_FILTERS_HEIGHT;
|
const P8P_DATA_GRID_FILTERS_HEIGHT = P8P_TABLE_FILTERS_HEIGHT;
|
||||||
|
|
||||||
//Размещение области страниц по вертикали
|
|
||||||
const P8P_DATA_GRID_PAGINATOR_ALIGN = P8P_TABLE_PAGINATOR_ALIGN;
|
|
||||||
|
|
||||||
//Размещение области страниц по горизонтали
|
|
||||||
const P8P_DATA_GRID_PAGINATOR_POSITION = P8P_TABLE_PAGINATOR_POSITION;
|
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Таблица данных
|
//Таблица данных
|
||||||
const P8PDataGrid = ({
|
const P8PDataGrid = ({
|
||||||
style = {},
|
columnsDef,
|
||||||
tableStyle = {},
|
|
||||||
columnsDef = [],
|
|
||||||
filtersInitial,
|
filtersInitial,
|
||||||
groups = [],
|
groups,
|
||||||
rows = [],
|
rows,
|
||||||
size,
|
size,
|
||||||
pageNumber = 1,
|
|
||||||
pagesCount = 0,
|
|
||||||
pagesAlign = P8P_DATA_GRID_PAGINATOR_ALIGN.RIGHT,
|
|
||||||
pagesPosition = P8P_DATA_GRID_PAGINATOR_POSITION.BOTTOM,
|
|
||||||
fixedHeader = false,
|
fixedHeader = false,
|
||||||
fixedColumns = 0,
|
fixedColumns = 0,
|
||||||
morePages = false,
|
morePages = false,
|
||||||
reloading = false,
|
reloading,
|
||||||
expandable,
|
expandable,
|
||||||
orderAscMenuItemCaption,
|
orderAscMenuItemCaption,
|
||||||
orderDescMenuItemCaption,
|
orderDescMenuItemCaption,
|
||||||
@ -90,7 +68,6 @@ const P8PDataGrid = ({
|
|||||||
onOrderChanged,
|
onOrderChanged,
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
onPagesCountChanged,
|
onPagesCountChanged,
|
||||||
onPageChanged,
|
|
||||||
objectsCopier
|
objectsCopier
|
||||||
}) => {
|
}) => {
|
||||||
//Собственное состояние - сортировки
|
//Собственное состояние - сортировки
|
||||||
@ -129,9 +106,6 @@ const P8PDataGrid = ({
|
|||||||
if (onPagesCountChanged) onPagesCountChanged();
|
if (onPagesCountChanged) onPagesCountChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
//При изменении номера страницы
|
|
||||||
const handlePageChange = ({ page }) => onPageChanged && onPageChanged({ page });
|
|
||||||
|
|
||||||
//При изменении списка установленных извне фильтров
|
//При изменении списка установленных извне фильтров
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilters(filtersInitial || []);
|
setFilters(filtersInitial || []);
|
||||||
@ -140,18 +114,12 @@ const P8PDataGrid = ({
|
|||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<P8PTable
|
<P8PTable
|
||||||
style={style}
|
|
||||||
tableStyle={tableStyle}
|
|
||||||
columnsDef={columnsDef}
|
columnsDef={columnsDef}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
orders={orders}
|
orders={orders}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
size={size || P8P_DATA_GRID_SIZE.MEDIUM}
|
size={size || P8P_DATA_GRID_SIZE.MEDIUM}
|
||||||
pageNumber={pageNumber}
|
|
||||||
pagesCount={pagesCount}
|
|
||||||
pagesAlign={pagesAlign}
|
|
||||||
pagesPosition={pagesPosition}
|
|
||||||
fixedHeader={fixedHeader}
|
fixedHeader={fixedHeader}
|
||||||
fixedColumns={fixedColumns}
|
fixedColumns={fixedColumns}
|
||||||
morePages={morePages}
|
morePages={morePages}
|
||||||
@ -180,28 +148,21 @@ const P8PDataGrid = ({
|
|||||||
onOrderChanged={handleOrderChanged}
|
onOrderChanged={handleOrderChanged}
|
||||||
onFilterChanged={handleFilterChanged}
|
onFilterChanged={handleFilterChanged}
|
||||||
onPagesCountChanged={handlePagesCountChanged}
|
onPagesCountChanged={handlePagesCountChanged}
|
||||||
onPageChanged={handlePageChange}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Контроль свойств - Таблица данных
|
//Контроль свойств - Таблица данных
|
||||||
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,
|
||||||
pageNumber: PropTypes.number,
|
|
||||||
pagesCount: PropTypes.number,
|
|
||||||
pagesAlign: PropTypes.string,
|
|
||||||
pagesPosition: 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,
|
||||||
@ -225,7 +186,6 @@ P8PDataGrid.propTypes = {
|
|||||||
onOrderChanged: PropTypes.func,
|
onOrderChanged: PropTypes.func,
|
||||||
onFilterChanged: PropTypes.func,
|
onFilterChanged: PropTypes.func,
|
||||||
onPagesCountChanged: PropTypes.func,
|
onPagesCountChanged: PropTypes.func,
|
||||||
onPageChanged: PropTypes.func,
|
|
||||||
objectsCopier: PropTypes.func.isRequired
|
objectsCopier: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -239,8 +199,5 @@ export {
|
|||||||
P8P_DATA_GRID_FILTER_SHAPE,
|
P8P_DATA_GRID_FILTER_SHAPE,
|
||||||
P8P_DATA_GRID_MORE_HEIGHT,
|
P8P_DATA_GRID_MORE_HEIGHT,
|
||||||
P8P_DATA_GRID_FILTERS_HEIGHT,
|
P8P_DATA_GRID_FILTERS_HEIGHT,
|
||||||
P8P_DATA_GRID_PAGINATOR_ALIGN,
|
P8PDataGrid
|
||||||
P8P_DATA_GRID_PAGINATOR_POSITION,
|
|
||||||
P8PDataGrid,
|
|
||||||
useP8PDataGrid
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,224 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга
|
|
||||||
Хуки для таблиц данных
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React
|
|
||||||
import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { object2Base64XML } from "../core/utils"; //Вспомогательные функции
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Константы - значения по умолчанию
|
|
||||||
const DG_PAGE_SIZE_DEF = 10; //Размер страницы
|
|
||||||
const DG_NODE_NAME_DEF = "XDATA_GRID"; //Наименование узла, содержащего информацию о таблице
|
|
||||||
const FILTERS_NODE_NAME_DEF = "filters"; //Наименование узла отборов
|
|
||||||
const ORDERS_NODE_NAME_DEF = "orders"; //Наименование узла сортировок
|
|
||||||
const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию о таблице
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук для P8PDataGrid
|
|
||||||
const useP8PDataGrid = ({
|
|
||||||
stored,
|
|
||||||
respArg = RESP_ARG_DEF,
|
|
||||||
contentNodeName = DG_NODE_NAME_DEF,
|
|
||||||
filtersNodeName = FILTERS_NODE_NAME_DEF,
|
|
||||||
ordersNodeName = ORDERS_NODE_NAME_DEF,
|
|
||||||
pageSize = DG_PAGE_SIZE_DEF,
|
|
||||||
reloadDef = false,
|
|
||||||
initFilters = [],
|
|
||||||
initOrders = [],
|
|
||||||
storedArgs = {},
|
|
||||||
executeStoredArgs = {},
|
|
||||||
allowDataLoad = () => true
|
|
||||||
}) => {
|
|
||||||
//Собственное состояние - таблица данных
|
|
||||||
const [dataGrid, setDataGrid] = useState({
|
|
||||||
columnsDef: [],
|
|
||||||
groups: [],
|
|
||||||
rows: [],
|
|
||||||
filters: Array.isArray(initFilters) ? [...initFilters] : [],
|
|
||||||
orders: Array.isArray(initOrders) ? [...initOrders] : [],
|
|
||||||
pageNumber: 1,
|
|
||||||
pagesAlign: null,
|
|
||||||
pagesPosition: null,
|
|
||||||
pagesCount: 0,
|
|
||||||
fixedColumns: 0,
|
|
||||||
fixedHeader: false,
|
|
||||||
morePages: true
|
|
||||||
});
|
|
||||||
|
|
||||||
//Собственное состояние - признак загрузки данных
|
|
||||||
const [isDataLoaded, setIsDataLoaded] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - флаг загрузки
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - необходимость обновления данных
|
|
||||||
const [reload, setReload] = useState(true);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные параметры процедуры
|
|
||||||
const refStoredArgs = useRef(storedArgs);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные параметры вызова процедуры
|
|
||||||
const refExecuteStoredArgs = useRef(executeStoredArgs);
|
|
||||||
|
|
||||||
//Признак допустимости обновления данных
|
|
||||||
const isAllowDataLoad = useMemo(() => {
|
|
||||||
return allowDataLoad();
|
|
||||||
}, [allowDataLoad]);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB, isRespErr } = useContext(BackEndCtx);
|
|
||||||
|
|
||||||
//Загрузка данных таблицы с сервера
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const data = await executeStored({
|
|
||||||
stored,
|
|
||||||
args: {
|
|
||||||
CFILTERS: { VALUE: object2Base64XML(dataGrid.filters, { arrayNodeName: filtersNodeName }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
|
||||||
CORDERS: { VALUE: object2Base64XML(dataGrid.orders, { arrayNodeName: ordersNodeName }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
|
||||||
NPAGE_NUMBER: dataGrid.pageNumber,
|
|
||||||
NPAGE_SIZE: pageSize,
|
|
||||||
NINCLUDE_DEF: reloadDef ? 1 : dataGrid.dataLoaded ? 0 : 1,
|
|
||||||
...refStoredArgs.current
|
|
||||||
},
|
|
||||||
respArg,
|
|
||||||
...refExecuteStoredArgs.current
|
|
||||||
});
|
|
||||||
setDataGrid(pv => ({
|
|
||||||
...pv,
|
|
||||||
...data[contentNodeName],
|
|
||||||
columnsDef: data[contentNodeName].columnsDef ? [...data[contentNodeName].columnsDef] : pv.columnsDef || [],
|
|
||||||
rows:
|
|
||||||
data[contentNodeName].pagesCount > 0 || pv.pageNumber == 1
|
|
||||||
? [...(data[contentNodeName].rows || [])]
|
|
||||||
: [...(pv.rows || []), ...(data[contentNodeName].rows || [])],
|
|
||||||
groups: data[contentNodeName].groups
|
|
||||||
? data[contentNodeName].pagesCount > 0 || pv.pageNumber == 1
|
|
||||||
? [...(data[contentNodeName].groups || [])]
|
|
||||||
: [...(pv.groups || []), ...data[contentNodeName].groups.filter(g => !pv.groups.find(pg => pg.name == g.name))]
|
|
||||||
: [...(pv.groups || [])],
|
|
||||||
morePages: data[contentNodeName].morePages && (data[contentNodeName].rows || []).length >= pageSize
|
|
||||||
}));
|
|
||||||
//Устанавливаем признак загрузки данных с учетом возможных ошибок
|
|
||||||
setIsDataLoaded(!isRespErr(data));
|
|
||||||
} catch (e) {
|
|
||||||
//Если произошла ошибка - данные не загружены
|
|
||||||
setIsDataLoaded(false);
|
|
||||||
} finally {
|
|
||||||
//Сбрасываем признаки загрузки и перезагрузки данных
|
|
||||||
setLoading(false);
|
|
||||||
setReload(false);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
SERV_DATA_TYPE_CLOB,
|
|
||||||
contentNodeName,
|
|
||||||
dataGrid.dataLoaded,
|
|
||||||
dataGrid.filters,
|
|
||||||
dataGrid.orders,
|
|
||||||
dataGrid.pageNumber,
|
|
||||||
executeStored,
|
|
||||||
filtersNodeName,
|
|
||||||
isRespErr,
|
|
||||||
ordersNodeName,
|
|
||||||
pageSize,
|
|
||||||
reloadDef,
|
|
||||||
respArg,
|
|
||||||
stored
|
|
||||||
]);
|
|
||||||
|
|
||||||
//При изменении состояния фильтра
|
|
||||||
const handleFilterChanged = useCallback(({ filters }) => {
|
|
||||||
setDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1 }));
|
|
||||||
setReload(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//При изменении состояния сортировки
|
|
||||||
const handleOrderChanged = useCallback(({ orders }) => {
|
|
||||||
setDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));
|
|
||||||
setReload(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//При изменении количества отображаемых страниц
|
|
||||||
const handlePagesCountChanged = useCallback(() => {
|
|
||||||
setDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
|
|
||||||
setReload(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//При изменении страницы отображения
|
|
||||||
const handlePageChange = useCallback(({ page }) => {
|
|
||||||
setDataGrid(pv => ({ ...pv, pageNumber: page }));
|
|
||||||
setReload(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//При необходимости обновления таблицы
|
|
||||||
const doReload = useCallback(
|
|
||||||
({ returnOnFirstPage = false }) => {
|
|
||||||
//Если это не страничный вывод или установлен признак возврата на первую страницу
|
|
||||||
if (dataGrid.pagesCount <= 0 || returnOnFirstPage) {
|
|
||||||
setDataGrid(pv => ({ ...pv, pageNumber: 1 }));
|
|
||||||
}
|
|
||||||
setReload(true);
|
|
||||||
},
|
|
||||||
[dataGrid.pagesCount]
|
|
||||||
);
|
|
||||||
|
|
||||||
//Проверка изменений параметров
|
|
||||||
const isArgsChanged = useCallback(
|
|
||||||
(currentArgs, args) => {
|
|
||||||
//Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера)
|
|
||||||
return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args);
|
|
||||||
},
|
|
||||||
[isLoading]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refStoredArgs.current, storedArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refStoredArgs.current = storedArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [storedArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров вызова процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refExecuteStoredArgs.current = executeStoredArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [executeStoredArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При необходимости обновить данные таблицы
|
|
||||||
useEffect(() => {
|
|
||||||
if (isAllowDataLoad && reload) {
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
}, [isAllowDataLoad, reload, loadData]);
|
|
||||||
|
|
||||||
//Возвращаем данные таблицы
|
|
||||||
return { dataGrid, isDataLoaded, isLoading, handleFilterChanged, handleOrderChanged, handlePagesCountChanged, handlePageChange, doReload };
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useP8PDataGrid };
|
|
||||||
@ -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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
@ -27,14 +27,13 @@ import {
|
|||||||
Link
|
Link
|
||||||
} from "@mui/material"; //Интерфейсные компоненты
|
} from "@mui/material"; //Интерфейсные компоненты
|
||||||
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
|
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
|
||||||
import { useP8PGantt } from "./p8p_gantt_hooks"; //Хук для диаграммы Ганта
|
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
//Уровни масштаба
|
//Уровни масштаба
|
||||||
const P8P_GANTT_ZOOM = [0, 1, 2, 3, 4, 5];
|
const P8P_GANTT_ZOOM = [0, 1, 2, 3, 4];
|
||||||
|
|
||||||
//Уровни масштаба (строковые наименования в терминах библиотеки)
|
//Уровни масштаба (строковые наименования в терминах библиотеки)
|
||||||
const P8P_GANTT_ZOOM_VIEW_MODES = {
|
const P8P_GANTT_ZOOM_VIEW_MODES = {
|
||||||
@ -42,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"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Структура задачи
|
//Структура задачи
|
||||||
@ -140,7 +138,6 @@ const P8PGanttTaskEditor = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
taskAttributeRenderer,
|
taskAttributeRenderer,
|
||||||
taskDialogRenderer,
|
taskDialogRenderer,
|
||||||
taskDialogProps,
|
|
||||||
numbCaption,
|
numbCaption,
|
||||||
nameCaption,
|
nameCaption,
|
||||||
startCaption,
|
startCaption,
|
||||||
@ -188,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 })
|
||||||
) : (
|
) : (
|
||||||
@ -317,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,
|
||||||
@ -350,7 +346,6 @@ const P8PGantt = ({
|
|||||||
onTaskProgressChange,
|
onTaskProgressChange,
|
||||||
taskAttributeRenderer,
|
taskAttributeRenderer,
|
||||||
taskDialogRenderer,
|
taskDialogRenderer,
|
||||||
taskDialogProps,
|
|
||||||
noDataFoundText,
|
noDataFoundText,
|
||||||
numbTaskEditorCaption,
|
numbTaskEditorCaption,
|
||||||
nameTaskEditorCaption,
|
nameTaskEditorCaption,
|
||||||
@ -471,7 +466,6 @@ const P8PGantt = ({
|
|||||||
onCancel={handleTaskEditorCancel}
|
onCancel={handleTaskEditorCancel}
|
||||||
taskAttributeRenderer={taskAttributeRenderer}
|
taskAttributeRenderer={taskAttributeRenderer}
|
||||||
taskDialogRenderer={taskDialogRenderer}
|
taskDialogRenderer={taskDialogRenderer}
|
||||||
taskDialogProps={taskDialogProps}
|
|
||||||
numbCaption={numbTaskEditorCaption}
|
numbCaption={numbTaskEditorCaption}
|
||||||
nameCaption={nameTaskEditorCaption}
|
nameCaption={nameTaskEditorCaption}
|
||||||
startCaption={startTaskEditorCaption}
|
startCaption={startTaskEditorCaption}
|
||||||
@ -507,7 +501,6 @@ P8PGantt.propTypes = {
|
|||||||
onTaskProgressChange: PropTypes.func,
|
onTaskProgressChange: PropTypes.func,
|
||||||
taskAttributeRenderer: PropTypes.func,
|
taskAttributeRenderer: PropTypes.func,
|
||||||
taskDialogRenderer: PropTypes.func,
|
taskDialogRenderer: PropTypes.func,
|
||||||
taskDialogProps: PropTypes.object,
|
|
||||||
noDataFoundText: PropTypes.string.isRequired,
|
noDataFoundText: PropTypes.string.isRequired,
|
||||||
numbTaskEditorCaption: PropTypes.string.isRequired,
|
numbTaskEditorCaption: PropTypes.string.isRequired,
|
||||||
nameTaskEditorCaption: PropTypes.string.isRequired,
|
nameTaskEditorCaption: PropTypes.string.isRequired,
|
||||||
@ -523,4 +516,4 @@ P8PGantt.propTypes = {
|
|||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export { P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE, taskLegendDesc, P8PGantt, useP8PGantt };
|
export { P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE, taskLegendDesc, P8PGantt };
|
||||||
|
|||||||
@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга
|
|
||||||
Хуки для диаграмм Ганта
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React
|
|
||||||
import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { formatDateJSONDateOnly } from "../core/utils"; //Вспомогательные функции
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Константы - значения по умолчанию
|
|
||||||
const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию о диаграмме ганта
|
|
||||||
const GANTT_NODE_NAME_DEF = "XGANTT"; //Наименование узла, содержащего информацию о диаграмме ганта
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук для P8PGantt
|
|
||||||
const useP8PGantt = ({
|
|
||||||
stored,
|
|
||||||
respArg = RESP_ARG_DEF,
|
|
||||||
contentNodeName = GANTT_NODE_NAME_DEF,
|
|
||||||
storedArgs = {},
|
|
||||||
executeStoredArgs = {},
|
|
||||||
allowDataLoad = () => true
|
|
||||||
}) => {
|
|
||||||
//Собственное состояние - диаграмма ганта
|
|
||||||
const [gantt, setGantt] = useState({
|
|
||||||
title: null,
|
|
||||||
zoom: null,
|
|
||||||
zoomBar: null,
|
|
||||||
readOnly: false,
|
|
||||||
readOnlyDates: false,
|
|
||||||
readOnlyProgress: false,
|
|
||||||
taskAttributes: [],
|
|
||||||
tasks: [],
|
|
||||||
taskColors: []
|
|
||||||
});
|
|
||||||
|
|
||||||
//Собственное состояние - признак загрузки данных
|
|
||||||
const [isDataLoaded, setIsDataLoaded] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - флаг загрузки
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - необходимость обновления данных
|
|
||||||
const [reload, setReload] = useState(true);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные агрументы
|
|
||||||
const refStoredArgs = useRef(storedArgs);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные параметры вызова процедуры
|
|
||||||
const refExecuteStoredArgs = useRef(executeStoredArgs);
|
|
||||||
|
|
||||||
//Признак допустимости обновления данных
|
|
||||||
const isAllowDataLoad = useMemo(() => {
|
|
||||||
return allowDataLoad();
|
|
||||||
}, [allowDataLoad]);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored, isRespErr } = useContext(BackEndCtx);
|
|
||||||
|
|
||||||
//Загрузка данных диаграммы ганта с сервера
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const data = await executeStored({
|
|
||||||
stored,
|
|
||||||
args: { ...refStoredArgs.current },
|
|
||||||
attributeValueProcessor: (name, val) =>
|
|
||||||
name == "numb" ? undefined : ["start", "end"].includes(name) ? formatDateJSONDateOnly(val) : val,
|
|
||||||
respArg,
|
|
||||||
...refExecuteStoredArgs.current
|
|
||||||
});
|
|
||||||
setGantt(pv => ({ ...pv, ...data[contentNodeName] }));
|
|
||||||
//Устанавливаем признак загрузки данных с учетом возможных ошибок
|
|
||||||
setIsDataLoaded(!isRespErr(data));
|
|
||||||
} catch (e) {
|
|
||||||
//Если произошла ошибка - данные не загружены
|
|
||||||
setIsDataLoaded(false);
|
|
||||||
} finally {
|
|
||||||
//Сбрасываем признаки загрузки и перезагрузки данных
|
|
||||||
setLoading(false);
|
|
||||||
setReload(false);
|
|
||||||
}
|
|
||||||
}, [contentNodeName, executeStored, isRespErr, respArg, stored]);
|
|
||||||
|
|
||||||
//При необходимости обновления диаграммы ганта
|
|
||||||
const doReload = useCallback(() => {
|
|
||||||
setReload(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//Проверка изменений параметров
|
|
||||||
const isArgsChanged = useCallback(
|
|
||||||
(currentArgs, args) => {
|
|
||||||
//Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера)
|
|
||||||
return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args);
|
|
||||||
},
|
|
||||||
[isLoading]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refStoredArgs.current, storedArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refStoredArgs.current = storedArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [storedArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров вызова процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refExecuteStoredArgs.current = executeStoredArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [executeStoredArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При необходимости обновить данные диаграммы ганта
|
|
||||||
useEffect(() => {
|
|
||||||
//Если необходимо перезагрузить данные и это допустимо
|
|
||||||
if (isAllowDataLoad && reload) {
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
}, [isAllowDataLoad, reload, loadData]);
|
|
||||||
|
|
||||||
//Возвращаем данные диаграммы ганта
|
|
||||||
return { gantt, isDataLoaded, isLoading, doReload };
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useP8PGantt };
|
|
||||||
@ -1,230 +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"; //Типовые стили
|
|
||||||
import { useP8PIndicator } from "./p8p_indicator_hooks"; //Хук для индикатора
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Варианты исполнения
|
|
||||||
|
|
||||||
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": { filter: "brightness(0.92) !important" },
|
|
||||||
"&:active": { backgroundColor: APP_COLORS.ACTIVE.color }
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
}),
|
|
||||||
ICON: (state, userColor) => ({ fontSize: "50px", ...getColor(state, userColor) }),
|
|
||||||
HINT_ICON: (state, userColor) => ({ fontSize: "1rem", ...getColor(state, userColor) }),
|
|
||||||
VALUE_CAPTION_STACK: { containerType: "inline-size", width: "100%", overflow: "hidden" },
|
|
||||||
CAPTION_TYPOGRAPHY: clickable => ({
|
|
||||||
width: "99cqw",
|
|
||||||
...(clickable
|
|
||||||
? {
|
|
||||||
cursor: "pointer"
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
}),
|
|
||||||
VALUE_TYPOGRAPHY: clickable => ({
|
|
||||||
...(clickable
|
|
||||||
? {
|
|
||||||
cursor: "pointer"
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------------------
|
|
||||||
//Вспомогательные функции
|
|
||||||
//-----------------------
|
|
||||||
|
|
||||||
//Подбор цвета заливки
|
|
||||||
const getBackgroundColor = (state, userColor) =>
|
|
||||||
userColor ? { backgroundColor: userColor } : BG_COLOR[state] ? { backgroundColor: BG_COLOR[state] } : {};
|
|
||||||
|
|
||||||
//Подбор цвета текста
|
|
||||||
const getColor = (state, userColor) => (userColor ? { color: userColor } : COLOR[state] ? { color: COLOR[state] } : {});
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Индикатор
|
|
||||||
const P8PIndicator = ({
|
|
||||||
caption,
|
|
||||||
value,
|
|
||||||
icon = null,
|
|
||||||
state = STATE.UNDEFINED,
|
|
||||||
square = false,
|
|
||||||
elevation = 3,
|
|
||||||
variant = P8P_INDICATOR_VARIANT.ELEVATION,
|
|
||||||
hint = null,
|
|
||||||
onClick = null,
|
|
||||||
onValueClick = null,
|
|
||||||
onCaptionClick = 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 = e => {
|
|
||||||
setShowHint(false);
|
|
||||||
e.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
//При нажатии на заголовок
|
|
||||||
const handleCaptionClick = event => {
|
|
||||||
//Если есть функция нажатия на заголовок
|
|
||||||
onCaptionClick && onCaptionClick(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При нажатии на значение
|
|
||||||
const handleValueClick = event => {
|
|
||||||
//Если есть функция нажатия на заголовок
|
|
||||||
onValueClick && onValueClick(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Представление текста значения индикатора
|
|
||||||
const valueTextView = (
|
|
||||||
<Typography variant={"h4"} sx={STYLES.VALUE_TYPOGRAPHY(onValueClick ? true : false)} onClick={handleValueClick}>
|
|
||||||
{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
|
|
||||||
//Представление текста подписи индикатора
|
|
||||||
const captionView = (
|
|
||||||
<Typography
|
|
||||||
align={"left"}
|
|
||||||
noWrap={true}
|
|
||||||
sx={STYLES.CAPTION_TYPOGRAPHY(onCaptionClick ? true : false)}
|
|
||||||
title={caption}
|
|
||||||
onClick={handleCaptionClick}
|
|
||||||
>
|
|
||||||
{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,
|
|
||||||
onValueClick: PropTypes.func,
|
|
||||||
onCaptionClick: PropTypes.func,
|
|
||||||
backgroundColor: PropTypes.string,
|
|
||||||
color: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator, useP8PIndicator };
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга
|
|
||||||
Хуки для индикаторов
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useContext, useRef, useMemo } from "react"; //Классы React
|
|
||||||
import { BackEndCtx } from "../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { P8P_INDICATOR_STATE, P8P_INDICATOR_VARIANT } from "./p8p_indicator";
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Константы - значения по умолчанию
|
|
||||||
const RESP_ARG_DEF = "COUT"; //Имя параметра, содержащего информацию об индикаторе
|
|
||||||
const INDICATOR_NODE_NAME_DEF = "XINDICATOR"; //Наименование узла, содержащего информацию об индикаторе
|
|
||||||
const ELEVATION_DEF = 3; //Высота парения
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Хук для P8PIndicator
|
|
||||||
const useP8PIndicator = ({
|
|
||||||
stored,
|
|
||||||
respArg = RESP_ARG_DEF,
|
|
||||||
contentNodeName = INDICATOR_NODE_NAME_DEF,
|
|
||||||
storedArgs = {},
|
|
||||||
executeStoredArgs = {},
|
|
||||||
allowDataLoad = () => true
|
|
||||||
}) => {
|
|
||||||
//Собственное состояние - индикатор
|
|
||||||
const [indicator, setIndicator] = useState({
|
|
||||||
caption: null,
|
|
||||||
value: null,
|
|
||||||
icon: null,
|
|
||||||
state: P8P_INDICATOR_STATE.UNDEFINED,
|
|
||||||
square: false,
|
|
||||||
elevation: ELEVATION_DEF,
|
|
||||||
variant: P8P_INDICATOR_VARIANT.ELEVATION,
|
|
||||||
hint: null,
|
|
||||||
backgroundColor: null,
|
|
||||||
color: null
|
|
||||||
});
|
|
||||||
|
|
||||||
//Собственное состояние - признак загрузки данных
|
|
||||||
const [isDataLoaded, setIsDataLoaded] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - флаг загрузки
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
//Собственное состояние - необходимость обновления данных
|
|
||||||
const [reload, setReload] = useState(true);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные агрументы
|
|
||||||
const refStoredArgs = useRef(storedArgs);
|
|
||||||
|
|
||||||
//Собственное состояние - дополнительные параметры вызова процедуры
|
|
||||||
const refExecuteStoredArgs = useRef(executeStoredArgs);
|
|
||||||
|
|
||||||
//Признак допустимости обновления данных
|
|
||||||
const isAllowDataLoad = useMemo(() => {
|
|
||||||
return allowDataLoad();
|
|
||||||
}, [allowDataLoad]);
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored, isRespErr } = useContext(BackEndCtx);
|
|
||||||
|
|
||||||
//Загрузка данных индикатора с сервера
|
|
||||||
const loadData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const data = await executeStored({ stored, respArg, args: { ...refStoredArgs.current }, ...refExecuteStoredArgs.current });
|
|
||||||
setIndicator(pv => ({ ...pv, ...data[contentNodeName] }));
|
|
||||||
//Устанавливаем признак загрузки данных с учетом возможных ошибок
|
|
||||||
setIsDataLoaded(!isRespErr(data));
|
|
||||||
} catch (e) {
|
|
||||||
//Если произошла ошибка - данные не загружены
|
|
||||||
setIsDataLoaded(false);
|
|
||||||
} finally {
|
|
||||||
//Сбрасываем признаки загрузки и перезагрузки данных
|
|
||||||
setLoading(false);
|
|
||||||
setReload(false);
|
|
||||||
}
|
|
||||||
}, [contentNodeName, executeStored, isRespErr, respArg, stored]);
|
|
||||||
|
|
||||||
//При необходимости обновления индикатора
|
|
||||||
const doReload = useCallback(() => {
|
|
||||||
setReload(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//Проверка изменений параметров
|
|
||||||
const isArgsChanged = useCallback(
|
|
||||||
(currentArgs, args) => {
|
|
||||||
//Если дополнительные параметры изменились (и сейчас не происходит загрузка данных с сервера)
|
|
||||||
return !isLoading && JSON.stringify(currentArgs) != JSON.stringify(args);
|
|
||||||
},
|
|
||||||
[isLoading]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refStoredArgs.current, storedArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refStoredArgs.current = storedArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [storedArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При изменение дополнительных параметров вызова процедуры
|
|
||||||
useEffect(() => {
|
|
||||||
//Если параметры изменились
|
|
||||||
if (isArgsChanged(refExecuteStoredArgs.current, executeStoredArgs)) {
|
|
||||||
//Устанавливаем новые дополнительные параметры
|
|
||||||
refExecuteStoredArgs.current = executeStoredArgs;
|
|
||||||
//При изменении дополнительных параметров необходимо перезагрузить данные
|
|
||||||
setReload(true);
|
|
||||||
}
|
|
||||||
}, [executeStoredArgs, isArgsChanged]);
|
|
||||||
|
|
||||||
//При необходимости обновить данные индикатора
|
|
||||||
useEffect(() => {
|
|
||||||
//Если необходимо перезагрузить данные и это допустимо
|
|
||||||
if (isAllowDataLoad && reload) {
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
}, [isAllowDataLoad, reload, loadData]);
|
|
||||||
|
|
||||||
//Возвращаем данные индикатора
|
|
||||||
return { indicator, isDataLoaded, isLoading, doReload };
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { useP8PIndicator };
|
|
||||||
@ -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}
|
||||||
>
|
>
|
||||||
<Icon sx={STYLES.DESKTOP_ITEM_ICON}>{panel.icon}</Icon>
|
<Stack sx={STYLES.DESKTOP_ITEM_STACK}>
|
||||||
<Typography sx={STYLES.DESKTOP_ITEM_CATION} variant="body1">
|
<Icon sx={STYLES.DESKTOP_ITEM_ICON}>{panel.icon}</Icon>
|
||||||
{panel.caption}
|
<Typography sx={STYLES.DESKTOP_ITEM_CATION} variant="body1">
|
||||||
</Typography>
|
{panel.caption}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -242,12 +230,7 @@ const P8PPanelsMenuDesktop = ({ group, onItemNavigate, panels = [], defaultGroup
|
|||||||
const panelsLinks = getPanelsLinks({ variant: P8P_PANELS_MENU_VARIANT.DESKTOP, panels, group, defaultGroupTytle, onItemNavigate });
|
const panelsLinks = getPanelsLinks({ variant: P8P_PANELS_MENU_VARIANT.DESKTOP, panels, group, defaultGroupTytle, onItemNavigate });
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return <Box p={2}>{panelsLinks}</Box>;
|
||||||
<Box p={2}>
|
|
||||||
{panelsLinks[0]}
|
|
||||||
<Stack direction="row">{panelsLinks.map((l, i) => (i > 0 ? l : null))}</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Контроль свойств - Меню панелей - рабочий стол
|
//Контроль свойств - Меню панелей - рабочий стол
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import {
|
|||||||
TableContainer,
|
TableContainer,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
Pagination,
|
|
||||||
Paper,
|
Paper,
|
||||||
IconButton,
|
IconButton,
|
||||||
Icon,
|
Icon,
|
||||||
@ -35,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"; //Редьюсер состояния
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
@ -82,20 +81,6 @@ const P8P_TABLE_FILTER_SHAPE = PropTypes.shape({
|
|||||||
to: PropTypes.any
|
to: PropTypes.any
|
||||||
});
|
});
|
||||||
|
|
||||||
//Размещение области страниц по вертикали
|
|
||||||
const P8P_TABLE_PAGINATOR_ALIGN = {
|
|
||||||
LEFT: "left",
|
|
||||||
RIGHT: "right",
|
|
||||||
CENTER: "center"
|
|
||||||
};
|
|
||||||
|
|
||||||
//Размещение области страниц по горизонтали
|
|
||||||
const P8P_TABLE_PAGINATOR_POSITION = {
|
|
||||||
TOP: "top",
|
|
||||||
BOTTOM: "bottom",
|
|
||||||
BOTH: "both"
|
|
||||||
};
|
|
||||||
|
|
||||||
//Высота кнопки догрузки данных
|
//Высота кнопки догрузки данных
|
||||||
const P8P_TABLE_MORE_HEIGHT = "49px";
|
const P8P_TABLE_MORE_HEIGHT = "49px";
|
||||||
|
|
||||||
@ -104,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,
|
||||||
@ -131,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"
|
||||||
@ -151,16 +136,6 @@ const STYLES = {
|
|||||||
FILTER_CHIP: {
|
FILTER_CHIP: {
|
||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
},
|
},
|
||||||
PAGINATION: (pagesAlign, position) => ({
|
|
||||||
display: "flex",
|
|
||||||
justifyContent:
|
|
||||||
pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.LEFT
|
|
||||||
? "flex-start"
|
|
||||||
: pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.CENTER
|
|
||||||
? "space-around"
|
|
||||||
: "flex-end",
|
|
||||||
...(position === P8P_TABLE_PAGINATOR_POSITION.TOP ? { paddingBottom: "10px" } : { paddingTop: "10px" })
|
|
||||||
}),
|
|
||||||
MORE_BUTTON_CONTAINER: {
|
MORE_BUTTON_CONTAINER: {
|
||||||
with: "100%",
|
with: "100%",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
@ -313,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,
|
||||||
@ -489,22 +486,16 @@ P8PTableFiltersChips.propTypes = {
|
|||||||
|
|
||||||
//Таблица
|
//Таблица
|
||||||
const P8PTable = ({
|
const P8PTable = ({
|
||||||
style = {},
|
columnsDef,
|
||||||
tableStyle = {},
|
groups,
|
||||||
columnsDef = [],
|
rows,
|
||||||
groups = [],
|
|
||||||
rows = [],
|
|
||||||
orders,
|
orders,
|
||||||
filters,
|
filters,
|
||||||
size,
|
size,
|
||||||
pageNumber = 1,
|
|
||||||
pagesCount = 0,
|
|
||||||
pagesAlign = P8P_TABLE_PAGINATOR_ALIGN.RIGHT,
|
|
||||||
pagesPosition = P8P_TABLE_PAGINATOR_POSITION.BOTTOM,
|
|
||||||
fixedHeader = false,
|
fixedHeader = false,
|
||||||
fixedColumns = 0,
|
fixedColumns = 0,
|
||||||
morePages = false,
|
morePages = false,
|
||||||
reloading = false,
|
reloading,
|
||||||
expandable,
|
expandable,
|
||||||
orderAscMenuItemCaption,
|
orderAscMenuItemCaption,
|
||||||
orderDescMenuItemCaption,
|
orderDescMenuItemCaption,
|
||||||
@ -526,7 +517,6 @@ const P8PTable = ({
|
|||||||
onOrderChanged,
|
onOrderChanged,
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
onPagesCountChanged,
|
onPagesCountChanged,
|
||||||
onPageChanged,
|
|
||||||
objectsCopier,
|
objectsCopier,
|
||||||
containerComponent,
|
containerComponent,
|
||||||
containerComponentProps
|
containerComponentProps
|
||||||
@ -541,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);
|
||||||
@ -664,9 +652,6 @@ const P8PTable = ({
|
|||||||
else setExpanded(pv => ({ ...pv, [rowIndex]: true }));
|
else setExpanded(pv => ({ ...pv, [rowIndex]: true }));
|
||||||
};
|
};
|
||||||
|
|
||||||
//Отработка изменения страницы
|
|
||||||
const handlePageChange = (e, page) => onPageChanged && onPageChanged({ page });
|
|
||||||
|
|
||||||
//При перезагрузке данных
|
//При перезагрузке данных
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reloading) setExpanded({});
|
if (reloading) setExpanded({});
|
||||||
@ -711,35 +696,12 @@ const P8PTable = ({
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
//Генерация области страниц
|
|
||||||
const renderPagination = position => {
|
|
||||||
//Признак отображения в конкретной области
|
|
||||||
const isVisible = [
|
|
||||||
position === P8P_TABLE_PAGINATOR_POSITION.TOP ? P8P_TABLE_PAGINATOR_POSITION.TOP : P8P_TABLE_PAGINATOR_POSITION.BOTTOM,
|
|
||||||
P8P_TABLE_PAGINATOR_POSITION.BOTH
|
|
||||||
].includes(pagesPosition);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{pagesCount && pagesCount > 0 && isVisible ? (
|
|
||||||
<Pagination
|
|
||||||
sx={STYLES.PAGINATION(pagesAlign, position)}
|
|
||||||
count={pagesCount}
|
|
||||||
defaultPage={1}
|
|
||||||
page={pageNumber}
|
|
||||||
size="medium"
|
|
||||||
onChange={handlePageChange}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
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}
|
||||||
@ -768,9 +730,8 @@ const P8PTable = ({
|
|||||||
valueFormatter={valueFormatter}
|
valueFormatter={valueFormatter}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{renderPagination(P8P_TABLE_PAGINATOR_POSITION.TOP)}
|
|
||||||
<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}>
|
||||||
@ -922,8 +883,7 @@ const P8PTable = ({
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
{renderPagination(P8P_TABLE_PAGINATOR_POSITION.BOTTOM)}
|
{morePages ? (
|
||||||
{morePages && (!pagesCount || pagesCount <= 0) ? (
|
|
||||||
<Container style={STYLES.MORE_BUTTON_CONTAINER}>
|
<Container style={STYLES.MORE_BUTTON_CONTAINER}>
|
||||||
<Button fullWidth onClick={handleMorePagesBtnClick} {...(morePagesBtnProps ? morePagesBtnProps : {})}>
|
<Button fullWidth onClick={handleMorePagesBtnClick} {...(morePagesBtnProps ? morePagesBtnProps : {})}>
|
||||||
{morePagesBtnCaption}
|
{morePagesBtnCaption}
|
||||||
@ -936,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,
|
||||||
@ -970,14 +928,10 @@ P8PTable.propTypes = {
|
|||||||
).isRequired,
|
).isRequired,
|
||||||
filters: PropTypes.arrayOf(P8P_TABLE_FILTER_SHAPE).isRequired,
|
filters: PropTypes.arrayOf(P8P_TABLE_FILTER_SHAPE).isRequired,
|
||||||
size: PropTypes.string,
|
size: PropTypes.string,
|
||||||
pageNumber: PropTypes.number,
|
|
||||||
pagesCount: PropTypes.number,
|
|
||||||
pagesAlign: PropTypes.string,
|
|
||||||
pagesPosition: 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,
|
||||||
@ -999,7 +953,6 @@ P8PTable.propTypes = {
|
|||||||
onOrderChanged: PropTypes.func,
|
onOrderChanged: PropTypes.func,
|
||||||
onFilterChanged: PropTypes.func,
|
onFilterChanged: PropTypes.func,
|
||||||
onPagesCountChanged: PropTypes.func,
|
onPagesCountChanged: PropTypes.func,
|
||||||
onPageChanged: PropTypes.func,
|
|
||||||
objectsCopier: PropTypes.func.isRequired,
|
objectsCopier: PropTypes.func.isRequired,
|
||||||
containerComponent: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]),
|
containerComponent: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]),
|
||||||
containerComponentProps: PropTypes.object
|
containerComponentProps: PropTypes.object
|
||||||
@ -1009,13 +962,4 @@ P8PTable.propTypes = {
|
|||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export {
|
export { P8P_TABLE_DATA_TYPE, P8P_TABLE_SIZE, P8P_TABLE_FILTER_SHAPE, P8P_TABLE_MORE_HEIGHT, P8P_TABLE_FILTERS_HEIGHT, P8PTable };
|
||||||
P8P_TABLE_DATA_TYPE,
|
|
||||||
P8P_TABLE_SIZE,
|
|
||||||
P8P_TABLE_FILTER_SHAPE,
|
|
||||||
P8P_TABLE_MORE_HEIGHT,
|
|
||||||
P8P_TABLE_FILTERS_HEIGHT,
|
|
||||||
P8P_TABLE_PAGINATOR_ALIGN,
|
|
||||||
P8P_TABLE_PAGINATOR_POSITION,
|
|
||||||
P8PTable
|
|
||||||
};
|
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
import React, { useReducer, createContext, useEffect, useContext, useCallback, useMemo } from "react"; //ReactJS
|
import React, { useReducer, createContext, useEffect, useContext, useCallback, useMemo } from "react"; //ReactJS
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { APP_AT, INITIAL_STATE, applicationReducer } from "./application_reducer"; //Редьюсер состояния
|
import { APP_AT, INITIAL_STATE, applicationReducer } from "./application_reducer"; //Редьюсер состояния
|
||||||
import { MessagingCtx } from "./messaging"; //Контекст отображения сообщений
|
import { MessagingСtx } from "./messaging"; //Контекст отображения сообщений
|
||||||
import { BackEndCtx } from "./backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "./backend"; //Контекст взаимодействия с сервером
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -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
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
@ -31,7 +30,7 @@ const APPLICATION_CONTEXT_ERRORS_SHAPE = PropTypes.shape({
|
|||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
//Контекст приложения
|
//Контекст приложения
|
||||||
export const ApplicationCtx = createContext();
|
export const ApplicationСtx = createContext();
|
||||||
|
|
||||||
//Провайдер контекста приложения
|
//Провайдер контекста приложения
|
||||||
export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, config, children }) => {
|
export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, config, children }) => {
|
||||||
@ -39,10 +38,10 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
|
|||||||
const [state, dispatch] = useReducer(applicationReducer, INITIAL_STATE(displaySizeGetter));
|
const [state, dispatch] = useReducer(applicationReducer, INITIAL_STATE(displaySizeGetter));
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { getConfig, getRespPayload } = useContext(BackEndCtx);
|
const { getConfig, getRespPayload } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Подключение к контексту отображения сообщений
|
//Подключение к контексту отображения сообщений
|
||||||
const { showMsgErr } = useContext(MessagingCtx);
|
const { showMsgErr } = useContext(MessagingСtx);
|
||||||
|
|
||||||
//Установка флага инициализированности приложения
|
//Установка флага инициализированности приложения
|
||||||
const setInitialized = () => dispatch({ type: APP_AT.SET_INITIALIZED });
|
const setInitialized = () => dispatch({ type: APP_AT.SET_INITIALIZED });
|
||||||
@ -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 Онлайн"
|
||||||
@ -170,9 +149,8 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
|
|||||||
|
|
||||||
//Вернём компонент провайдера
|
//Вернём компонент провайдера
|
||||||
return (
|
return (
|
||||||
<ApplicationCtx.Provider
|
<ApplicationСtx.Provider
|
||||||
value={{
|
value={{
|
||||||
setAppBarTitle,
|
|
||||||
findPanelByName,
|
findPanelByName,
|
||||||
pOnlineShowTab,
|
pOnlineShowTab,
|
||||||
pOnlineShowUnit,
|
pOnlineShowUnit,
|
||||||
@ -186,7 +164,7 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ApplicationCtx.Provider>
|
</ApplicationСtx.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
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 { MessagingCtx } from "./messaging"; //Контекст сообщений
|
import { MessagingСtx } from "./messaging"; //Контекст сообщений
|
||||||
import { formatErrorMessage } from "../core/utils"; //Вспомогательные функции
|
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -34,12 +33,12 @@ const BACKEND_CONTEXT_CLIENT_SHAPE = PropTypes.shape({
|
|||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
//Контекст взаимодействия с серверным API
|
//Контекст взаимодействия с серверным API
|
||||||
export const BackEndCtx = createContext();
|
export const BackEndСtx = createContext();
|
||||||
|
|
||||||
//Провайдер контекста взаимодействия с серверным API
|
//Провайдер контекста взаимодействия с серверным API
|
||||||
export const BackEndContext = ({ client, children }) => {
|
export const BackEndContext = ({ client, children }) => {
|
||||||
//Подключение к контексту сообщений
|
//Подключение к контексту сообщений
|
||||||
const { showLoader, hideLoader, showMsgErr } = useContext(MessagingCtx);
|
const { showLoader, hideLoader, showMsgErr } = useContext(MessagingСtx);
|
||||||
|
|
||||||
//Проверка ответа на наличие ошибки
|
//Проверка ответа на наличие ошибки
|
||||||
const isRespErr = useCallback(resp => client.isRespErr(resp), [client]);
|
const isRespErr = useCallback(resp => client.isRespErr(resp), [client]);
|
||||||
@ -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();
|
||||||
@ -116,7 +108,7 @@ export const BackEndContext = ({ client, children }) => {
|
|||||||
|
|
||||||
//Вернём компонент провайдера
|
//Вернём компонент провайдера
|
||||||
return (
|
return (
|
||||||
<BackEndCtx.Provider
|
<BackEndСtx.Provider
|
||||||
value={{
|
value={{
|
||||||
SERV_DATA_TYPE_STR: client.SERV_DATA_TYPE_STR,
|
SERV_DATA_TYPE_STR: client.SERV_DATA_TYPE_STR,
|
||||||
SERV_DATA_TYPE_NUMB: client.SERV_DATA_TYPE_NUMB,
|
SERV_DATA_TYPE_NUMB: client.SERV_DATA_TYPE_NUMB,
|
||||||
@ -130,7 +122,7 @@ export const BackEndContext = ({ client, children }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</BackEndCtx.Provider>
|
</BackEndСtx.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
@ -43,7 +41,7 @@ const MESSAGING_CONTEXT_BUTTONS_SHAPE = PropTypes.shape({
|
|||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
//Контекст сообщений
|
//Контекст сообщений
|
||||||
export const MessagingCtx = createContext();
|
export const MessagingСtx = createContext();
|
||||||
|
|
||||||
//Провайдер контекста сообщений
|
//Провайдер контекста сообщений
|
||||||
export const MessagingContext = ({ titles, texts, buttons, children }) => {
|
export const MessagingContext = ({ titles, texts, buttons, children }) => {
|
||||||
@ -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]);
|
||||||
@ -109,7 +103,7 @@ export const MessagingContext = ({ titles, texts, buttons, children }) => {
|
|||||||
|
|
||||||
//Вернём компонент провайдера
|
//Вернём компонент провайдера
|
||||||
return (
|
return (
|
||||||
<MessagingCtx.Provider
|
<MessagingСtx.Provider
|
||||||
value={{
|
value={{
|
||||||
MSG_TYPE,
|
MSG_TYPE,
|
||||||
showLoader,
|
showLoader,
|
||||||
@ -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,12 +134,10 @@ 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}
|
||||||
</MessagingCtx.Provider>
|
</MessagingСtx.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import React, { createContext, useContext } from "react"; //ReactJS
|
|||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { useLocation, useNavigate } from "react-router-dom"; //Роутер приложения
|
import { useLocation, useNavigate } from "react-router-dom"; //Роутер приложения
|
||||||
import queryString from "query-string"; //Работа со строкой запроса
|
import queryString from "query-string"; //Работа со строкой запроса
|
||||||
import { ApplicationCtx } from "./application"; //Контекст приложения
|
import { ApplicationСtx } from "./application"; //Контекст приложения
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -41,7 +41,7 @@ export const NavigationContext = ({ children }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { findPanelByName, setAppBarTitle } = useContext(ApplicationCtx);
|
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,52 +140,18 @@ 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 =>
|
||||||
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
||||||
);
|
);
|
||||||
|
|
||||||
//Формироваеие уникального идентификатора по текущему времени
|
|
||||||
const genUID = prefix => (prefix ? `${prefix}_${Date.now()}` : `${Date.now()}`);
|
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
@ -206,14 +160,10 @@ export {
|
|||||||
hasValue,
|
hasValue,
|
||||||
getDisplaySize,
|
getDisplaySize,
|
||||||
deepCopyObject,
|
deepCopyObject,
|
||||||
object2XML,
|
|
||||||
object2Base64XML,
|
object2Base64XML,
|
||||||
xml2JSON,
|
xml2JSON,
|
||||||
formatDateRF,
|
formatDateRF,
|
||||||
formatDateTimeRF,
|
|
||||||
formatDateJSONDateOnly,
|
formatDateJSONDateOnly,
|
||||||
formatNumberRFCurrency,
|
formatNumberRFCurrency,
|
||||||
formatErrorMessage,
|
genGUID
|
||||||
genGUID,
|
|
||||||
genUID
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 { BackEndCtx } 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(BackEndCtx);
|
|
||||||
|
|
||||||
//При изменении типа события фильтра
|
|
||||||
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 { ApplicationCtx } from "../../../context/application"; //Контекст приложения
|
|
||||||
import { BackEndCtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
|
||||||
import { MessagingCtx } 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(BackEndCtx);
|
|
||||||
|
|
||||||
//Подключение к контексту сообщений
|
|
||||||
const { showMsgWarn } = useContext(MessagingCtx);
|
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
|
||||||
const { pOnlineShowDocument } = useContext(ApplicationCtx);
|
|
||||||
|
|
||||||
//По нажатию на открытие меню действий
|
|
||||||
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 { ApplicationCtx } from "../../../context/application"; //Контекст приложения
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Состояние открытия разделов
|
|
||||||
const useDictionary = () => {
|
|
||||||
//Подключение к контексту приложения
|
|
||||||
const { pOnlineShowDictionary } = useContext(ApplicationCtx);
|
|
||||||
|
|
||||||
//Отображение раздела "Сотрудники"
|
|
||||||
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 { BackEndCtx } 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(BackEndCtx);
|
|
||||||
|
|
||||||
//Считывание учётных документов
|
|
||||||
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(BackEndCtx);
|
|
||||||
|
|
||||||
//При необходимости загрузки заливок
|
|
||||||
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(BackEndCtx);
|
|
||||||
|
|
||||||
//При необходимости сортировки статусов
|
|
||||||
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 { BackEndCtx } 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(BackEndCtx);
|
|
||||||
|
|
||||||
//При инициализации события
|
|
||||||
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(BackEndCtx);
|
|
||||||
|
|
||||||
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 { BackEndCtx } 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(BackEndCtx);
|
|
||||||
|
|
||||||
//Выполнение направления события
|
|
||||||
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(BackEndCtx);
|
|
||||||
|
|
||||||
//Инициализация параметров события
|
|
||||||
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 { BackEndCtx } 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(BackEndCtx);
|
|
||||||
|
|
||||||
//При изменении информации о задаче
|
|
||||||
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 };
|
|
||||||
@ -11,9 +11,9 @@ import React, { useState, useContext, useCallback, useEffect } from "react"; //
|
|||||||
import { Box } from "@mui/material"; //Интерфейсные компоненты
|
import { Box } from "@mui/material"; //Интерфейсные компоненты
|
||||||
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
||||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||||
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { ApplicationCtx } from "../../context/application"; //Контекст приложения
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
import { MessagingCtx } from "../../context/messaging"; //Контекст сообщений
|
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||||
import { headCellRender, dataCellRender, groupCellRender, DIGITS_REG_EXP, MONTH_NAME_REG_EXP, DAY_NAME_REG_EXP } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
import { headCellRender, dataCellRender, groupCellRender, DIGITS_REG_EXP, MONTH_NAME_REG_EXP, DAY_NAME_REG_EXP } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||||
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
|
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
|
||||||
import { Filter } from "./filter"; //Компонент фильтра
|
import { Filter } from "./filter"; //Компонент фильтра
|
||||||
@ -88,13 +88,13 @@ const EqsPrfrm = () => {
|
|||||||
const [refIsDeprecated, setRidFlag] = useState(true);
|
const [refIsDeprecated, setRidFlag] = useState(true);
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { pOnlineShowUnit } = useContext(ApplicationCtx);
|
const { pOnlineShowUnit } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Подключение к контексту сообщений
|
//Подключение к контексту сообщений
|
||||||
const { showMsgErr } = useContext(MessagingCtx);
|
const { showMsgErr } = useContext(MessagingСtx);
|
||||||
|
|
||||||
//Загрузка данных таблицы с сервера
|
//Загрузка данных таблицы с сервера
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
@ -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
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import React, { useState, useContext } from "react"; //Классы React
|
|||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, Grid } from "@mui/material"; //Интерфейсные компоненты
|
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, Grid } from "@mui/material"; //Интерфейсные компоненты
|
||||||
import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода
|
import { FilterInputField } from "./filter_input_field"; //Компонент поля ввода
|
||||||
import { ApplicationCtx } from "../../context/application"; //Контекст приложения
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -77,7 +77,7 @@ const FilterDialog = ({ initial, onCancel, onOk }) => {
|
|||||||
const [filter, setFilter] = useState({ ...initial });
|
const [filter, setFilter] = useState({ ...initial });
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { pOnlineShowDictionary } = useContext(ApplicationCtx);
|
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//При закрытии диалога без изменения фильтра
|
//При закрытии диалога без изменения фильтра
|
||||||
const handleCancel = () => (onCancel ? onCancel() : null);
|
const handleCancel = () => (onCancel ? onCancel() : null);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React
|
import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React
|
||||||
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
|
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
@ -50,7 +50,7 @@ const useMechRecAssemblyMon = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Инициализация каталогов планов
|
//Инициализация каталогов планов
|
||||||
const initPlanCtlgs = useCallback(async () => {
|
const initPlanCtlgs = useCallback(async () => {
|
||||||
@ -134,7 +134,7 @@ const useCostProductComposition = planSpec => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//При подключении компонента к странице
|
//При подключении компонента к странице
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -174,7 +174,7 @@ const useProductDetailsTable = (planSpec, product, orders, pageNumber, stored) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Загрузка данных при изменении зависимостей
|
//Загрузка данных при изменении зависимостей
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -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,32 +240,26 @@ const MechRecAssemblyMon = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
{state.init == true ? (
|
{state.init == true ? (
|
||||||
state.selectedPlanCtlg.NRN ? (
|
state.selectedPlanCtlg.NRN ? (
|
||||||
state.planSpecs.length !== 0 ? (
|
<>
|
||||||
<>
|
<Typography variant="h3" sx={STYLES.MAIN_TITLE} pb={2}>
|
||||||
<Typography variant="h3" sx={STYLES.MAIN_TITLE} pb={2}>
|
{title}
|
||||||
{title}
|
|
||||||
</Typography>
|
|
||||||
{state.planSpecsLoaded == true ? (
|
|
||||||
state.selectedPlanSpec.NRN ? (
|
|
||||||
<PlanSpecDetail
|
|
||||||
planSpec={state.selectedPlanSpec}
|
|
||||||
disableNavigatePrev={planDetailNavigation.disableNavigatePrev}
|
|
||||||
disableNavigateNext={planDetailNavigation.disableNavigateNext}
|
|
||||||
onNavigate={handlePlanDetailNavigateClick}
|
|
||||||
onBack={handlePlanDetailBackClick}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box sx={STYLES.PLANS_LIST_BOX}>
|
|
||||||
<PlanSpecsList planSpecs={state.planSpecs} onItemClick={handlePlanClick} />
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Typography variant="h4" sx={STYLES.MAIN_TITLE}>
|
|
||||||
В каталоге планов отсутствуют записи подходящих первичных документов
|
|
||||||
</Typography>
|
</Typography>
|
||||||
)
|
{state.planSpecsLoaded == true ? (
|
||||||
|
state.selectedPlanSpec.NRN ? (
|
||||||
|
<PlanSpecDetail
|
||||||
|
planSpec={state.selectedPlanSpec}
|
||||||
|
disableNavigatePrev={planDetailNavigation.disableNavigatePrev}
|
||||||
|
disableNavigateNext={planDetailNavigation.disableNavigateNext}
|
||||||
|
onNavigate={handlePlanDetailNavigateClick}
|
||||||
|
onBack={handlePlanDetailBackClick}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Box sx={STYLES.PLANS_LIST_BOX}>
|
||||||
|
<PlanSpecsList planSpecs={state.planSpecs} onItemClick={handlePlanClick} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Typography variant="h4" sx={STYLES.MAIN_TITLE}>
|
<Typography variant="h4" sx={STYLES.MAIN_TITLE}>
|
||||||
Укажите каталог планов для отображения спецификаций
|
Укажите каталог планов для отображения спецификаций
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React
|
import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React
|
||||||
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
|
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
|
||||||
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
|
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ const useCostJobs = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
//Подключение к контексту навигации
|
//Подключение к контексту навигации
|
||||||
const { getNavigationSearch } = useContext(NavigationCtx);
|
const { getNavigationSearch } = useContext(NavigationCtx);
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ const useCostJobsSpecs = task => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Выдача задания
|
//Выдача задания
|
||||||
const issueCostJobsSpecs = useCallback(
|
const issueCostJobsSpecs = useCallback(
|
||||||
@ -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();
|
||||||
@ -192,7 +191,7 @@ const useEquipConfiguration = (task, fromAction) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Включение станка в строку сменного задания
|
//Включение станка в строку сменного задания
|
||||||
const includeEquipConfiguration = useCallback(
|
const includeEquipConfiguration = useCallback(
|
||||||
@ -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,8 +10,7 @@
|
|||||||
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 { MessagingCtx } 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" }
|
||||||
};
|
};
|
||||||
@ -97,7 +96,7 @@ const MechRecCostJobs = () => {
|
|||||||
const filteredJobs = useFilteredFcjobs(state.jobList, filter);
|
const filteredJobs = useFilteredFcjobs(state.jobList, filter);
|
||||||
|
|
||||||
//Подключение к контексту сообщений
|
//Подключение к контексту сообщений
|
||||||
const { InlineMsgInfo } = useContext(MessagingCtx);
|
const { InlineMsgInfo } = useContext(MessagingСtx);
|
||||||
|
|
||||||
//Выбор плана
|
//Выбор плана
|
||||||
const selectJob = job => {
|
const selectJob = job => {
|
||||||
|
|||||||
@ -1,281 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
|
|
||||||
Кастомные хуки
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useContext } from "react"; //Классы React
|
|
||||||
import { BackEndCtx } 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(BackEndCtx);
|
|
||||||
//Подключение к контексту навигации
|
|
||||||
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(BackEndCtx);
|
|
||||||
|
|
||||||
//Выдача задания
|
|
||||||
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(BackEndCtx);
|
|
||||||
|
|
||||||
//Включение рабочего в строку сменного задания
|
|
||||||
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 };
|
|
||||||
@ -3,7 +3,7 @@
|
|||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useContext } from "react";
|
import { useState, useCallback, useEffect, useContext } from "react";
|
||||||
import { BackEndCtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { object2Base64XML, formatDateRF } from "../../../core/utils"; //Вспомогательные функции
|
import { object2Base64XML, formatDateRF } from "../../../core/utils"; //Вспомогательные функции
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
@ -33,7 +33,7 @@ const useCostRouteLists = (task, taskType) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Загрузка данных таблицы с сервера
|
//Загрузка данных таблицы с сервера
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
@ -104,7 +102,7 @@ const useIncomFromDeps = (task, taskType) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Загрузка данных таблицы с сервера
|
//Загрузка данных таблицы с сервера
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
@ -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
|
||||||
@ -157,7 +154,7 @@ const useGoodsParties = mainRowRN => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Загрузка данных таблицы с сервера
|
//Загрузка данных таблицы с сервера
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
@ -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
|
||||||
@ -208,7 +204,7 @@ const useCostDeliveryLists = mainRowRN => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Загрузка данных таблицы строк комплектации с сервера
|
//Загрузка данных таблицы строк комплектации с сервера
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
@ -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 { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { MessagingCtx } 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,146 +114,55 @@ 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"
|
label="Каталог"
|
||||||
label="Каталог"
|
value={filter.ctlgName}
|
||||||
value={filter.ctlgName}
|
variant="standard"
|
||||||
variant="standard"
|
fullWidth
|
||||||
fullWidth
|
onChange={event => {
|
||||||
onChange={event => {
|
setFilter(pv => ({ ...pv, ctlgName: event.target.value }));
|
||||||
setFilter(pv => ({ ...pv, ctlgName: event.target.value }));
|
}}
|
||||||
}}
|
></TextField>
|
||||||
></TextField>
|
<FormGroup sx={STYLES.PLANS_CHECKBOX_HAVEDOCS}>
|
||||||
<FormGroup sx={STYLES.PLANS_CHECKBOX_HAVEDOCS}>
|
<FormControlLabel
|
||||||
<FormControlLabel
|
control={<Checkbox checked={filter.haveDocs} onChange={event => setFilter(pv => ({ ...pv, haveDocs: event.target.checked }))} />}
|
||||||
control={
|
label="Только с планами"
|
||||||
<Checkbox checked={filter.haveDocs} onChange={event => setFilter(pv => ({ ...pv, haveDocs: event.target.checked }))} />
|
labelPlacement="end"
|
||||||
}
|
/>
|
||||||
label="Только с планами"
|
</FormGroup>
|
||||||
labelPlacement="end"
|
<List>
|
||||||
/>
|
{planCtlgs.map(p => (
|
||||||
</FormGroup>
|
<ListItemButton
|
||||||
<List>
|
sx={p.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : null}
|
||||||
{planCtlgs.map(ctlg => (
|
key={p.NRN}
|
||||||
<Box key={ctlg.NRN}>
|
selected={p.NRN === selectedPlanCtlg}
|
||||||
<ListItemButton
|
onClick={() => (onClick ? onClick(p) : null)}
|
||||||
sx={ctlg.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : null}
|
>
|
||||||
key={ctlg.NRN}
|
<ListItemText
|
||||||
selected={ctlg.NRN === selectedPlanCtlg}
|
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{p.SNAME}</Typography>}
|
||||||
onClick={() => (onCtlgClick ? onCtlgClick(ctlg) : null)}
|
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(p.NCOUNT_DOCS)}</Typography>}
|
||||||
disabled={ctlg.NCOUNT_DOCS == 0}
|
/>
|
||||||
>
|
</ListItemButton>
|
||||||
<ListItemText
|
))}
|
||||||
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{ctlg.SNAME}</Typography>}
|
</List>
|
||||||
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(ctlg.NCOUNT_DOCS, 0)}</Typography>}
|
</div>
|
||||||
/>
|
|
||||||
</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>
|
|
||||||
</Box>
|
|
||||||
<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
|
||||||
@ -384,17 +250,14 @@ const MechRecCostProdPlans = () => {
|
|||||||
const filteredPlanCtgls = useFilteredPlanCtlgs(state.planCtlgs, filter);
|
const filteredPlanCtgls = useFilteredPlanCtlgs(state.planCtlgs, filter);
|
||||||
|
|
||||||
//Подключение к контексту сообщений
|
//Подключение к контексту сообщений
|
||||||
const { InlineMsgInfo } = useContext(MessagingCtx);
|
const { InlineMsgInfo } = useContext(MessagingСtx);
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Подключение к контексту навигации
|
//Подключение к контексту навигации
|
||||||
const { getNavigationSearch } = useContext(NavigationCtx);
|
const { getNavigationSearch } = useContext(NavigationCtx);
|
||||||
|
|
||||||
//Подключение к контексту сообщений
|
|
||||||
const { showMsgInfo } = useContext(MessagingCtx);
|
|
||||||
|
|
||||||
//Инициализация каталогов планов
|
//Инициализация каталогов планов
|
||||||
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>
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useContext } from "react"; //Классы React
|
import { useState, useCallback, useEffect, useContext } from "react"; //Классы React
|
||||||
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
@ -40,7 +40,7 @@ const useMechRecDeptCostJobs = (subdiv, fullDate, workHours) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//При необходимости обновить данные таблицы
|
//При необходимости обновить данные таблицы
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -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
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
@ -91,7 +92,7 @@ const useInsDepartment = fullDate => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//При необходимости обновить данные таблицы
|
//При необходимости обновить данные таблицы
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -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) {
|
||||||
@ -142,7 +142,7 @@ const useFilter = (currentMonth, currentYear) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//Считываем количества рабочих дней
|
//Считываем количества рабочих дней
|
||||||
const getWorkDays = useCallback(
|
const getWorkDays = useCallback(
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import PropTypes from "prop-types"; //Контроль свойств компо
|
|||||||
import { Typography, Box, Paper, Dialog, DialogContent, DialogActions, Button, TextField, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
|
import { Typography, Box, Paper, Dialog, DialogContent, DialogActions, Button, TextField, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
||||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||||
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { CostRouteListsSpecsDataGrid } from "./fcroutlstsp"; //Состояние таблицы заказов маршрутных листов
|
import { CostRouteListsSpecsDataGrid } from "./fcroutlstsp"; //Состояние таблицы заказов маршрутных листов
|
||||||
import { useCostRouteLists } from "./hooks.js"; //Хук состояния таблицы маршрутных листов
|
import { useCostRouteLists } from "./hooks.js"; //Хук состояния таблицы маршрутных листов
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ const CostRouteListsDataGrid = ({ task }) => {
|
|||||||
const [costRouteLists, setCostRouteLists] = useCostRouteLists(task);
|
const [costRouteLists, setCostRouteLists] = useCostRouteLists(task);
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//При изменении состояния сортировки
|
//При изменении состояния сортировки
|
||||||
const handleOrderChanged = ({ orders }) => setCostRouteLists(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
|
const handleOrderChanged = ({ orders }) => setCostRouteLists(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
//Подключение библиотек
|
//Подключение библиотек
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React, { useState, useEffect, useContext, useCallback } from "react"; //Классы React
|
import React, { useState, useEffect, useContext } from "react"; //Классы React
|
||||||
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
@ -32,60 +32,17 @@ export const useFilteredPlans = (plans, filter) => {
|
|||||||
return filteredPlans;
|
return filteredPlans;
|
||||||
};
|
};
|
||||||
|
|
||||||
//Хук для планов
|
//Хук для основной таблицы
|
||||||
const useDeptCostProdPlans = month => {
|
const useDeptCostProdPlans = () => {
|
||||||
//Собственное состояние - таблица данных
|
|
||||||
const [state, setState] = useState({
|
|
||||||
init: false,
|
|
||||||
loaded: false,
|
|
||||||
showPlanList: false,
|
|
||||||
rows: [],
|
|
||||||
reload: true,
|
|
||||||
selected: {},
|
|
||||||
currentMonth: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
|
||||||
const { executeStored } = useContext(BackEndCtx);
|
|
||||||
|
|
||||||
//При подключении компонента к странице
|
|
||||||
useEffect(() => {
|
|
||||||
const initPlans = async () => {
|
|
||||||
const data = await executeStored({
|
|
||||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_DEPT_INIT",
|
|
||||||
args: { SMONTH: month },
|
|
||||||
respArg: "COUT",
|
|
||||||
isArray: name => name === "XFCPRODPLANS",
|
|
||||||
attributeValueProcessor: (name, val) => (name === "SPERIOD" ? undefined : val)
|
|
||||||
});
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
init: true,
|
|
||||||
rows: [...(data?.XFCPRODPLANS || [])],
|
|
||||||
loaded: true,
|
|
||||||
reload: false,
|
|
||||||
selected: {},
|
|
||||||
currentMonth: month
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
//Если месяц указан и он не соответствует текущим данным
|
|
||||||
if (month && month !== state.currentMonth) {
|
|
||||||
initPlans();
|
|
||||||
}
|
|
||||||
}, [executeStored, month, state.currentMonth]);
|
|
||||||
|
|
||||||
//Возвращаем данные
|
|
||||||
return [state, setState];
|
|
||||||
};
|
|
||||||
|
|
||||||
//Хук для информации о плане
|
|
||||||
const useDeptCostProdPlanInfo = plan => {
|
|
||||||
//Собственное состояние - таблица данных
|
//Собственное состояние - таблица данных
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
init: false,
|
init: false,
|
||||||
showPlanList: false,
|
showPlanList: false,
|
||||||
showIncomeFromDeps: null,
|
showIncomeFromDeps: null,
|
||||||
showFcroutelst: null,
|
showFcroutelst: null,
|
||||||
|
planList: [],
|
||||||
|
planListLoaded: false,
|
||||||
|
selectedPlan: {},
|
||||||
dataLoaded: false,
|
dataLoaded: false,
|
||||||
columnsDef: [],
|
columnsDef: [],
|
||||||
orders: null,
|
orders: null,
|
||||||
@ -98,45 +55,16 @@ const useDeptCostProdPlanInfo = plan => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//При необходимости очистки данных о плане
|
|
||||||
const handleClear = useCallback(
|
|
||||||
() =>
|
|
||||||
setState(pv => ({
|
|
||||||
...pv,
|
|
||||||
init: false,
|
|
||||||
showPlanList: false,
|
|
||||||
showIncomeFromDeps: null,
|
|
||||||
showFcroutelst: null,
|
|
||||||
dataLoaded: false,
|
|
||||||
columnsDef: [],
|
|
||||||
orders: null,
|
|
||||||
rows: [],
|
|
||||||
reload: true,
|
|
||||||
pageNumber: 1,
|
|
||||||
morePages: true,
|
|
||||||
fixedHeader: false,
|
|
||||||
fixedColumns: 0
|
|
||||||
})),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
//При изменении состояния сортировки
|
|
||||||
const handleOrderChanged = ({ orders }) => setState(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
|
|
||||||
|
|
||||||
//При изменении количества отображаемых страниц
|
|
||||||
const handlePagesCountChanged = () => setState(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
|
|
||||||
|
|
||||||
//При необходимости обновить данные таблицы
|
//При необходимости обновить данные таблицы
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//Если план выбран
|
if (state.selectedPlan.NRN) {
|
||||||
if (plan.NRN) {
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
const data = await executeStored({
|
const data = await executeStored({
|
||||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_DEPT_DG_GET",
|
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_DEPT_DG_GET",
|
||||||
args: {
|
args: {
|
||||||
NFCPRODPLAN: plan.NRN,
|
NFCPRODPLAN: state.selectedPlan.NRN,
|
||||||
CORDERS: { VALUE: object2Base64XML(state.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
CORDERS: { VALUE: object2Base64XML(state.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||||
NPAGE_NUMBER: state.pageNumber,
|
NPAGE_NUMBER: state.pageNumber,
|
||||||
NPAGE_SIZE: DATA_GRID_PAGE_SIZE_LARGE,
|
NPAGE_SIZE: DATA_GRID_PAGE_SIZE_LARGE,
|
||||||
@ -147,29 +75,40 @@ const useDeptCostProdPlanInfo = plan => {
|
|||||||
});
|
});
|
||||||
setState(pv => ({
|
setState(pv => ({
|
||||||
...pv,
|
...pv,
|
||||||
...data.XDATA_GRID,
|
fixedHeader: data.XDATA_GRID.fixedHeader,
|
||||||
init: true,
|
fixedColumns: data.XDATA_GRID.fixedColumns,
|
||||||
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_LARGE
|
morePages: (data.XROWS || []).length >= DATA_GRID_PAGE_SIZE_LARGE
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
//Если необходимо перезагрузить
|
|
||||||
if (state.reload) {
|
if (state.reload) {
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Если план не выбран и есть какие-то данные
|
}, [state.selectedPlan, state.reload, state.orders, state.pageNumber, state.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB]);
|
||||||
if (!plan.NRN && state.dataLoaded) {
|
|
||||||
//Очищаем их
|
|
||||||
handleClear();
|
|
||||||
}
|
|
||||||
}, [plan.NRN, state.reload, state.orders, state.pageNumber, state.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB, handleClear]);
|
|
||||||
|
|
||||||
//Возвращаем данные
|
//При подключении компонента к странице
|
||||||
return [state, setState, handleClear, handleOrderChanged, handlePagesCountChanged];
|
useEffect(() => {
|
||||||
|
const initPlans = async () => {
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_DEPT_INIT",
|
||||||
|
args: {},
|
||||||
|
respArg: "COUT",
|
||||||
|
isArray: name => name === "XFCPRODPLANS",
|
||||||
|
attributeValueProcessor: (name, val) => (name === "SPERIOD" ? undefined : val)
|
||||||
|
});
|
||||||
|
setState(pv => ({ ...pv, init: true, planList: [...(data?.XFCPRODPLANS || [])], planListLoaded: true }));
|
||||||
|
};
|
||||||
|
if (!state.init) {
|
||||||
|
initPlans();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [state, setState];
|
||||||
};
|
};
|
||||||
|
|
||||||
//Хук для таблицы маршрутных листов
|
//Хук для таблицы маршрутных листов
|
||||||
@ -187,7 +126,7 @@ const useCostRouteLists = task => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//При необходимости обновить данные таблицы
|
//При необходимости обновить данные таблицы
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -205,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) {
|
||||||
@ -226,7 +164,6 @@ const useCostRouteLists = task => {
|
|||||||
task
|
task
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//Возвращаем данные
|
|
||||||
return [costRouteLists, setCostRouteLists];
|
return [costRouteLists, setCostRouteLists];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -244,7 +181,7 @@ const useCostRouteListsSpecs = mainRowRN => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//При необходимости обновить данные таблицы
|
//При необходимости обновить данные таблицы
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -265,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) {
|
||||||
@ -286,7 +222,6 @@ const useCostRouteListsSpecs = mainRowRN => {
|
|||||||
mainRowRN
|
mainRowRN
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//Возвращаем данные
|
|
||||||
return [costRouteListsSpecs, setCostRouteListsSpecs];
|
return [costRouteListsSpecs, setCostRouteListsSpecs];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -304,7 +239,7 @@ const useIncomFromDeps = task => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
//При необходимости обновить данные таблицы
|
//При необходимости обновить данные таблицы
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -323,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) {
|
||||||
@ -336,8 +270,7 @@ const useIncomFromDeps = task => {
|
|||||||
}
|
}
|
||||||
}, [SERV_DATA_TYPE_CLOB, executeStored, incomFromDeps.dataLoaded, incomFromDeps.orders, incomFromDeps.pageNumber, incomFromDeps.reload, task]);
|
}, [SERV_DATA_TYPE_CLOB, executeStored, incomFromDeps.dataLoaded, incomFromDeps.orders, incomFromDeps.pageNumber, incomFromDeps.reload, task]);
|
||||||
|
|
||||||
//Возвращаем данные
|
|
||||||
return [incomFromDeps, setIncomFromDeps];
|
return [incomFromDeps, setIncomFromDeps];
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useDeptCostProdPlans, useDeptCostProdPlanInfo, useCostRouteLists, useCostRouteListsSpecs, useIncomFromDeps };
|
export { useDeptCostProdPlans, useCostRouteLists, useCostRouteListsSpecs, useIncomFromDeps };
|
||||||
|
|||||||
@ -12,10 +12,10 @@ import PropTypes from "prop-types"; //Контроль свойств компо
|
|||||||
import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField, Link, Grid } from "@mui/material"; //Интерфейсные элементы
|
import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField, Link, Grid } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
||||||
import { useDeptCostProdPlans, useFilteredPlans, useDeptCostProdPlanInfo } from "./hooks"; //Вспомогательные хуки
|
import { useDeptCostProdPlans, useFilteredPlans } from "./hooks"; //Вспомогательные хуки
|
||||||
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 { MessagingCtx } from "../../context/messaging"; //Контекст сообщений
|
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||||
import { IncomFromDepsDataGridDialog } from "./incomefromdeps"; //Диалог сдачи продукции
|
import { IncomFromDepsDataGridDialog } from "./incomefromdeps"; //Диалог сдачи продукции
|
||||||
import { CostRouteListsDataGridDialog } from "./fcroutlst"; //Диалог маршрутных листов
|
import { CostRouteListsDataGridDialog } from "./fcroutlst"; //Диалог маршрутных листов
|
||||||
|
|
||||||
@ -34,15 +34,14 @@ const TITLE_PADDING_BOTTOM = "20px";
|
|||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
PLANS_FILTER: { paddingTop: "20px", display: "flex", flexDirection: "column", alignItems: "center", gap: "5px" },
|
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
|
||||||
PLANS_FILTER_ITEM: { margin: "0px 10px", width: "93%" },
|
|
||||||
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
|
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
|
||||||
PLANS_BUTTON: { position: "absolute" },
|
PLANS_BUTTON: { position: "absolute" },
|
||||||
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" }
|
||||||
},
|
},
|
||||||
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" },
|
||||||
@ -61,18 +60,10 @@ const STYLES = {
|
|||||||
FACT_VALUE: { color: "blue" }
|
FACT_VALUE: { color: "blue" }
|
||||||
};
|
};
|
||||||
|
|
||||||
//Имена полей компонента
|
|
||||||
const SFIELD_MONTH = "month";
|
|
||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
//Вспомогательные функции и компоненты
|
//Вспомогательные функции и компоненты
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
//Считывание текущего года и месяца в формате "YYYY-MM"
|
|
||||||
const getCurrentYearMonth = () => {
|
|
||||||
return `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация представления ячейки заголовка группы
|
//Генерация представления ячейки заголовка группы
|
||||||
export const groupCellRender = ({ group }) => ({
|
export const groupCellRender = ({ group }) => ({
|
||||||
cellStyle: STYLES.DATA_GRID_GROUP_CELL,
|
cellStyle: STYLES.DATA_GRID_GROUP_CELL,
|
||||||
@ -97,7 +88,7 @@ const getRowBackgroudColor = row => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Генерация заливки строки исходя от значений
|
//Генерация заливки строки исходя от значений
|
||||||
const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick }) => {
|
const dataCellRender = ({ row, columnDef, handleProdOrderClick, handleMatresCodeClick }) => {
|
||||||
//Описываем общие свойства
|
//Описываем общие свойства
|
||||||
let cellProps = { title: row[columnDef.name] };
|
let cellProps = { title: row[columnDef.name] };
|
||||||
//Описываем общий стиль
|
//Описываем общий стиль
|
||||||
@ -136,7 +127,7 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
|
|||||||
cellProps,
|
cellProps,
|
||||||
cellStyle,
|
cellStyle,
|
||||||
data: (
|
data: (
|
||||||
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => onProdOrderClick(row["NRN"])}>
|
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => handleProdOrderClick(row["NRN"])}>
|
||||||
{row[columnDef.name]}
|
{row[columnDef.name]}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
@ -148,7 +139,7 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
|
|||||||
cellProps,
|
cellProps,
|
||||||
cellStyle: STYLES.DATA_GRID_CELL_MATRES_CODE(cellStyle, row),
|
cellStyle: STYLES.DATA_GRID_CELL_MATRES_CODE(cellStyle, row),
|
||||||
data: (
|
data: (
|
||||||
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => onMatresCodeClick(row["NRN"])}>
|
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => handleMatresCodeClick(row["NRN"])}>
|
||||||
{row[columnDef.name]}
|
{row[columnDef.name]}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
@ -158,38 +149,21 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Список каталогов планов
|
//Список каталогов планов
|
||||||
const PlanList = ({ plans = [], selectedPlan, filter, onFilterChange, onClick } = {}) => {
|
const PlanList = ({ plans = [], selectedPlan, filter, setFilter, onClick } = {}) => {
|
||||||
//При изменении фильтра
|
|
||||||
const handleFilterChange = e => {
|
|
||||||
onFilterChange && onFilterChange(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Box sx={STYLES.PLANS_FILTER}>
|
<TextField
|
||||||
<TextField
|
sx={STYLES.PLANS_FINDER}
|
||||||
sx={STYLES.PLANS_FILTER_ITEM}
|
name="planFilter"
|
||||||
name={SFIELD_MONTH}
|
label="План"
|
||||||
label="Месяц"
|
value={filter.planName}
|
||||||
type="month"
|
variant="standard"
|
||||||
value={filter.month}
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
onChange={event => {
|
||||||
variant="standard"
|
setFilter(pv => ({ ...pv, planName: event.target.value }));
|
||||||
fullWidth
|
}}
|
||||||
onChange={handleFilterChange}
|
></TextField>
|
||||||
required={true}
|
|
||||||
></TextField>
|
|
||||||
<TextField
|
|
||||||
sx={STYLES.PLANS_FILTER_ITEM}
|
|
||||||
name="planName"
|
|
||||||
label="План"
|
|
||||||
value={filter.planName}
|
|
||||||
variant="standard"
|
|
||||||
fullWidth
|
|
||||||
onChange={handleFilterChange}
|
|
||||||
></TextField>
|
|
||||||
</Box>
|
|
||||||
<List>
|
<List>
|
||||||
{plans.map(p => (
|
{plans.map(p => (
|
||||||
<ListItemButton key={p.NRN} selected={p.NRN === selectedPlan.NRN} onClick={() => (onClick ? onClick(p) : null)}>
|
<ListItemButton key={p.NRN} selected={p.NRN === selectedPlan.NRN} onClick={() => (onClick ? onClick(p) : null)}>
|
||||||
@ -207,7 +181,7 @@ PlanList.propTypes = {
|
|||||||
selectedPlan: PropTypes.object,
|
selectedPlan: PropTypes.object,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
filter: PropTypes.object,
|
filter: PropTypes.object,
|
||||||
onFilterChange: PropTypes.func
|
setFilter: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -216,135 +190,135 @@ PlanList.propTypes = {
|
|||||||
|
|
||||||
//Корневая панель производственного плана цеха
|
//Корневая панель производственного плана цеха
|
||||||
const MechRecDeptCostProdPlans = () => {
|
const MechRecDeptCostProdPlans = () => {
|
||||||
|
//Собственное состояние - таблица данных
|
||||||
|
const [state, setState] = useDeptCostProdPlans();
|
||||||
|
|
||||||
//Состояние для фильтра каталогов
|
//Состояние для фильтра каталогов
|
||||||
const [filter, setFilter] = useState({ planName: "", month: getCurrentYearMonth() });
|
const [filter, setFilter] = useState({ planName: "" });
|
||||||
|
|
||||||
//Собственное состояние - таблица планов
|
|
||||||
const [plans, setPlans] = useDeptCostProdPlans(filter.month);
|
|
||||||
|
|
||||||
//Собственное состояние - таблица информации
|
|
||||||
const [planInfo, setPlanInfo, onPlanInfoClear, onPlanInfoOrderChanged, onPlanInfoPagesCountChanged] = useDeptCostProdPlanInfo(plans.selected);
|
|
||||||
|
|
||||||
//Массив отфильтрованных каталогов
|
//Массив отфильтрованных каталогов
|
||||||
const filteredPlanCtgls = useFilteredPlans(plans.rows, filter);
|
const filteredPlanCtgls = useFilteredPlans(state.planList, filter);
|
||||||
|
|
||||||
//Подключение к контексту сообщений
|
//Подключение к контексту сообщений
|
||||||
const { InlineMsgInfo } = useContext(MessagingCtx);
|
const { InlineMsgInfo } = useContext(MessagingСtx);
|
||||||
|
|
||||||
//Выбор плана
|
//Выбор плана
|
||||||
const selectPlan = plan => {
|
const selectPlan = plan => {
|
||||||
setPlans(pv => ({
|
setState(pv => ({
|
||||||
...pv,
|
...pv,
|
||||||
selected: plan,
|
showIncomeFromDeps: null,
|
||||||
showPlanList: false
|
showFcroutelst: null,
|
||||||
|
selectedPlan: plan,
|
||||||
|
showPlanList: false,
|
||||||
|
dataLoaded: false,
|
||||||
|
columnsDef: [],
|
||||||
|
orders: null,
|
||||||
|
rows: [],
|
||||||
|
reload: true,
|
||||||
|
pageNumber: 1,
|
||||||
|
morePages: true
|
||||||
}));
|
}));
|
||||||
onPlanInfoClear();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//Сброс выбора плана
|
//Сброс выбора плана
|
||||||
const unselectPlan = () => {
|
const unselectPlan = () =>
|
||||||
setPlans(pv => ({
|
setState(pv => ({
|
||||||
...pv,
|
...pv,
|
||||||
selected: {},
|
showIncomeFromDeps: null,
|
||||||
showPlanList: false
|
showFcroutelst: null,
|
||||||
|
selectedPlan: {},
|
||||||
|
showPlanList: false,
|
||||||
|
dataLoaded: false,
|
||||||
|
columnsDef: [],
|
||||||
|
orders: null,
|
||||||
|
rows: [],
|
||||||
|
reload: true,
|
||||||
|
pageNumber: 1,
|
||||||
|
morePages: true
|
||||||
}));
|
}));
|
||||||
onPlanInfoClear();
|
|
||||||
};
|
|
||||||
|
|
||||||
//Обработка нажатия на элемент в списке планов
|
//Обработка нажатия на элемент в списке планов
|
||||||
const handlePlanClick = plan => {
|
const handlePlanClick = plan => {
|
||||||
if (plans.selected.NRN != plan.NRN) selectPlan(plan);
|
if (state.selectedPlan.NRN != plan.NRN) selectPlan(plan);
|
||||||
else unselectPlan();
|
else unselectPlan();
|
||||||
};
|
};
|
||||||
|
|
||||||
//При изменении состояния сортировки информации плана
|
//При изменении состояния сортировки
|
||||||
const handlePlanInfoOrderChanged = ({ orders }) => onPlanInfoOrderChanged({ orders });
|
const handleOrderChanged = ({ orders }) => setState(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
|
||||||
|
|
||||||
//При изменении количества отображаемых страниц информации плана
|
//При изменении количества отображаемых страниц
|
||||||
const handlePlanInfoPagesCountChanged = () => onPlanInfoPagesCountChanged();
|
const handlePagesCountChanged = () => setState(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
|
||||||
|
|
||||||
//При нажатии на "Заказ"
|
//При нажатии на "Заказ"
|
||||||
const handleProdOrderClick = planSp => {
|
const handleProdOrderClick = planSp => {
|
||||||
setPlanInfo(pv => ({ ...pv, showIncomeFromDeps: planSp }));
|
setState(pv => ({ ...pv, showIncomeFromDeps: planSp }));
|
||||||
};
|
};
|
||||||
|
|
||||||
//При нажатии на "Обозначение"
|
//При нажатии на "Обозначение"
|
||||||
const handleMatresCodeClick = planSp => {
|
const handleMatresCodeClick = planSp => {
|
||||||
setPlanInfo(pv => ({ ...pv, showFcroutelst: planSp }));
|
setState(pv => ({ ...pv, showFcroutelst: planSp }));
|
||||||
};
|
};
|
||||||
|
|
||||||
//При изменении фильтров
|
|
||||||
const handleFilterChange = e =>
|
|
||||||
setFilter(pv => ({ ...pv, [e.target.name]: e.target.name === SFIELD_MONTH && !e.target.value ? getCurrentYearMonth() : e.target.value }));
|
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<Box p={2}>
|
<Box p={2}>
|
||||||
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setPlans(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
|
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
|
||||||
Планы
|
Планы
|
||||||
</Fab>
|
</Fab>
|
||||||
<Drawer
|
<Drawer
|
||||||
anchor={"left"}
|
anchor={"left"}
|
||||||
open={plans.showPlanList}
|
open={state.showPlanList}
|
||||||
onClose={() => setPlans(pv => ({ ...pv, showPlanList: false }))}
|
onClose={() => setState(pv => ({ ...pv, showPlanList: false }))}
|
||||||
sx={STYLES.PLANS_DRAWER}
|
sx={STYLES.PLANS_DRAWER}
|
||||||
>
|
>
|
||||||
<PlanList
|
<PlanList
|
||||||
plans={filteredPlanCtgls}
|
plans={filteredPlanCtgls}
|
||||||
selectedPlan={plans.selected}
|
selectedPlan={state.selectedPlan}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onFilterChange={handleFilterChange}
|
setFilter={setFilter}
|
||||||
onClick={handlePlanClick}
|
onClick={handlePlanClick}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box display="flex" justifyContent="center" alignItems="center">
|
<Box display="flex" justifyContent="center" alignItems="center">
|
||||||
{planInfo.dataLoaded ? (
|
{state.dataLoaded ? (
|
||||||
planInfo.rows.length === 0 ? (
|
state.rows.length === 0 ? (
|
||||||
<InlineMsgInfo okBtn={false} text={"В плане отсутствуют записи спецификации"} />
|
<InlineMsgInfo okBtn={false} text={"В плане отсутствуют записи спецификации"} />
|
||||||
) : (
|
) : (
|
||||||
<Box sx={STYLES.CONTAINER}>
|
<Box sx={STYLES.CONTAINER}>
|
||||||
<Typography sx={STYLES.TITLE} variant={"h6"}>
|
<Typography sx={STYLES.TITLE} variant={"h6"}>
|
||||||
{`Производственный план цеха №${plans.selected.SSUBDIV} на ${plans.selected.SPERIOD}`}
|
{`Производственный план цеха №${state.selectedPlan.SSUBDIV} на ${state.selectedPlan.SPERIOD}`}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box>
|
<Box>
|
||||||
<P8PDataGrid
|
<P8PDataGrid
|
||||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||||
containerComponentProps={{ elevation: 6, sx: STYLES.DATA_GRID_CONTAINER }}
|
containerComponentProps={{ elevation: 6, sx: STYLES.DATA_GRID_CONTAINER }}
|
||||||
fixedHeader={planInfo.fixedHeader}
|
fixedHeader={state.fixedHeader}
|
||||||
fixedColumns={planInfo.fixedColumns}
|
fixedColumns={state.fixedColumns}
|
||||||
columnsDef={planInfo.columnsDef}
|
columnsDef={state.columnsDef}
|
||||||
rows={planInfo.rows}
|
rows={state.rows}
|
||||||
size={P8P_DATA_GRID_SIZE.MEDIUM}
|
size={P8P_DATA_GRID_SIZE.MEDIUM}
|
||||||
morePages={planInfo.morePages}
|
morePages={state.morePages}
|
||||||
reloading={planInfo.reload}
|
reloading={state.reload}
|
||||||
onOrderChanged={handlePlanInfoOrderChanged}
|
onOrderChanged={handleOrderChanged}
|
||||||
onPagesCountChanged={handlePlanInfoPagesCountChanged}
|
onPagesCountChanged={handlePagesCountChanged}
|
||||||
dataCellRender={prms =>
|
dataCellRender={prms => dataCellRender({ ...prms, handleProdOrderClick, handleMatresCodeClick })}
|
||||||
dataCellRender({
|
|
||||||
...prms,
|
|
||||||
onProdOrderClick: handleProdOrderClick,
|
|
||||||
onMatresCodeClick: handleMatresCodeClick
|
|
||||||
})
|
|
||||||
}
|
|
||||||
groupCellRender={groupCellRender}
|
groupCellRender={groupCellRender}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
) : !plans.selected.NRN ? (
|
) : !state.selectedPlan.NRN ? (
|
||||||
<InlineMsgInfo okBtn={false} text={"Укажите план для отображения спецификаций"} />
|
<InlineMsgInfo okBtn={false} text={"Укажите план для отображения спецификаций"} />
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
{planInfo.showIncomeFromDeps ? (
|
{state.showIncomeFromDeps ? (
|
||||||
<IncomFromDepsDataGridDialog task={planInfo.showIncomeFromDeps} onClose={() => handleProdOrderClick(null)} />
|
<IncomFromDepsDataGridDialog task={state.showIncomeFromDeps} onClose={() => handleProdOrderClick(null)} />
|
||||||
) : null}
|
|
||||||
{planInfo.showFcroutelst ? (
|
|
||||||
<CostRouteListsDataGridDialog task={planInfo.showFcroutelst} onClose={() => handleMatresCodeClick(null)} />
|
|
||||||
) : null}
|
) : null}
|
||||||
|
{state.showFcroutelst ? <CostRouteListsDataGridDialog task={state.showFcroutelst} onClose={() => handleMatresCodeClick(null)} /> : null}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import React, { useContext, useState } from "react"; //Классы React
|
|||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Box, Grid, Typography, Link, List, ListItem, ListItemButton, ListItemText, Divider, Fab, Icon } from "@mui/material"; //Интерфейсные элементы
|
import { Box, Grid, Typography, Link, List, ListItem, ListItemButton, ListItemText, Divider, Fab, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы
|
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы
|
||||||
import { ApplicationCtx } from "../../context/application"; //Контекст приложения
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
import img1_1 from "./img/1_1.png"; //Изображение
|
import img1_1 from "./img/1_1.png"; //Изображение
|
||||||
import img1_2 from "./img/1_2.png"; //Изображение
|
import img1_2 from "./img/1_2.png"; //Изображение
|
||||||
import img1_3 from "./img/1_3.png"; //Изображение
|
import img1_3 from "./img/1_3.png"; //Изображение
|
||||||
@ -252,7 +252,7 @@ Img.propTypes = {
|
|||||||
//Ссылка на раздел Системы
|
//Ссылка на раздел Системы
|
||||||
const UnitLink = ({ unitCode, children }) => {
|
const UnitLink = ({ unitCode, children }) => {
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { pOnlineShowUnit } = useContext(ApplicationCtx);
|
const { pOnlineShowUnit } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
@ -299,7 +299,7 @@ ChapterLink.propTypes = {
|
|||||||
//Ссылка на информационную панель
|
//Ссылка на информационную панель
|
||||||
const PanelLink = ({ panelName, children }) => {
|
const PanelLink = ({ panelName, children }) => {
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { configUrlBase, findPanelByName, pOnlineShowTab } = useContext(ApplicationCtx);
|
const { configUrlBase, findPanelByName, pOnlineShowTab } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -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,83 +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 STYLES = {
|
|
||||||
COMPONENT_BOX: isDragging => {
|
|
||||||
return isDragging ? { pointerEvents: "none" } : {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//-----------
|
|
||||||
//Тело модуля
|
|
||||||
//-----------
|
|
||||||
|
|
||||||
//Представление компонента панели
|
|
||||||
const ComponentView = ({ id, isDragging, path, settings = {}, values = {}, onValuesChange = null } = {}) => {
|
|
||||||
//Подгрузка модуля представления компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
|
|
||||||
const [ComponentView, init] = useComponentModule({ path, module: "view" });
|
|
||||||
|
|
||||||
//При смене значений
|
|
||||||
const handleValuesChange = values => onValuesChange && onValuesChange(values);
|
|
||||||
|
|
||||||
//Расчёт флага наличия компонента
|
|
||||||
const haveComponent = path ? true : false;
|
|
||||||
|
|
||||||
//Формирование представления
|
|
||||||
return (
|
|
||||||
<Box className={"component-view__wrap"} sx={STYLES.COMPONENT_BOX(isDragging)}>
|
|
||||||
{haveComponent && init && <ComponentView.default id={id} {...settings} values={values} onValuesChange={handleValuesChange} />}
|
|
||||||
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств компонента - компонент панели
|
|
||||||
ComponentView.propTypes = {
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
isDragging: PropTypes.bool.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,51 +0,0 @@
|
|||||||
/*
|
|
||||||
Парус 8 - Панели мониторинга - Редактор панелей
|
|
||||||
Компоненты: График (общие ресурсы действия)
|
|
||||||
*/
|
|
||||||
|
|
||||||
//---------------------
|
|
||||||
//Подключение библиотек
|
|
||||||
//---------------------
|
|
||||||
|
|
||||||
//---------
|
|
||||||
//Константы
|
|
||||||
//---------
|
|
||||||
|
|
||||||
//Собственные значения типа действия
|
|
||||||
const P8P_CA_CHART_TYPE_VALUE = {
|
|
||||||
GRPH_ELEMENT: "Элемент графика"
|
|
||||||
};
|
|
||||||
|
|
||||||
//Типы значений действий графика
|
|
||||||
const P8P_CA_CHART_VALUE_TYPES = [...Object.values(P8P_CA_CHART_TYPE_VALUE)];
|
|
||||||
|
|
||||||
//Доступные области действий графика
|
|
||||||
const P8P_CA_CHART_ACTION_AREAS = [
|
|
||||||
{ name: "Компонент", area: "component", hasElement: false },
|
|
||||||
{ name: "Элемент графика", area: "chart_item", hasElement: false }
|
|
||||||
];
|
|
||||||
|
|
||||||
//------------------------------------
|
|
||||||
//Вспомогательные функции и компоненты
|
|
||||||
//------------------------------------
|
|
||||||
|
|
||||||
//Получение значения собственного типа действия
|
|
||||||
const getChartCustomTypeValue = ({ type, value, prms }) => {
|
|
||||||
//Если это элемента графика - возвращаем нужное значение, иначе - null
|
|
||||||
return type === P8P_CA_CHART_TYPE_VALUE.GRPH_ELEMENT ? prms.item[value] : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Считывание обработчиков графика
|
|
||||||
const getChartHandlers = handlers => {
|
|
||||||
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
|
||||||
return {
|
|
||||||
onComponentClick: handlers["component."]?.fn,
|
|
||||||
onChartItemClick: handlers["chart_item."]?.fn
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
//Интерфейс модуля
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
export { P8P_CA_CHART_VALUE_TYPES, P8P_CA_CHART_ACTION_AREAS, getChartCustomTypeValue, getChartHandlers };
|
|
||||||
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