Compare commits

..

2 Commits
main ... main

236 changed files with 9253 additions and 44422 deletions

608
README.md

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -12,21 +12,13 @@ export const TITLES = {
INFO: "Информация", //Информационный блок
WARN: "Предупреждение", //Блок предупреждения
ERR: "Ошибка", //Информация об ошибке
DEFAULT_PANELS_GROUP: "Без привязки к группе", //Заголовок группы панелей по умолчанию
DATA_SOURCE_CONFIG: "Настройка источника данных", //Заголовок для настройки источника данных
INSERT: "Добавление", //Заголовок для диалогов/форм добавления
UPDATE: "Исправление", //Заголовок для диалогов/форм исправления
CONFIG: "Настройка" //Заголовок для диалога настройки
DEFAULT_PANELS_GROUP: "Без привязки к группе" //Заголовок группы панелей по умолчанию
};
//Текст
export const TEXTS = {
LOADING: "Ожидайте...", //Ожидание завершения процесса
NO_DATA_FOUND: "Данных не найдено", //Отсутствие данных
NO_DATA_FOUND_SHORT: "Н.Д.", //Отсутствие данных (кратко)
NO_SETTINGS: "Настройки не определены", //Отстутсвие настроек
UNKNOWN_SOURCE_TYPE: "Неизвестный тип источника", //Отсуствие типа источника
UNNAMED_SOURCE: "Источник без наименования" //Отсутствие наименования источника
NO_DATA_FOUND: "Данных не найдено" //Отсутствие данных
};
//Текст кнопок
@ -37,19 +29,11 @@ export const BUTTONS = {
OK: "ОК", //Ок
CANCEL: "Отмена", //Отмена
CLOSE: "Закрыть", //Сокрытие
DETAIL: "Подробнее", //Отображение подробностей/детализации
HIDE: "Скрыть", //Скрытие информации
CLEAR: "Очистить", //Очистка
ORDER_ASC: "По возрастанию", //Сортировка по возрастанию
ORDER_DESC: "По убыванию", //Сортировка по убыванию
FILTER: "Фильтр", //Фильтрация
MORE: "Ещё", //Догрузка данных
APPLY: "Применить", //Сохранение без закрытия интерфейса ввода
SAVE: "Сохранить", //Сохранение
CONFIG: "Настроить", //Настройка
INSERT: "Добавить", //Добавление
UPDATE: "Исправить", //Исправление
DELETE: "Удалить" //Удаление
MORE: "Ещё" //Догрузка данных
};
//Метки атрибутов, сопроводительные надписи
@ -62,9 +46,7 @@ export const CAPTIONS = {
START: "Начало",
END: "Окончание",
PROGRESS: "Прогресс",
LEGEND: "Легенда",
USER_PROC: "Пользовательская процедура",
QUERY: "Запрос"
LEGEND: "Легенда"
};
//Типовые сообщения об ошибках
@ -72,20 +54,10 @@ export const ERRORS = {
UNDER_CONSTRUCTION: "Панель в разработке",
P8O_API_UNAVAILABLE: '"ПАРУС 8 Онлайн" недоступен',
P8O_API_UNSUPPORTED: 'Функция "ПАРУС 8 Онлайн" не поддерживается',
DEFAULT: "Неожиданная ошибка",
DATA_SOURCE_NO_REQ_ARGS: "Не заданы обязательные параметры источника данных"
DEFAULT: "Неожиданная ошибка"
};
//Типовые сообщения для ошибок HTTP
export const ERRORS_HTTP = {
404: "Адрес не найден"
};
//Типовые статусы
export const STATE = {
UNDEFINED: "UNDEFINED",
INFO: "INFORMATION",
OK: "OK",
ERR: "ERR",
WARN: "WARN"
};

View File

@ -10,7 +10,7 @@
import React, { useState, useContext, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
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 { P8PAppErrorPage } from "./components/p8p_app_error_page"; //Страница с ошибкой
import { P8PAppWorkspace } from "./components/p8p_app_workspace"; //Рабочее пространство панели
@ -54,7 +54,7 @@ const MainMenu = ({ panels = [] } = {}) => {
const { navigatePanel, isNavigationSearch, getNavigationSearch } = useContext(NavigationCtx);
//Подключение к контексту приложения
const { configUrlBase, pOnlineShowTab } = useContext(ApplicationCtx);
const { configUrlBase, pOnlineShowTab } = useContext(ApplicationСtx);
//Получим параметры запроса из адресной строки
const qS = isNavigationSearch() ? getNavigationSearch() : null;
@ -86,9 +86,6 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
//Подключение к контексту навигации
const { navigateRoot, navigatePanel } = useContext(NavigationCtx);
//Подключение к контексту приложения
const { appState } = useContext(ApplicationCtx);
//Отработка действия навигации домой
const handleHomeNavigate = () => navigateRoot();
@ -101,7 +98,6 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
{...P8P_APP_WORKSPACE_CONFIG_PROPS}
panels={panels}
selectedPanel={selectedPanel}
caption={appState.appBarTitle}
onHomeNavigate={handleHomeNavigate}
onItemNavigate={handleItemNavigate}
>
@ -130,7 +126,7 @@ const App = () => {
const [routes, setRoutes] = useState([]);
//Подключение к контексту приложения
const { appState } = useContext(ApplicationCtx);
const { appState } = useContext(ApplicationСtx);
//Инициализация роутера
const content = routes.length > 0 ? <RouterProvider router={createHashRouter(routes)}></RouterProvider> : null;

View File

@ -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 };

View File

@ -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 };

View File

@ -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
};

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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
};

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

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

View File

@ -47,7 +47,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);
@ -86,7 +86,7 @@ const P8PAppWorkspace = ({ children, panels = [], selectedPanel, caption, closeC
<Icon>{open ? "chevron_left" : "menu"}</Icon>
</IconButton>
<Typography variant="h6" noWrap component="div">
{caption || selectedPanel?.caption}
{selectedPanel?.caption}
</Typography>
</Toolbar>
</AppBar>
@ -120,7 +120,6 @@ P8PAppWorkspace.propTypes = {
children: PropTypes.element,
panels: PropTypes.arrayOf(P8P_PANELS_MENU_PANEL_SHAPE).isRequired,
selectedPanel: P8P_PANELS_MENU_PANEL_SHAPE,
caption: PropTypes.string,
closeCaption: PropTypes.string.isRequired,
homeCaption: PropTypes.string.isRequired,
onHomeNavigate: PropTypes.func,

View File

@ -10,7 +10,6 @@
import React, { useCallback, useEffect, useRef } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import Chart from "chart.js/auto"; //Диаграммы и графики
import { useP8PChart } from "./p8p_chart_hooks"; //Хук для графика
//---------
//Константы
@ -122,4 +121,4 @@ P8PChart.propTypes = {
//Интерфейс модуля
//----------------
export { P8P_CHART_TYPE, P8PChart, useP8PChart };
export { P8P_CHART_TYPE, P8PChart };

View File

@ -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 };

View File

@ -26,7 +26,6 @@ import {
} from "@mui/material"; //Интерфейсные компоненты
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { hasValue } from "../core/utils"; //Вспомогательный функции
import { useP8PCyclogram } from "./p8p_cyclogram_hooks"; //Хук для циклограммы
//---------
//Константы
@ -817,4 +816,4 @@ P8PCyclogram.propTypes = {
//Интерфейс модуля
//----------------
export { P8PCyclogram, useP8PCyclogram };
export { P8PCyclogram };

View File

@ -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 };

View File

@ -9,17 +9,7 @@
import React, { useState, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
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"; //Хук для таблицы данных
import { P8PTable, P8P_TABLE_SIZE, P8P_TABLE_DATA_TYPE, P8P_TABLE_FILTER_SHAPE, P8P_TABLE_MORE_HEIGHT, P8P_TABLE_FILTERS_HEIGHT } from "./p8p_table"; //Таблица
//---------
//Константы
@ -40,29 +30,17 @@ const P8P_DATA_GRID_MORE_HEIGHT = P8P_TABLE_MORE_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 = ({
style = {},
tableStyle = {},
columnsDef = [],
filtersInitial,
groups = [],
rows = [],
size,
pageNumber = 1,
pagesCount = 0,
pagesAlign = P8P_DATA_GRID_PAGINATOR_ALIGN.RIGHT,
pagesPosition = P8P_DATA_GRID_PAGINATOR_POSITION.BOTTOM,
fixedHeader = false,
fixedColumns = 0,
morePages = false,
@ -90,7 +68,6 @@ const P8PDataGrid = ({
onOrderChanged,
onFilterChanged,
onPagesCountChanged,
onPageChanged,
objectsCopier
}) => {
//Собственное состояние - сортировки
@ -129,9 +106,6 @@ const P8PDataGrid = ({
if (onPagesCountChanged) onPagesCountChanged();
};
//При изменении номера страницы
const handlePageChange = ({ page }) => onPageChanged && onPageChanged({ page });
//При изменении списка установленных извне фильтров
useEffect(() => {
setFilters(filtersInitial || []);
@ -140,18 +114,12 @@ const P8PDataGrid = ({
//Генерация содержимого
return (
<P8PTable
style={style}
tableStyle={tableStyle}
columnsDef={columnsDef}
groups={groups}
rows={rows}
orders={orders}
filters={filters}
size={size || P8P_DATA_GRID_SIZE.MEDIUM}
pageNumber={pageNumber}
pagesCount={pagesCount}
pagesAlign={pagesAlign}
pagesPosition={pagesPosition}
fixedHeader={fixedHeader}
fixedColumns={fixedColumns}
morePages={morePages}
@ -180,24 +148,17 @@ const P8PDataGrid = ({
onOrderChanged={handleOrderChanged}
onFilterChanged={handleFilterChanged}
onPagesCountChanged={handlePagesCountChanged}
onPageChanged={handlePageChange}
/>
);
};
//Контроль свойств - Таблица данных
P8PDataGrid.propTypes = {
style: PropTypes.object,
tableStyle: PropTypes.object,
columnsDef: PropTypes.array,
filtersInitial: PropTypes.arrayOf(P8P_DATA_GRID_FILTER_SHAPE),
groups: PropTypes.array,
rows: PropTypes.array,
size: PropTypes.string,
pageNumber: PropTypes.number,
pagesCount: PropTypes.number,
pagesAlign: PropTypes.string,
pagesPosition: PropTypes.string,
fixedHeader: PropTypes.bool,
fixedColumns: PropTypes.number,
morePages: PropTypes.bool,
@ -225,7 +186,6 @@ P8PDataGrid.propTypes = {
onOrderChanged: PropTypes.func,
onFilterChanged: PropTypes.func,
onPagesCountChanged: PropTypes.func,
onPageChanged: PropTypes.func,
objectsCopier: PropTypes.func.isRequired
};
@ -239,8 +199,5 @@ export {
P8P_DATA_GRID_FILTER_SHAPE,
P8P_DATA_GRID_MORE_HEIGHT,
P8P_DATA_GRID_FILTERS_HEIGHT,
P8P_DATA_GRID_PAGINATOR_ALIGN,
P8P_DATA_GRID_PAGINATOR_POSITION,
P8PDataGrid,
useP8PDataGrid
P8PDataGrid
};

View File

@ -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 };

View File

@ -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 };

View File

@ -27,7 +27,6 @@ import {
Link
} from "@mui/material"; //Интерфейсные компоненты
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { useP8PGantt } from "./p8p_gantt_hooks"; //Хук для диаграммы Ганта
//---------
//Константы
@ -140,7 +139,6 @@ const P8PGanttTaskEditor = ({
onCancel,
taskAttributeRenderer,
taskDialogRenderer,
taskDialogProps,
numbCaption,
nameCaption,
startCaption,
@ -188,7 +186,7 @@ const P8PGanttTaskEditor = ({
//Генерация содержимого
return (
<Dialog open onClose={handleCancel} {...(taskDialogProps ? taskDialogProps : {})}>
<Dialog open onClose={handleCancel}>
{taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
) : (
@ -317,7 +315,6 @@ P8PGanttTaskEditor.propTypes = {
onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
numbCaption: PropTypes.string.isRequired,
nameCaption: PropTypes.string.isRequired,
startCaption: PropTypes.string.isRequired,
@ -350,7 +347,6 @@ const P8PGantt = ({
onTaskProgressChange,
taskAttributeRenderer,
taskDialogRenderer,
taskDialogProps,
noDataFoundText,
numbTaskEditorCaption,
nameTaskEditorCaption,
@ -471,7 +467,6 @@ const P8PGantt = ({
onCancel={handleTaskEditorCancel}
taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={taskDialogRenderer}
taskDialogProps={taskDialogProps}
numbCaption={numbTaskEditorCaption}
nameCaption={nameTaskEditorCaption}
startCaption={startTaskEditorCaption}
@ -507,7 +502,6 @@ P8PGantt.propTypes = {
onTaskProgressChange: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
noDataFoundText: PropTypes.string.isRequired,
numbTaskEditorCaption: PropTypes.string.isRequired,
nameTaskEditorCaption: PropTypes.string.isRequired,
@ -523,4 +517,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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -16,7 +16,6 @@ import {
TableContainer,
TableHead,
TableRow,
Pagination,
Paper,
IconButton,
Icon,
@ -35,7 +34,7 @@ import {
Link
} from "@mui/material"; //Интерфейсные компоненты
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"; //Редьюсер состояния
//---------
@ -82,20 +81,6 @@ const P8P_TABLE_FILTER_SHAPE = PropTypes.shape({
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";
@ -104,7 +89,9 @@ const P8P_TABLE_FILTERS_HEIGHT = "48px";
//Стили
const STYLES = {
TABLE: {},
TABLE: {
with: "100%"
},
TABLE_HEAD_STICKY: {
position: "sticky",
top: 0,
@ -151,16 +138,6 @@ const STYLES = {
FILTER_CHIP: {
alignItems: "center"
},
PAGINATION: (pagesAlign, position) => ({
display: "flex",
justifyContent:
pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.LEFT
? "flex-start"
: pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.CENTER
? "space-around"
: "flex-end",
...(position === P8P_TABLE_PAGINATOR_POSITION.TOP ? { paddingBottom: "10px" } : { paddingTop: "10px" })
}),
MORE_BUTTON_CONTAINER: {
with: "100%",
textAlign: "center",
@ -313,6 +290,28 @@ P8PTableColumnMenu.propTypes = {
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 = ({
columnDef,
@ -489,18 +488,12 @@ P8PTableFiltersChips.propTypes = {
//Таблица
const P8PTable = ({
style = {},
tableStyle = {},
columnsDef = [],
groups = [],
rows = [],
orders,
filters,
size,
pageNumber = 1,
pagesCount = 0,
pagesAlign = P8P_TABLE_PAGINATOR_ALIGN.RIGHT,
pagesPosition = P8P_TABLE_PAGINATOR_POSITION.BOTTOM,
fixedHeader = false,
fixedColumns = 0,
morePages = false,
@ -526,7 +519,6 @@ const P8PTable = ({
onOrderChanged,
onFilterChanged,
onPagesCountChanged,
onPageChanged,
objectsCopier,
containerComponent,
containerComponentProps
@ -664,9 +656,6 @@ const P8PTable = ({
else setExpanded(pv => ({ ...pv, [rowIndex]: true }));
};
//Отработка изменения страницы
const handlePageChange = (e, page) => onPageChanged && onPageChanged({ page });
//При перезагрузке данных
useEffect(() => {
if (reloading) setExpanded({});
@ -711,35 +700,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}
/>
<div>
{displayHintColumn ? (
<P8PTableColumnHintDialog columnDef={displayHintColumnDef} okBtnCaption={okFilterBtnCaption} onOk={handleHintOk} />
) : null}
</>
);
};
//Генерация содержимого
return (
<div style={{ ...(style || {}) }}>
{displayHintColumn ? <P8PHintDialog title={displayHintColumnDef.caption} hint={displayHintColumnDef.hint} onOk={handleHintOk} /> : null}
{filterColumn ? (
<P8PTableColumnFilterDialog
columnDef={filterColumnDef}
@ -768,9 +734,8 @@ const P8PTable = ({
valueFormatter={valueFormatter}
/>
) : null}
{renderPagination(P8P_TABLE_PAGINATOR_POSITION.TOP)}
<TableContainer component={containerComponent ? containerComponent : Paper} {...(containerComponentProps ? containerComponentProps : {})}>
<Table stickyHeader={fixedHeader} sx={{ ...STYLES.TABLE, ...(tableStyle || {}) }} size={size || P8P_TABLE_SIZE.MEDIUM}>
<Table stickyHeader={fixedHeader} sx={STYLES.TABLE} size={size || P8P_TABLE_SIZE.MEDIUM}>
<TableHead sx={fixedHeader ? STYLES.TABLE_HEAD_STICKY : {}}>
{header.displayLevels.map((level, i) => (
<TableRow key={level}>
@ -922,8 +887,7 @@ const P8PTable = ({
</TableBody>
</Table>
</TableContainer>
{renderPagination(P8P_TABLE_PAGINATOR_POSITION.BOTTOM)}
{morePages && (!pagesCount || pagesCount <= 0) ? (
{morePages ? (
<Container style={STYLES.MORE_BUTTON_CONTAINER}>
<Button fullWidth onClick={handleMorePagesBtnClick} {...(morePagesBtnProps ? morePagesBtnProps : {})}>
{morePagesBtnCaption}
@ -936,8 +900,6 @@ const P8PTable = ({
//Контроль свойств - Таблица
P8PTable.propTypes = {
style: PropTypes.object,
tableStyle: PropTypes.object,
columnsDef: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
@ -970,10 +932,6 @@ P8PTable.propTypes = {
).isRequired,
filters: PropTypes.arrayOf(P8P_TABLE_FILTER_SHAPE).isRequired,
size: PropTypes.string,
pageNumber: PropTypes.number,
pagesCount: PropTypes.number,
pagesAlign: PropTypes.string,
pagesPosition: PropTypes.string,
fixedHeader: PropTypes.bool,
fixedColumns: PropTypes.number,
morePages: PropTypes.bool,
@ -999,7 +957,6 @@ P8PTable.propTypes = {
onOrderChanged: PropTypes.func,
onFilterChanged: PropTypes.func,
onPagesCountChanged: PropTypes.func,
onPageChanged: PropTypes.func,
objectsCopier: PropTypes.func.isRequired,
containerComponent: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]),
containerComponentProps: PropTypes.object
@ -1009,13 +966,4 @@ P8PTable.propTypes = {
//Интерфейс модуля
//----------------
export {
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
};
export { P8P_TABLE_DATA_TYPE, P8P_TABLE_SIZE, P8P_TABLE_FILTER_SHAPE, P8P_TABLE_MORE_HEIGHT, P8P_TABLE_FILTERS_HEIGHT, P8PTable };

View File

@ -10,8 +10,8 @@
import React, { useReducer, createContext, useEffect, useContext, useCallback, useMemo } from "react"; //ReactJS
import PropTypes from "prop-types"; //Контроль свойств компонента
import { APP_AT, INITIAL_STATE, applicationReducer } from "./application_reducer"; //Редьюсер состояния
import { MessagingCtx } from "./messaging"; //Контекст отображения сообщений
import { BackEndCtx } from "./backend"; //Контекст взаимодействия с сервером
import { MessagingСtx } from "./messaging"; //Контекст отображения сообщений
import { BackEndСtx } from "./backend"; //Контекст взаимодействия с сервером
//---------
//Константы
@ -31,7 +31,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 }) => {
@ -39,10 +39,10 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
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 });
@ -56,9 +56,6 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
//Установка списка панелей
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);
@ -170,9 +167,8 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
//Вернём компонент провайдера
return (
<ApplicationCtx.Provider
<ApplicationСtx.Provider
value={{
setAppBarTitle,
findPanelByName,
pOnlineShowTab,
pOnlineShowUnit,
@ -186,7 +182,7 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
}}
>
{children}
</ApplicationCtx.Provider>
</ApplicationСtx.Provider>
);
};

View File

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

View File

@ -9,8 +9,7 @@
import React, { createContext, useContext, useCallback } from "react"; //ReactJS
import PropTypes from "prop-types"; //Контроль свойств компонента
import { MessagingCtx } from "./messaging"; //Контекст сообщений
import { formatErrorMessage } from "../core/utils"; //Вспомогательные функции
import { MessagingСtx } from "./messaging"; //Контекст сообщений
//---------
//Константы
@ -34,12 +33,12 @@ const BACKEND_CONTEXT_CLIENT_SHAPE = PropTypes.shape({
//----------------
//Контекст взаимодействия с серверным API
export const BackEndCtx = createContext();
export const BackEndСtx = createContext();
//Провайдер контекста взаимодействия с серверным API
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]);
@ -64,8 +63,7 @@ export const BackEndContext = ({ client, children }) => {
throwError = true,
showErrorMessage = true,
fullResponse = false,
spreadOutArguments = true,
signal = null
spreadOutArguments = true
} = {}) => {
try {
if (loader !== false) showLoader(loaderMessage);
@ -77,18 +75,12 @@ export const BackEndContext = ({ client, children }) => {
tagValueProcessor,
attributeValueProcessor,
throwError,
spreadOutArguments,
signal
spreadOutArguments
});
if (fullResponse === true || isRespErr(result)) return result;
else return result.XPAYLOAD;
} catch (e) {
if (showErrorMessage) {
//Разбираем текст ошибки
let errMsg = formatErrorMessage(e.message);
//Отображаем ошибку
showMsgErr(errMsg.text, null, errMsg.fullErrorText);
}
if (showErrorMessage) showMsgErr(e.message);
throw e;
} finally {
if (loader !== false) hideLoader();
@ -116,7 +108,7 @@ export const BackEndContext = ({ client, children }) => {
//Вернём компонент провайдера
return (
<BackEndCtx.Provider
<BackEndСtx.Provider
value={{
SERV_DATA_TYPE_STR: client.SERV_DATA_TYPE_STR,
SERV_DATA_TYPE_NUMB: client.SERV_DATA_TYPE_NUMB,
@ -130,7 +122,7 @@ export const BackEndContext = ({ client, children }) => {
}}
>
{children}
</BackEndCtx.Provider>
</BackEndСtx.Provider>
);
};

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import React, { createContext, useContext } from "react"; //ReactJS
import PropTypes from "prop-types"; //Контроль свойств компонента
import { useLocation, useNavigate } from "react-router-dom"; //Роутер приложения
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 { findPanelByName, setAppBarTitle } = useContext(ApplicationCtx);
const { findPanelByName } = useContext(ApplicationСtx);
//Проверка наличия параметров запроса
const isNavigationSearch = () => (location.search ? true : false);
@ -65,8 +65,6 @@ export const NavigationContext = ({ children }) => {
const navigateTo = ({ path, search, state, replace = false }) => {
//Если указано куда переходить
if (path) {
//Сброс кастомного заголовка
setAppBarTitle("");
//Переходим к адресу
if (state) navigate(path, { state: JSON.stringify(state), replace });
else navigate({ pathname: path, search: queryString.stringify(search), replace });

View File

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

View File

@ -102,17 +102,14 @@ const getDisplaySize = () => {
};
//Глубокое копирование объекта
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj)));
const deepCopyObject = obj => JSON.parse(JSON.stringify(obj));
//Конвертация объекта в Base64 XML
const object2XML = (obj, builderOptions) => {
const object2Base64XML = (obj, builderOptions) => {
const builder = new XMLBuilder(builderOptions);
return builder.build(obj);
return btoa(unescape(encodeURIComponent(builder.build(obj))));
};
//Конвертация объекта в Base64 XML
const object2Base64XML = (obj, builderOptions) => btoa(unescape(encodeURIComponent(object2XML(obj, builderOptions))));
//Конвертация XML в JSON
const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attributeValueProcessor, useDefaultPatterns = true }) => {
return new Promise((resolve, reject) => {
@ -161,43 +158,12 @@ const formatDateJSONDateOnly = value => (value ? dayjs(value).format("YYYY-MM-DD
//Форматирование числа в "Денежном" формате РФ
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 = () =>
"10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
);
//Формироваеие уникального идентификатора по текущему времени
const genUID = prefix => (prefix ? `${prefix}_${Date.now()}` : `${Date.now()}`);
//----------------
//Интерфейс модуля
//----------------
@ -206,14 +172,11 @@ export {
hasValue,
getDisplaySize,
deepCopyObject,
object2XML,
object2Base64XML,
xml2JSON,
formatDateRF,
formatDateTimeRF,
formatDateJSONDateOnly,
formatNumberRFCurrency,
formatErrorMessage,
genGUID,
genUID
genGUID
};

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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"}>
*Поддерживаются правила заливки, базирующиеся на дополнительных свойствах типа &quot;Строка&quot; или &quot;Число&quot;, из
профиля пользователя, настроенного для раздела &quot;События&quot; в 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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

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

View File

@ -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
}
];
};

View File

@ -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 }
};

View File

@ -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 };

View File

@ -11,9 +11,9 @@ import React, { useState, useContext, useCallback, useEffect } from "react"; //
import { Box } from "@mui/material"; //Интерфейсные компоненты
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { ApplicationCtx } from "../../context/application"; //Контекст приложения
import { MessagingCtx } from "../../context/messaging"; //Контекст сообщений
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { headCellRender, dataCellRender, groupCellRender, DIGITS_REG_EXP, MONTH_NAME_REG_EXP, DAY_NAME_REG_EXP } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { Filter } from "./filter"; //Компонент фильтра
@ -88,13 +88,13 @@ const EqsPrfrm = () => {
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 () => {

View File

@ -11,7 +11,7 @@ import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, Grid } from "@mui/material"; //Интерфейсные компоненты
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 { pOnlineShowDictionary } = useContext(ApplicationCtx);
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//При закрытии диалога без изменения фильтра
const handleCancel = () => (onCancel ? onCancel() : null);

View File

@ -8,7 +8,7 @@
//---------------------
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"; //Вспомогательные функции
//---------
@ -50,7 +50,7 @@ const useMechRecAssemblyMon = () => {
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndCtx);
const { executeStored } = useContext(BackEndСtx);
//Инициализация каталогов планов
const initPlanCtlgs = useCallback(async () => {
@ -134,7 +134,7 @@ const useCostProductComposition = planSpec => {
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndCtx);
const { executeStored } = useContext(BackEndСtx);
//При подключении компонента к странице
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(() => {

View File

@ -8,7 +8,7 @@
//---------------------
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 { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
@ -49,7 +49,7 @@ const useCostJobs = () => {
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndCtx);
const { executeStored } = useContext(BackEndСtx);
//Подключение к контексту навигации
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(
@ -192,7 +192,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(

View File

@ -11,7 +11,7 @@ import React, { useContext, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField } from "@mui/material"; //Интерфейсные элементы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { MessagingCtx } from "../../context/messaging"; //Контекст сообщений
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { CostJobsSpecsDataGrid } from "./fcjobssp"; //Собственные хуки таблиц
import { useCostJobs, useFilteredFcjobs } from "./hooks"; //Вспомогательные хуки
@ -97,7 +97,7 @@ const MechRecCostJobs = () => {
const filteredJobs = useFilteredFcjobs(state.jobList, filter);
//Подключение к контексту сообщений
const { InlineMsgInfo } = useContext(MessagingCtx);
const { InlineMsgInfo } = useContext(MessagingСtx);
//Выбор плана
const selectJob = job => {

View File

@ -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 };

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
//---------------------
import { useState, useCallback, useEffect, useContext } from "react";
import { BackEndCtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
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 () => {
@ -48,8 +48,7 @@ const useCostRouteLists = (task, taskType) => {
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
NINCLUDE_DEF: costRouteLists.dataLoaded ? 0 : 1
},
attributeValueProcessor: (name, val) =>
["DEXEC_DATE", "DREL_DATE"].includes(name) ? formatDateRF(val) : ["SDOCPREF", "SDOCNUMB"].includes(name) ? undefined : val,
attributeValueProcessor: (name, val) => (["DEXEC_DATE", "DREL_DATE"].includes(name) ? formatDateRF(val) : val),
respArg: "COUT"
});
setCostRouteLists(pv => ({
@ -104,7 +103,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 () => {
@ -157,7 +156,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 () => {
@ -208,7 +207,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 () => {

View File

@ -36,11 +36,10 @@ import {
Card,
CardHeader,
CardContent,
CardActions,
Tooltip
CardActions
} from "@mui/material"; //Интерфейсные элементы
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingCtx } from "../../context/messaging"; //Контекст сообщений
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
import { P8P_GANTT_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
@ -56,34 +55,19 @@ import { IncomFromDepsDataGrid } from "./datagrids/incomefromdeps"; //Табли
//---------
//Склонения для документов
const PLANS_DECLINATIONS = ["план", "плана", "планов"];
const SPEC_DECLINATIONS = ["элемент", "элемента", "элементов"];
const DECLINATIONS = ["план", "плана", "планов"];
//Поля сортировки
const SORT_REP_DATE = "DREP_DATE";
const SORT_REP_DATE_TO = "DREP_DATE_TO";
//Максимальное количество элементов
const MAX_TASKS = 10000;
//Стили
const STYLES = {
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
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_PRIMARY: { wordWrap: "break-word" },
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_DRAWER: {
width: "350px",
@ -100,26 +84,7 @@ const STYLES = {
TASK_DIALOG_ACTION_CONTAINER: { border: 1, borderColor: "text.primary", borderRadius: "5px", width: "100%" },
FILTERS: { display: "table", float: "right" },
FILTERS_DATE: { display: "table-cell", verticalAlign: "middle" },
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"
}
}
: {};
}
FILTERS_LEVEL: { display: "table-cell", verticalAlign: "middle", paddingLeft: "15px" }
};
//------------------------------------
@ -137,9 +102,7 @@ const parseProdPlanSpXML = async xmlDoc => {
};
//Форматирование для отображения количества документов
const formatCountDocs = (nCountDocs, nType = 0) => {
//Склонение документов
let DECLINATIONS = nType === 0 ? PLANS_DECLINATIONS : SPEC_DECLINATIONS;
const formatCountDocs = nCountDocs => {
//Получаем последнюю цифру в значении
let num = (nCountDocs % 100) % 10;
//Документов
@ -152,43 +115,11 @@ const formatCountDocs = (nCountDocs, nType = 0) => {
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 = ({
planCtlgs = [],
selectedPlans = [],
selectedPlanCtlg,
selectedPlansElements,
filter,
setFilter,
onCtlgClick,
onCtlgPlanClick,
onCtlgPlansOk,
onCtlgPlansCancel
} = {}) => {
const PlanCtlgsList = ({ planCtlgs = [], selectedPlanCtlg, filter, setFilter, onClick } = {}) => {
//Генерация содержимого
return (
<Box sx={STYLES.PLANS_LIST_CONTAINER}>
<Box sx={STYLES.PLANS_LIST_FILTER_CONTAINER}>
<div>
<TextField
sx={STYLES.PLANS_FINDER}
name="planFilter"
@ -202,96 +133,37 @@ const PlanCtlgsList = ({
></TextField>
<FormGroup sx={STYLES.PLANS_CHECKBOX_HAVEDOCS}>
<FormControlLabel
control={
<Checkbox checked={filter.haveDocs} onChange={event => setFilter(pv => ({ ...pv, haveDocs: event.target.checked }))} />
}
control={<Checkbox checked={filter.haveDocs} onChange={event => setFilter(pv => ({ ...pv, haveDocs: event.target.checked }))} />}
label="Только с планами"
labelPlacement="end"
/>
</FormGroup>
<List>
{planCtlgs.map(ctlg => (
<Box key={ctlg.NRN}>
{planCtlgs.map(p => (
<ListItemButton
sx={ctlg.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : null}
key={ctlg.NRN}
selected={ctlg.NRN === selectedPlanCtlg}
onClick={() => (onCtlgClick ? onCtlgClick(ctlg) : null)}
disabled={ctlg.NCOUNT_DOCS == 0}
sx={p.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : null}
key={p.NRN}
selected={p.NRN === selectedPlanCtlg}
onClick={() => (onClick ? onClick(p) : null)}
>
<ListItemText
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{ctlg.SNAME}</Typography>}
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(ctlg.NCOUNT_DOCS, 0)}</Typography>}
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{p.SNAME}</Typography>}
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(p.NCOUNT_DOCS)}</Typography>}
/>
</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>
</div>
);
};
//Контроль свойств - Список каталогов планов
PlanCtlgsList.propTypes = {
planCtlgs: PropTypes.array,
selectedPlans: PropTypes.array,
selectedPlanCtlg: PropTypes.number,
selectedPlansElements: PropTypes.number,
onCtlgClick: PropTypes.func,
onCtlgPlanClick: PropTypes.func,
onClick: PropTypes.func,
filter: PropTypes.object,
setFilter: PropTypes.func,
onCtlgPlansOk: PropTypes.func,
onCtlgPlansCancel: PropTypes.func
setFilter: PropTypes.func
};
//Генерация диалога задачи
@ -360,18 +232,12 @@ const MechRecCostProdPlans = () => {
showPlanList: false,
planCtlgs: [],
planCtlgsLoaded: false,
selectedPlans: [],
selectedPlansElements: 0,
selectedPlanCtlgSpecsLoaded: false,
selectedPlanCtlg: null,
selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null,
selectedPlanCtlgOutOfLimit: 0,
selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null,
loadedCtlg: null,
loadedPlans: [],
loadedElements: 0,
gantt: {},
selectedTaskDetail: null,
selectedTaskDetailType: null,
@ -384,17 +250,14 @@ const MechRecCostProdPlans = () => {
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 { showMsgInfo } = useContext(MessagingCtx);
//Инициализация каталогов планов
const initPlanCtlgs = useCallback(async () => {
if (!state.init) {
@ -402,101 +265,74 @@ const MechRecCostProdPlans = () => {
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_PP_CTLG_INIT",
args: {},
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 }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.init, executeStored]);
//Выбор каталога планов
const selectPlan = project => {
setState(pv => ({
...pv,
selectedPlanCtlg: project,
selectedPlanCtlgSpecsLoaded: false,
selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null,
selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null,
gantt: {},
showPlanList: false,
selectedTaskDetail: null,
selectedTaskDetailType: null
}));
};
//Сброс выбора каталога планов
const unselectPlan = () =>
setState(pv => ({
...pv,
selectedPlanCtlgSpecsLoaded: false,
selectedPlanCtlg: null,
selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null,
selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null,
gantt: {},
showPlanList: false,
selectedTaskDetail: null,
selectedTaskDetailType: null
}));
//Загрузка списка спецификаций каталога планов
const loadPlanCtglSpecs = useCallback(
async (level = null, sort = null) => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_GET",
args: {
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
}
args: { NCRN: state.selectedPlanCtlg, NLEVEL: level, SSORT_FIELD: sort, NFCPRODPLANSP: state.planSpec }
});
let doc = await parseProdPlanSpXML(data.COUT);
setState(pv => ({
...pv,
selectedPlanCtlgMaxLevel: data.NMAX_LEVEL,
selectedPlanCtlgLevel: level || level === 0 ? level : data.NMAX_LEVEL,
selectedPlanCtlgOutOfLimit: data.NOUT_OF_LIMIT,
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,
gantt: { ...doc, tasks: [...(doc?.tasks || [])] },
loadedCtlg: state.selectedPlanCtlg,
loadedPlans: [...state.selectedPlans],
loadedElements: state.selectedPlansElements,
showPlanList: false
gantt: { ...doc, tasks: [...(doc?.tasks || [])] }
}));
},
// 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 => {
//Если этот каталог не был выбран
if (state.selectedPlanCtlg != project.NRN) {
//Если выбран уже загруженный - укажем информацию о том, как он загружен
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
}));
const handleProjectClick = project => {
if (state.selectedPlanCtlg != project.NRN) selectPlan(project.NRN);
else unselectPlan();
};
//При подключении компонента к странице
@ -509,8 +345,8 @@ const MechRecCostProdPlans = () => {
//При смене выбранного каталога плана или при явном указании позиции спецификации плана
useEffect(() => {
if (state.planSpec) loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
}, [state.planSpec, loadPlanCtglSpecs]);
if (state.selectedPlanCtlg || state.planSpec) loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
}, [state.selectedPlanCtlg, state.planSpec, loadPlanCtglSpecs]);
//Выбор уровня
const handleChangeSelectLevel = selectedLevel => {
@ -534,17 +370,6 @@ const MechRecCostProdPlans = () => {
setState(pv => ({ ...pv, selectedTaskDetail: taskRn, selectedTaskDetailType: taskType }));
};
//При открытии окна информации об ограничении уровня
const handleLevelLimitInfoOpen = () => {
//Отображаем информацию
showMsgInfo(
`Размер производственной программы превышает предельно допустимый для одновременного отображения в виде диаграммы Ганта.
Доступные для просмотра уровни вложенности ограничены.
Вы можете просматривать производственную программу частями, используя действие "Открытие панели Производственная программа" в спецификации "Выпуск"
раздела "Планы и отчеты производства изделий".`
);
};
//Генерация содержимого
return (
<Box>
@ -553,18 +378,18 @@ const MechRecCostProdPlans = () => {
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
Каталоги планов
</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
planCtlgs={filteredPlanCtgls}
selectedPlans={state.selectedPlans}
selectedPlanCtlg={state.selectedPlanCtlg}
selectedPlansElements={state.selectedPlansElements}
filter={filter}
setFilter={setFilter}
onCtlgClick={handleCtlgClick}
onCtlgPlanClick={handleCtlgPlanClick}
onCtlgPlansOk={handleSelectedPlansOk}
onCtlgPlansCancel={handleSelectedPlansCancel}
onClick={handleProjectClick}
/>
</Drawer>
</>
@ -609,16 +434,8 @@ const MechRecCostProdPlans = () => {
</Select>
</Box>
<Box sx={STYLES.FILTERS_LEVEL}>
<Box sx={STYLES.FILTERS_LEVEL_CAPTION}>
<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
sx={STYLES.FILTERS_LIMIT_SELECT(state.selectedPlanCtlgOutOfLimit)}
labelId="select-label-level"
id="select-level"
value={state.selectedPlanCtlgLevel}
@ -646,14 +463,14 @@ const MechRecCostProdPlans = () => {
/>
</Box>
)
) : !state.loadedCtlg ? (
) : !state.selectedPlanCtlg ? (
<Box pt={3}>
<InlineMsgInfo
okBtn={false}
text={
state.planSpec
? "Загружаю график для выбранной позиции плана..."
: "Укажите каталог планов или планы для отображения их спецификаций"
: "Укажите каталог планов для отображения их спецификаций"
}
/>
</Box>

View File

@ -8,7 +8,7 @@
//---------------------
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"; //Вспомогательные функции
//---------
@ -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(() => {
@ -91,7 +91,7 @@ const useInsDepartment = fullDate => {
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndCtx);
const { executeStored } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
@ -142,7 +142,7 @@ const useFilter = (currentMonth, currentYear) => {
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndCtx);
const { executeStored } = useContext(BackEndСtx);
//Считываем количества рабочих дней
const getWorkDays = useCallback(

View File

@ -12,7 +12,7 @@ import PropTypes from "prop-types"; //Контроль свойств компо
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 { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { CostRouteListsSpecsDataGrid } from "./fcroutlstsp"; //Состояние таблицы заказов маршрутных листов
import { useCostRouteLists } from "./hooks.js"; //Хук состояния таблицы маршрутных листов
@ -142,7 +142,7 @@ const CostRouteListsDataGrid = ({ 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 }));

View File

@ -7,8 +7,8 @@
//Подключение библиотек
//---------------------
import React, { useState, useEffect, useContext, useCallback } from "react"; //Классы React
import { BackEndCtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import React, { useState, useEffect, useContext } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
//---------
@ -32,60 +32,17 @@ export const useFilteredPlans = (plans, filter) => {
return filteredPlans;
};
//Хук для планов
const useDeptCostProdPlans = month => {
//Собственное состояние - таблица данных
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 useDeptCostProdPlans = () => {
//Собственное состояние - таблица данных
const [state, setState] = useState({
init: false,
showPlanList: false,
showIncomeFromDeps: null,
showFcroutelst: null,
planList: [],
planListLoaded: false,
selectedPlan: {},
dataLoaded: false,
columnsDef: [],
orders: null,
@ -98,45 +55,16 @@ const useDeptCostProdPlanInfo = plan => {
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
//При необходимости очистки данных о плане
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 }));
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Если план выбран
if (plan.NRN) {
if (state.selectedPlan.NRN) {
const loadData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_DEPT_DG_GET",
args: {
NFCPRODPLAN: plan.NRN,
NFCPRODPLAN: state.selectedPlan.NRN,
CORDERS: { VALUE: object2Base64XML(state.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: state.pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE_LARGE,
@ -148,7 +76,6 @@ const useDeptCostProdPlanInfo = plan => {
setState(pv => ({
...pv,
...data.XDATA_GRID,
init: true,
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,
@ -156,20 +83,31 @@ const useDeptCostProdPlanInfo = plan => {
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_LARGE
}));
};
//Если необходимо перезагрузить
if (state.reload) {
loadData();
}
}
//Если план не выбран и есть какие-то данные
if (!plan.NRN && state.dataLoaded) {
//Очищаем их
handleClear();
}
}, [plan.NRN, state.reload, state.orders, state.pageNumber, state.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB, handleClear]);
}, [state.selectedPlan, state.reload, state.orders, state.pageNumber, state.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB]);
//Возвращаем данные
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 +125,7 @@ const useCostRouteLists = task => {
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
@ -226,7 +164,6 @@ const useCostRouteLists = task => {
task
]);
//Возвращаем данные
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(() => {
@ -286,7 +223,6 @@ const useCostRouteListsSpecs = mainRowRN => {
mainRowRN
]);
//Возвращаем данные
return [costRouteListsSpecs, setCostRouteListsSpecs];
};
@ -304,7 +240,7 @@ const useIncomFromDeps = task => {
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
@ -336,8 +272,7 @@ const useIncomFromDeps = task => {
}
}, [SERV_DATA_TYPE_CLOB, executeStored, incomFromDeps.dataLoaded, incomFromDeps.orders, incomFromDeps.pageNumber, incomFromDeps.reload, task]);
//Возвращаем данные
return [incomFromDeps, setIncomFromDeps];
};
export { useDeptCostProdPlans, useDeptCostProdPlanInfo, useCostRouteLists, useCostRouteListsSpecs, useIncomFromDeps };
export { useDeptCostProdPlans, useCostRouteLists, useCostRouteListsSpecs, useIncomFromDeps };

View File

@ -12,10 +12,10 @@ import PropTypes from "prop-types"; //Контроль свойств компо
import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField, Link, Grid } from "@mui/material"; //Интерфейсные элементы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
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 { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { MessagingCtx } from "../../context/messaging"; //Контекст сообщений
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { IncomFromDepsDataGridDialog } from "./incomefromdeps"; //Диалог сдачи продукции
import { CostRouteListsDataGridDialog } from "./fcroutlst"; //Диалог маршрутных листов
@ -34,8 +34,7 @@ const TITLE_PADDING_BOTTOM = "20px";
//Стили
const STYLES = {
PLANS_FILTER: { paddingTop: "20px", display: "flex", flexDirection: "column", alignItems: "center", gap: "5px" },
PLANS_FILTER_ITEM: { margin: "0px 10px", width: "93%" },
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
PLANS_BUTTON: { position: "absolute" },
PLANS_DRAWER: {
@ -61,18 +60,10 @@ const STYLES = {
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 }) => ({
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] };
//Описываем общий стиль
@ -136,7 +127,7 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
cellProps,
cellStyle,
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]}
</Link>
)
@ -148,7 +139,7 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
cellProps,
cellStyle: STYLES.DATA_GRID_CELL_MATRES_CODE(cellStyle, row),
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]}
</Link>
)
@ -158,38 +149,21 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
};
//Список каталогов планов
const PlanList = ({ plans = [], selectedPlan, filter, onFilterChange, onClick } = {}) => {
//При изменении фильтра
const handleFilterChange = e => {
onFilterChange && onFilterChange(e);
};
const PlanList = ({ plans = [], selectedPlan, filter, setFilter, onClick } = {}) => {
//Генерация содержимого
return (
<div>
<Box sx={STYLES.PLANS_FILTER}>
<TextField
sx={STYLES.PLANS_FILTER_ITEM}
name={SFIELD_MONTH}
label="Месяц"
type="month"
value={filter.month}
InputLabelProps={{ shrink: true }}
variant="standard"
fullWidth
onChange={handleFilterChange}
required={true}
></TextField>
<TextField
sx={STYLES.PLANS_FILTER_ITEM}
name="planName"
sx={STYLES.PLANS_FINDER}
name="planFilter"
label="План"
value={filter.planName}
variant="standard"
fullWidth
onChange={handleFilterChange}
onChange={event => {
setFilter(pv => ({ ...pv, planName: event.target.value }));
}}
></TextField>
</Box>
<List>
{plans.map(p => (
<ListItemButton key={p.NRN} selected={p.NRN === selectedPlan.NRN} onClick={() => (onClick ? onClick(p) : null)}>
@ -207,7 +181,7 @@ PlanList.propTypes = {
selectedPlan: PropTypes.object,
onClick: PropTypes.func,
filter: PropTypes.object,
onFilterChange: PropTypes.func
setFilter: PropTypes.func
};
//-----------
@ -216,135 +190,135 @@ PlanList.propTypes = {
//Корневая панель производственного плана цеха
const MechRecDeptCostProdPlans = () => {
//Собственное состояние - таблица данных
const [state, setState] = useDeptCostProdPlans();
//Состояние для фильтра каталогов
const [filter, setFilter] = useState({ planName: "", month: getCurrentYearMonth() });
//Собственное состояние - таблица планов
const [plans, setPlans] = useDeptCostProdPlans(filter.month);
//Собственное состояние - таблица информации
const [planInfo, setPlanInfo, onPlanInfoClear, onPlanInfoOrderChanged, onPlanInfoPagesCountChanged] = useDeptCostProdPlanInfo(plans.selected);
const [filter, setFilter] = useState({ planName: "" });
//Массив отфильтрованных каталогов
const filteredPlanCtgls = useFilteredPlans(plans.rows, filter);
const filteredPlanCtgls = useFilteredPlans(state.planList, filter);
//Подключение к контексту сообщений
const { InlineMsgInfo } = useContext(MessagingCtx);
const { InlineMsgInfo } = useContext(MessagingСtx);
//Выбор плана
const selectPlan = plan => {
setPlans(pv => ({
setState(pv => ({
...pv,
selected: plan,
showPlanList: false
showIncomeFromDeps: null,
showFcroutelst: null,
selectedPlan: plan,
showPlanList: false,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
reload: true,
pageNumber: 1,
morePages: true
}));
onPlanInfoClear();
};
//Сброс выбора плана
const unselectPlan = () => {
setPlans(pv => ({
const unselectPlan = () =>
setState(pv => ({
...pv,
selected: {},
showPlanList: false
showIncomeFromDeps: null,
showFcroutelst: null,
selectedPlan: {},
showPlanList: false,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
reload: true,
pageNumber: 1,
morePages: true
}));
onPlanInfoClear();
};
//Обработка нажатия на элемент в списке планов
const handlePlanClick = plan => {
if (plans.selected.NRN != plan.NRN) selectPlan(plan);
if (state.selectedPlan.NRN != plan.NRN) selectPlan(plan);
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 => {
setPlanInfo(pv => ({ ...pv, showIncomeFromDeps: planSp }));
setState(pv => ({ ...pv, showIncomeFromDeps: 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 (
<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>
<Drawer
anchor={"left"}
open={plans.showPlanList}
onClose={() => setPlans(pv => ({ ...pv, showPlanList: false }))}
open={state.showPlanList}
onClose={() => setState(pv => ({ ...pv, showPlanList: false }))}
sx={STYLES.PLANS_DRAWER}
>
<PlanList
plans={filteredPlanCtgls}
selectedPlan={plans.selected}
selectedPlan={state.selectedPlan}
filter={filter}
onFilterChange={handleFilterChange}
setFilter={setFilter}
onClick={handlePlanClick}
/>
</Drawer>
<Grid container>
<Grid item xs={12}>
<Box display="flex" justifyContent="center" alignItems="center">
{planInfo.dataLoaded ? (
planInfo.rows.length === 0 ? (
{state.dataLoaded ? (
state.rows.length === 0 ? (
<InlineMsgInfo okBtn={false} text={"В плане отсутствуют записи спецификации"} />
) : (
<Box sx={STYLES.CONTAINER}>
<Typography sx={STYLES.TITLE} variant={"h6"}>
{`Производственный план цеха №${plans.selected.SSUBDIV} на ${plans.selected.SPERIOD}`}
{`Производственный план цеха №${state.selectedPlan.SSUBDIV} на ${state.selectedPlan.SPERIOD}`}
</Typography>
<Box>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ elevation: 6, sx: STYLES.DATA_GRID_CONTAINER }}
fixedHeader={planInfo.fixedHeader}
fixedColumns={planInfo.fixedColumns}
columnsDef={planInfo.columnsDef}
rows={planInfo.rows}
fixedHeader={state.fixedHeader}
fixedColumns={state.fixedColumns}
columnsDef={state.columnsDef}
rows={state.rows}
size={P8P_DATA_GRID_SIZE.MEDIUM}
morePages={planInfo.morePages}
reloading={planInfo.reload}
onOrderChanged={handlePlanInfoOrderChanged}
onPagesCountChanged={handlePlanInfoPagesCountChanged}
dataCellRender={prms =>
dataCellRender({
...prms,
onProdOrderClick: handleProdOrderClick,
onMatresCodeClick: handleMatresCodeClick
})
}
morePages={state.morePages}
reloading={state.reload}
onOrderChanged={handleOrderChanged}
onPagesCountChanged={handlePagesCountChanged}
dataCellRender={prms => dataCellRender({ ...prms, handleProdOrderClick, handleMatresCodeClick })}
groupCellRender={groupCellRender}
/>
</Box>
</Box>
)
) : !plans.selected.NRN ? (
) : !state.selectedPlan.NRN ? (
<InlineMsgInfo okBtn={false} text={"Укажите план для отображения спецификаций"} />
) : null}
</Box>
</Grid>
</Grid>
{planInfo.showIncomeFromDeps ? (
<IncomFromDepsDataGridDialog task={planInfo.showIncomeFromDeps} onClose={() => handleProdOrderClick(null)} />
) : null}
{planInfo.showFcroutelst ? (
<CostRouteListsDataGridDialog task={planInfo.showFcroutelst} onClose={() => handleMatresCodeClick(null)} />
{state.showIncomeFromDeps ? (
<IncomFromDepsDataGridDialog task={state.showIncomeFromDeps} onClose={() => handleProdOrderClick(null)} />
) : null}
{state.showFcroutelst ? <CostRouteListsDataGridDialog task={state.showFcroutelst} onClose={() => handleMatresCodeClick(null)} /> : null}
</Box>
);
};

View File

@ -11,7 +11,7 @@ import React, { useContext, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Grid, Typography, Link, List, ListItem, ListItemButton, ListItemText, Divider, Fab, Icon } from "@mui/material"; //Интерфейсные элементы
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_2 from "./img/1_2.png"; //Изображение
import img1_3 from "./img/1_3.png"; //Изображение
@ -252,7 +252,7 @@ Img.propTypes = {
//Ссылка на раздел Системы
const UnitLink = ({ unitCode, children }) => {
//Подключение к контексту приложения
const { pOnlineShowUnit } = useContext(ApplicationCtx);
const { pOnlineShowUnit } = useContext(ApplicationСtx);
//Генерация содержимого
return (
@ -299,7 +299,7 @@ ChapterLink.propTypes = {
//Ссылка на информационную панель
const PanelLink = ({ panelName, children }) => {
//Подключение к контексту приложения
const { configUrlBase, findPanelByName, pOnlineShowTab } = useContext(ApplicationCtx);
const { configUrlBase, findPanelByName, pOnlineShowTab } = useContext(ApplicationСtx);
//Генерация содержимого
return (

View File

@ -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 };

View File

@ -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>
);
};
*/

View File

@ -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 };

View File

@ -1,73 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: График (редактор настроек)
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
import { P8P_CA_CHART_VALUE_TYPES, P8P_CA_CHART_ACTION_AREAS } from "./action"; //Общие ресурсы действий графика
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
//-----------
//Тело модуля
//-----------
//График (редактор настроек)
const ChartEditor = ({ id, dataSource = null, valueProviders = {}, actions = P8P_CAS_INITIAL, onSettingsChange = null } = {}) => {
//Собственное состояние - текущие настройки
const [settings, setSettings] = useState(null);
//При сохранении изменений элемента
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
//При изменении действий
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
//При сохранении настроек
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
//При изменении компонента
useEffect(() => {
settings?.id != id && setSettings({ id, dataSource, actions });
}, [settings, id, dataSource, actions]);
//Формирование представления
return (
<P8PEditorBox title={"Параметры графика"} onSave={handleSave}>
<P8PEditorSubHeader title={"Источник данных"} />
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
<P8PEditorSubHeader title={"Действия"} />
<P8PActions
actions={settings?.actions}
valueProviders={valueProviders}
areas={P8P_CA_CHART_ACTION_AREAS}
valueTypes={P8P_CA_CHART_VALUE_TYPES}
onChange={handleActionsChange}
/>
</P8PEditorBox>
);
};
//Контроль свойств компонента - График (редактор настроек)
ChartEditor.propTypes = {
id: PropTypes.string.isRequired,
dataSource: P8P_DATA_SOURCE_SHAPE,
valueProviders: PropTypes.object,
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
onSettingsChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export default ChartEditor;

View File

@ -1,101 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: График (представление)
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Paper } from "@mui/material"; //Интерфейсные элементы
import { P8PChart } from "../../../../components/p8p_chart"; //График
import { useDataSource, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
import {
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
P8P_COMPONENT_INLINE_MESSAGE,
P8PComponentInlineMessage
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
import { P8P_CA_SHAPE } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
import { getChartCustomTypeValue, getChartHandlers } from "./action"; //Общие ресурсы действий графика
//---------
//Константы
//---------
//Иконка компонента
const COMPONENT_ICON = "bar_chart";
//Наименование компонента
const COMPONENT_NAME = "График";
//Стили
const STYLES = {
CHART: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", display: "flex" },
CONTAINER: clickable => ({
...(clickable
? {
cursor: "pointer"
}
: {})
})
};
//-----------
//Тело модуля
//-----------
//График (представление)
const Chart = ({ dataSource = null, values = {}, actions = [], onValuesChange = null } = {}) => {
//Собственное состояние - данные
const [chart, error, haveConfing, haveData] = useDataSource({ dataSource, values, componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.CHART });
//Собственное состояние - обработчики компонента
const [handlers] = useComponentHandlers({ actions, onValuesChange, getCustomTypeValue: getChartCustomTypeValue });
//Обработчики областей
const { onComponentClick, onChartItemClick } = getChartHandlers(handlers);
//Формирование представления
return (
<Paper
sx={STYLES.CONTAINER(onComponentClick)}
className={"component-view__container component-view__container__empty"}
elevation={6}
onClick={event => onComponentClick && onComponentClick({ event, values })}
>
{haveConfing && haveData ? (
<P8PChart
style={STYLES.CHART}
{...chart}
options={{ responsive: true, maintainAspectRatio: false }}
onClick={prms => onChartItemClick && onChartItemClick({ values, prms })}
/>
) : (
<P8PComponentInlineMessage
icon={COMPONENT_ICON}
name={COMPONENT_NAME}
message={!haveConfing ? P8P_COMPONENT_INLINE_MESSAGE.NO_SETTINGS : error ? error : P8P_COMPONENT_INLINE_MESSAGE.NO_DATA_FOUND}
type={error ? P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR : P8P_COMPONENT_INLINE_MESSAGE_TYPE.COMMON}
/>
)}
</Paper>
);
};
//Контроль свойств компонента - График (представление)
Chart.propTypes = {
dataSource: P8P_DATA_SOURCE_SHAPE,
values: PropTypes.object,
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
onValuesChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export default Chart;

View File

@ -1,57 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Описание
*/
//---------
//Константы
//---------
const COMPONENTS = [
{
name: "Форма",
path: "form",
settings: {
id: "",
title: "",
autoApply: false,
orientation: "v",
items: []
}
},
{
name: "График",
path: "chart",
settings: {
id: "",
dataSource: {},
actions: []
}
},
{
name: "Таблица",
path: "table",
settings: {
id: "",
dataSource: {},
actions: [],
conditions: []
}
},
{
name: "Индикатор",
path: "indicator",
settings: {
id: "",
dataSource: {},
actions: [],
conditions: []
}
}
];
//----------------
//Интерфейс модуля
//----------------
export { COMPONENTS };

View File

@ -1,639 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Компоненты: Хуки компонентов
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useEffect, useContext, useCallback, useLayoutEffect } from "react"; //Классы React
import { BackEndCtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingCtx } from "../../../context/messaging"; //Контекст сообщений
import { object2Base64XML, genUID, xml2JSON } from "../../../core/utils"; //Вспомогательные функции
import { exportXMLFile } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
import { P8P_COMPONENT_SETTINGS_PATHS } from "../../../components/editors/p8p_component_settings"; //Дополнительные настройки источников
import { getActionsVariables } from "../../../components/editors/p8p_component_action/util"; //Вспомогательный функционал действий компонентов
import { P8P_CA_TYPE } from "../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
//---------
//Константы
//---------
//Начальное состояние размера макета
const INITIAL_BREAKPOINT = "lg";
//Начальное состояние макета
const INITIAL_LAYOUTS = {
[INITIAL_BREAKPOINT]: []
};
//---------
//Константы расчета высоты строки ResponsiveGridLayout
//---------
//Структура параметров для расчета высоты строки ResponsiveGridLayout
const GRID_LAYOUT_PARAMS = {
//Высота главного меню (px)
APP_BAR_HEIGHT: 64,
//Дополнительный отступ (px)
SAFE_OFFSET: 20,
//Максимальное количество строк (число)
MAX_ROWS: 68,
//Высота отступа (px)
MARGIN: 10
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Проверка на массив загружаемых данных панели
const isArrayPanelDesc = (name, jPath) =>
["items", "arguments", "conditions", "actions", "dependencies", "inputParams"].includes(name) || /(.*)XLAYOUTS\.[^.]*$/.test(jPath);
//Обработка значений тэгов загружаемых данных панели
const tagValueProcessorPanelDesc = (name, val, jPath) =>
["condValue", "resValue", "description"].includes(name)
? undefined
: /(.*)dataSource.arguments.value$/.test(jPath) || /(.*)XVALUE_PROVIDERS(.*)$/.test(jPath)
? undefined
: val;
//Конвертация серверного описания компонентов в данные для редактора панелей
const convertServerData2Components = components => {
//Корректировка информации о действия (Для типа "setVariable" значение ключа "params" - обязательно массив, в иных случаях - объект)
const correctionActions = actions => {
return actions.reduce(
(prevActions, action) => [
...prevActions,
{
...(action.type === P8P_CA_TYPE.setVariable.code && !Array.isArray(action.params)
? { ...action, params: [{ ...action.params }] }
: { ...action })
}
],
[]
);
};
//Форматируем устанавливая пустой объект для dataSource, если он пуст
return Object.keys(components).reduce(
(prev, cur) => ({
...prev,
[cur]: {
...components[cur],
settings: {
...components[cur].settings,
//dataSource - обязательно объект
...(components[cur].settings?.dataSource ? { dataSource: components[cur].settings.dataSource || {} } : {}),
//actions - требуют корректировки
...(components[cur].settings?.actions
? {
actions: correctionActions(components[cur].settings.actions)
}
: {})
}
}
}),
{}
);
};
//Конвертация серверного описания проводников значений в данные для редактора панелей
const convertServerData2ValueProviders = valueProviders => {
//Форматируем инициализируя dependencies, если требуется
return Object.keys(valueProviders).reduce(
(prev, cur) => ({ ...prev, [cur]: { ...valueProviders[cur], dependencies: valueProviders[cur].dependencies || [] } }),
{}
);
};
//Конвертация серверного описания панели в данные для редактора панелей
const serverPanelData2PanelDesc = (components, valueProviders, layouts, breakpoint) => {
//Возвращаем информацию о панеле с учетом конвертаций
return {
components: convertServerData2Components(components),
valueProviders: convertServerData2ValueProviders(valueProviders),
layouts: layouts[breakpoint] ? { [breakpoint]: [...layouts[breakpoint]] } : INITIAL_LAYOUTS,
breakpoint: breakpoint
};
};
//Считывание общего списка зависимостей от проводника значений
const getValueProvidersLinks = (componentPath, settings) => {
//Если это индикатор/график/таблица
if ([P8P_COMPONENT_SETTINGS_PATHS.INDICATOR, P8P_COMPONENT_SETTINGS_PATHS.CHART, P8P_COMPONENT_SETTINGS_PATHS.TABLE].includes(componentPath)) {
//Собираем зависимости из настройки источника
let argumentsSources = Array.isArray(settings?.dataSource?.arguments)
? settings.dataSource.arguments.reduce((prev, cur) => (cur.valueSource ? [...prev, cur.valueSource] : [...prev]), [])
: [];
//Собираем зависимости из параметров действий
let actionsSources = Array.isArray(settings?.actions) ? getActionsVariables(settings.actions) : [];
//Возвращаем зависимости компонента
return [...new Set([...argumentsSources, ...actionsSources])];
}
//Если это форма
if (P8P_COMPONENT_SETTINGS_PATHS.FORM === componentPath) {
//Собираем зависимости из элементов формы
let items = Array.isArray(settings?.items) ? settings.items.map(item => item.name) : [];
//Возвращаем зависимости компонента
return [...new Set(items)];
}
};
//-----------
//Тело модуля
//-----------
//Отложенная загрузка модуля компонента (как альтернативу можно применять React.lazy)
const useComponentModule = ({ path = null, module = "view" } = {}) => {
//Собственное состояние - импортированный модуль компонента
const [componentModule, setComponentModule] = useState(null);
//Собственное состояние - флаг готовности
const [init, setInit] = useState(false);
//При подмонтировании к странице
useEffect(() => {
//Динамическая загрузка модуля компонента из библиотеки
const importComponentModule = async () => {
setInit(false);
const moduleContent = await import(`./${path}/${module}`);
setComponentModule(moduleContent);
setInit(true);
};
if (path) importComponentModule();
}, [path, module]);
//Возвращаем интерфейс хука
return [componentModule, init];
};
//Работа с панелью
const usePanel = () => {
//Собственное состояние - рабочая панель
const [panel, setPanel] = useState();
//Собственное состояние - имя рабочей панели
const [panelName, setPanelName] = useState(null);
//Собственное состояние - наличие изменений рабочей панели
const [isPanelChanged, setIsPanelChanged] = useState(false);
//Собственное состояние - возможность редактирования
const [isEditAvaliable, setIsEditAvaliable] = useState(true);
//Собственное состояние - режим редактирования
const [editMode, setEditMode] = useState(true);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndCtx);
//При необходимости загрузки информации о панели
const loadPanel = async panel => {
//Считываем информацию с сервера
const res = await executeStored({
stored: "PKG_P8PANELS_PE.PANEL_ATTRS_GET_BY_CODE",
args: {
SCODE: panel
},
respArg: "COUT",
loader: true
});
//Устанавливаем рабочую панель
setPanel(res.XPANEL_ATTRS.rn);
//Устанавливаем наименование рабочей панели
setPanelName(res.XPANEL_ATTRS.name);
//Сбрасываем наличие изменений рабочей панели
setIsPanelChanged(false);
//Загружаемую панель запрещено редактировать
setIsEditAvaliable(false);
setEditMode(false);
};
//При выборе панели
const selectPanel = (panel, panelName, isPanelEditAvaliable) => {
//Обновляем информацию о панели
setPanel(panel);
setPanelName(panelName);
setIsEditAvaliable(isPanelEditAvaliable);
setEditMode(isPanelEditAvaliable);
setIsPanelChanged(false);
};
//При закрытии панели
const closePanel = () => {
setPanel(null);
setPanelName(null);
setIsPanelChanged(false);
};
//При установке признака изменений панели
const setPanelChanged = isChanged => setIsPanelChanged(isChanged);
//При установке признака режима редактирования
const changeEditMode = isEditMode => setEditMode(isEditMode);
//Возвращаем интерфейс хука
return [panel, panelName, editMode, isEditAvaliable, isPanelChanged, loadPanel, selectPanel, closePanel, changeEditMode, setPanelChanged];
};
//Работа с менеджером панелей
const usePanelManager = () => {
//Собственное состояние - флаг необходимости обновления
const [refresh, setRefresh] = useState(true);
//Собственное состояние - данные
const [data, setData] = useState(null);
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
//Добавление панели
const insertPanel = useCallback(
async (code, name) => {
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_INSERT", args: { SCODE: code, SNAME: name }, loader: false });
setRefresh(true);
},
[executeStored]
);
//Изменение панели
const updatePanel = useCallback(
async (query, code, name) => {
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_UPDATE", args: { NRN: query, SCODE: code, SNAME: name }, loader: false });
setRefresh(true);
},
[executeStored]
);
//Удаление панели
const deletePanel = useCallback(
async query => {
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_DELETE", args: { NRN: query }, loader: false });
setRefresh(true);
},
[executeStored]
);
//Установка флага готовности панели
const setPanelReady = useCallback(
async (query, ready) => {
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_READY_SET", args: { NRN: query, NREADY: ready }, loader: false });
setRefresh(true);
},
[executeStored]
);
//Установка флага публичности панели
const setPanelPbl = useCallback(
async (query, pbl) => {
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_PBL_SET", args: { NRN: query, NPBL: pbl }, loader: false });
setRefresh(true);
},
[executeStored]
);
//Импорт новой панели
const importPanel = useCallback(
async fileData => {
await executeStored({
stored: "PKG_P8PANELS_PE.PANEL_IMPORT",
args: {
CPANEL: {
//Форматируем данные документа в base64
VALUE: btoa(unescape(encodeURIComponent(fileData))),
SDATA_TYPE: SERV_DATA_TYPE_CLOB
}
},
loader: true
});
setRefresh(true);
},
[SERV_DATA_TYPE_CLOB, executeStored]
);
//При необходимости получить/обновить данные
useEffect(() => {
//Загрузка данных с сервера
const loadData = async () => {
try {
const data = await executeStored({
stored: "PKG_P8PANELS_PE.PANEL_LIST",
respArg: "COUT",
isArray: name => ["XPANEL"].includes(name),
attributeValueProcessor: (name, val) => (["code", "name"].includes(name) ? undefined : val),
loader: true
});
setData(data?.XPANELS?.XPANEL || []);
} finally {
setRefresh(false);
}
};
//Если надо обновить
if (refresh)
//Получим данные
loadData();
}, [refresh, executeStored]);
//Возвращаем интерфейс хука
return [data, insertPanel, updatePanel, deletePanel, setPanelReady, setPanelPbl, importPanel];
};
//Работа с содержимым панели
const usePanelDesc = panel => {
//Собственное состояние - флаг инициализированности
const [isInit, setInit] = useState(false);
//Собственное состояние - флаг необходимости обновления
const [refresh, setRefresh] = useState(true);
//Собственное состояние - данные
const [data, setData] = useState({
components: {},
valueProviders: {},
layouts: INITIAL_LAYOUTS,
breakpoint: INITIAL_BREAKPOINT
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndCtx);
//Подключение к контексту сообщений
const { showMsgErr } = useContext(MessagingCtx);
//Считывание базовой информации о панели
const getPanelInfo = useCallback(async () => {
//Считываем информацию с сервера
const res = await executeStored({
stored: "PKG_P8PANELS_PE.PANEL_ATTRS_GET",
args: {
NRN: panel
},
respArg: "COUT",
loader: true
});
//Забираем все без RN
// eslint-disable-next-line no-unused-vars
const { rn, ...panelAttrs } = res.XPANEL_ATTRS;
//Возвращаем результат
return panelAttrs;
}, [executeStored, panel]);
//Формирования данных панели для выгрузки
const makeXMLPanelDesc = useCallback(
async (includePanelInfo = false, isBase64 = true) => {
//Считываем информацию о выгружаемой панели
const panelInfo = includePanelInfo ? await getPanelInfo() : {};
//Сформируем данные в формат XML
const xmlData = object2Base64XML(
{
XPANEL: {
...(includePanelInfo ? { XPANEL_INFO: panelInfo } : {}),
XCOMPONENTS: data.components,
XVALUE_PROVIDERS: data.valueProviders,
XLAYOUTS: data.layouts,
XOPTIONS: {
breakpoint: data.breakpoint
}
}
},
{ suppressEmptyNode: false }
);
//Возвращаем данные в формате XML (формат исходит от признака)
return isBase64 ? xmlData : decodeURIComponent(escape(atob(xmlData)));
},
[data.breakpoint, data.components, data.layouts, data.valueProviders, getPanelInfo]
);
//Добавление компонента в макет
const addComponent = component => {
//Генерируем ID
const id = genUID(component.path);
//Добавляем компонент и его макет
setData(pv => ({
...pv,
components: { ...pv.components, [id]: { ...component, settings: { ...component.settings, id: id } } },
layouts: { ...pv.layouts, [data.breakpoint]: [...pv.layouts[data.breakpoint], { i: id, x: 0, y: 0, w: 4, h: 10 }] }
}));
};
//Удаление компонента из макета
const deleteComponent = id => {
//Удаляем все старые зависимости от компонента
const newValueProviders = Object.keys(data.valueProviders).reduce(
(prev, cur) => ({
...prev,
[cur]: { ...data.valueProviders[cur], dependencies: data.valueProviders[cur].dependencies.filter(el => el !== id) }
}),
{}
);
//Обновляем данные
setData(pv => ({
...pv,
layouts: { ...pv.layouts, [data.breakpoint]: data.layouts[data.breakpoint].filter(item => item.i !== id) },
components: { ...pv.components, [id]: { ...pv.components[id], deleted: true } },
valueProviders: { ...newValueProviders }
}));
};
//Изменение размера холста
const breakpointChange = breakpoint => setData(pv => ({ ...pv, breakpoint: breakpoint }));
//Изменение состояния макета
const layoutsChange = layouts => setData(pv => ({ ...pv, layouts: layouts }));
//Изменение значений в компоненте
const changeValueProviders = values => {
//Считываем проводники
const newValueProviders = { ...data.valueProviders };
//Переносим новые значения в проводники
Object.keys(values).map(el => (newValueProviders[el].value = values[el]));
//Обновляем проводники
setData(pv => ({ ...pv, valueProviders: { ...newValueProviders } }));
};
//Изменение настроек компонента
const changeComponentSettings = (id = null, settings = {}, onChanged = null) => {
//Считываем новые зависимости компонента
const providedValues = getValueProvidersLinks(data.components[id].path, settings);
//Удаляем все старые зависимости от компонента
const newValueProviders = Object.keys(data.valueProviders).reduce(
(prev, cur) => ({
...prev,
[cur]: { ...data.valueProviders[cur], dependencies: data.valueProviders[cur].dependencies.filter(el => el !== id) }
}),
{}
);
//Добавляем новые зависимости
providedValues.map(providerName => newValueProviders[providerName].dependencies.push(id));
//Обновляем данные
setData(pv => ({
...pv,
components: { ...pv.components, [id]: { ...pv.components[id], settings: { ...settings } } },
valueProviders: { ...newValueProviders }
}));
//Выполняем действия после изменения
onChanged && onChanged();
};
//Изменение настроек панели
const changePanelSettings = valueProviders => {
//Обновляем данные
setData(pv => ({
...pv,
valueProviders: { ...valueProviders }
}));
};
//Сохранение описания панели
const savePanelDesc = useCallback(
async (callBack = null) => {
try {
await executeStored({
stored: "PKG_P8PANELS_PE.PANEL_DESC_SET",
args: {
NRN: panel,
CPANEL: {
VALUE: await makeXMLPanelDesc(),
SDATA_TYPE: SERV_DATA_TYPE_CLOB
}
},
loader: true
});
callBack && callBack(false);
} catch (e) {
callBack && callBack(true);
}
},
[SERV_DATA_TYPE_CLOB, executeStored, makeXMLPanelDesc, panel]
);
//Загрузка описания панели из файла
const importPanelDesc = async (fileData, callBack = null) => {
//Формируем данные панели
const panelData = await xml2JSON({
xmlDoc: fileData,
isArray: isArrayPanelDesc,
tagValueProcessor: tagValueProcessorPanelDesc
});
//Если файл содержит тэг XPANEL
if (panelData.XPANEL) {
setData(
serverPanelData2PanelDesc(
panelData.XPANEL?.XCOMPONENTS || {},
panelData.XPANEL?.XVALUE_PROVIDERS || {},
panelData.XPANEL?.XLAYOUTS || {},
panelData.XPANEL?.XOPTIONS?.breakpoint || INITIAL_BREAKPOINT
)
);
callBack && callBack(true);
} else {
showMsgErr("Загружаемые данные не соответствуют формату настройки панели.");
callBack && callBack(false);
}
};
//Выгрузка панели в файл
const exportPanelDesc = async panelName => {
//Формируем XML-представление панели
const xmlPanelDesc = await makeXMLPanelDesc(true, false);
//Выгружаем в файл
exportXMLFile(xmlPanelDesc, panelName);
};
//При необходимости получить/обновить данные
useEffect(() => {
//Загрузка данных с сервера
const loadData = async () => {
try {
const data = await executeStored({
stored: "PKG_P8PANELS_PE.PANEL_DESC_GET",
args: { NRN: panel },
respArg: "COUT",
isArray: isArrayPanelDesc,
tagValueProcessor: tagValueProcessorPanelDesc,
loader: true
});
setData(
serverPanelData2PanelDesc(
data?.XCOMPONENTS || {},
data?.XVALUE_PROVIDERS || {},
data?.XLAYOUTS || {},
data?.XOPTIONS?.breakpoint || INITIAL_BREAKPOINT
)
);
setInit(true);
} finally {
setRefresh(false);
}
};
//Если надо обновить
if (refresh)
if (panel)
//Если есть для чего получать данные
loadData();
//Нет идентификатора запроса - нет данных
else
setData({
components: {},
valueProviders: {},
layouts: INITIAL_LAYOUTS,
breakpoint: INITIAL_BREAKPOINT
});
}, [refresh, panel, executeStored]);
//При изменении входных свойств - поднимаем флаг обновления
useEffect(() => {
setInit(false);
setRefresh(true);
}, [panel]);
//Возвращаем интерфейс хука
return [
data,
isInit,
addComponent,
deleteComponent,
breakpointChange,
layoutsChange,
changeValueProviders,
changeComponentSettings,
changePanelSettings,
savePanelDesc,
importPanelDesc,
exportPanelDesc
];
};
//Работа с соотношением размеров
const useWindowResize = () => {
//Состояние высоты строки ResponsiveGridLayout
const [rowHeight, setRowHeight] = useState(1);
//При изменении размера
useLayoutEffect(() => {
//Расчет высоты строки
const updateRowHeight = () => {
//Определяем доступную область
const availableHeight = window.innerHeight - GRID_LAYOUT_PARAMS.APP_BAR_HEIGHT - GRID_LAYOUT_PARAMS.SAFE_OFFSET;
//Промежутки между рядами
const totalMargins = (GRID_LAYOUT_PARAMS.MAX_ROWS - 1) * GRID_LAYOUT_PARAMS.MARGIN;
//Рассчитываем высоту строки
const result = (availableHeight - totalMargins) / GRID_LAYOUT_PARAMS.MAX_ROWS;
//Устанавливаем
setRowHeight(result);
};
//Запускаем при открытии панели
updateRowHeight();
//Устанавливаем обработчик на событие
window.addEventListener("resize", updateRowHeight);
//Удаляем обработчик на событие
return () => window.removeEventListener("resize", updateRowHeight);
}, []);
//Возвращаем интерфейс хука
return [rowHeight];
};
//----------------
//Интерфейс модуля
//----------------
export { useComponentModule, usePanel, usePanelManager, usePanelDesc, useWindowResize };

View File

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

View File

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

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