From 759fc763e2efb2496497070160d4a891f1f00bf2 Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Tue, 22 Jul 2025 13:24:35 +0300 Subject: [PATCH] =?UTF-8?q?WEBAPP:=20=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20"=D0=A0=D0=B5=D0=B4=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=B9"=20-=20=D1=80=D0=B0=D0=B7=D0=B1=D0=B8=D1=82=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=82=D0=B8=D0=BF=D0=BE=D0=B2=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B,?= =?UTF-8?q?=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B=20=D0=B2=20=D1=8F=D0=B4?= =?UTF-8?q?=D1=80=D0=BE=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F,=20=D0=B7=D0=B0=20=D0=BF=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=8B=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.text.js | 18 +- .../editors/p8p_component_inline_message.js} | 30 +- app/components/editors/p8p_config_dialog.js | 40 ++ app/components/editors/p8p_data_source.js | 113 +++++ .../editors/p8p_data_source_common.js | 86 ++++ .../editors/p8p_data_source_config_dialog.js | 185 ++++++++ .../editors/p8p_data_source_hooks.js | 151 +++++++ app/components/editors/p8p_editor_box.js | 54 +++ .../editors/p8p_editor_sub_header.js | 46 ++ app/components/editors/p8p_editor_toolbar.js | 52 +++ app/components/editors/p8p_editors_common.js | 30 ++ app/panels/panels_editor/component_editor.js | 3 +- app/panels/panels_editor/component_view.js | 1 - .../panels_editor/components/chart/editor.js | 16 +- .../panels_editor/components/chart/view.js | 21 +- .../panels_editor/components/components.js | 8 +- .../components/components_hooks.js | 136 +----- .../components/editors_common.js | 426 ------------------ .../panels_editor/components/form/editor.js | 17 +- .../panels_editor/components/form/view.js | 6 +- .../components/indicator/editor.js | 16 +- .../components/indicator/view.js | 21 +- .../panels_editor/components/table/editor.js | 16 +- .../panels_editor/components/table/view.js | 21 +- app/panels/panels_editor/layout_item.js | 1 - app/panels/panels_editor/panels_editor.css | 11 - app/panels/panels_editor/panels_editor.js | 28 +- 27 files changed, 887 insertions(+), 666 deletions(-) rename app/{panels/panels_editor/components/views_common.js => components/editors/p8p_component_inline_message.js} (60%) create mode 100644 app/components/editors/p8p_config_dialog.js create mode 100644 app/components/editors/p8p_data_source.js create mode 100644 app/components/editors/p8p_data_source_common.js create mode 100644 app/components/editors/p8p_data_source_config_dialog.js create mode 100644 app/components/editors/p8p_data_source_hooks.js create mode 100644 app/components/editors/p8p_editor_box.js create mode 100644 app/components/editors/p8p_editor_sub_header.js create mode 100644 app/components/editors/p8p_editor_toolbar.js create mode 100644 app/components/editors/p8p_editors_common.js delete mode 100644 app/panels/panels_editor/components/editors_common.js diff --git a/app.text.js b/app.text.js index 57dd8c0..f70a11d 100644 --- a/app.text.js +++ b/app.text.js @@ -12,14 +12,18 @@ export const TITLES = { INFO: "Информация", //Информационный блок WARN: "Предупреждение", //Блок предупреждения ERR: "Ошибка", //Информация об ошибке - DEFAULT_PANELS_GROUP: "Без привязки к группе" //Заголовок группы панелей по умолчанию + DEFAULT_PANELS_GROUP: "Без привязки к группе", //Заголовок группы панелей по умолчанию + DATA_SOURCE_CONFIG: "Настройка источника данных" //Заголовок для настройки источника данных }; //Текст export const TEXTS = { LOADING: "Ожидайте...", //Ожидание завершения процесса NO_DATA_FOUND: "Данных не найдено", //Отсутствие данных - NO_DATA_FOUND_SHORT: "Н.Д." //Отсутствие данных (кратко) + NO_DATA_FOUND_SHORT: "Н.Д.", //Отсутствие данных (кратко) + NO_SETTINGS: "Настройки не определены", //Отстутсвие настроек + UNKNOWN_SOURCE_TYPE: "Неизвестный тип источника", //Отсуствие типа источника + UNNAMED_SOURCE: "Источник без наименования" //Отсутствие наименования источника }; //Текст кнопок @@ -38,7 +42,8 @@ export const BUTTONS = { FILTER: "Фильтр", //Фильтрация MORE: "Ещё", //Догрузка данных APPLY: "Применить", //Сохранение без закрытия интерфейса ввода - SAVE: "Сохранить" //Сохранение + SAVE: "Сохранить", //Сохранение + CONFIG: "Настроить" //Настройка }; //Метки атрибутов, сопроводительные надписи @@ -51,7 +56,9 @@ export const CAPTIONS = { START: "Начало", END: "Окончание", PROGRESS: "Прогресс", - LEGEND: "Легенда" + LEGEND: "Легенда", + USER_PROC: "Пользовательская процедура", + QUERY: "Запрос" }; //Типовые сообщения об ошибках @@ -59,7 +66,8 @@ export const ERRORS = { UNDER_CONSTRUCTION: "Панель в разработке", P8O_API_UNAVAILABLE: '"ПАРУС 8 Онлайн" недоступен', P8O_API_UNSUPPORTED: 'Функция "ПАРУС 8 Онлайн" не поддерживается', - DEFAULT: "Неожиданная ошибка" + DEFAULT: "Неожиданная ошибка", + DATA_SOURCE_NO_REQ_ARGS: "Не заданы обязательные параметры источника данных" }; //Типовые сообщения для ошибок HTTP diff --git a/app/panels/panels_editor/components/views_common.js b/app/components/editors/p8p_component_inline_message.js similarity index 60% rename from app/panels/panels_editor/components/views_common.js rename to app/components/editors/p8p_component_inline_message.js index 085ed0c..4cf4176 100644 --- a/app/panels/panels_editor/components/views_common.js +++ b/app/components/editors/p8p_component_inline_message.js @@ -1,6 +1,6 @@ /* - Парус 8 - Панели мониторинга - Редактор панелей - Общие компоненты представлений элементов панели + Парус 8 - Панели мониторинга - Редакторы панелей + Компонент: Информационное сообщение внутри компонента */ //--------------------- @@ -10,22 +10,22 @@ import React from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Stack, Icon, Typography } from "@mui/material"; //Интерфейсные элементы -import { TEXTS } from "../../../../app.text"; //Общие текстовые ресурсы +import { TEXTS } from "../../../app.text"; //Общие текстовые ресурсы //--------- //Константы //--------- -//Типы сообщений -const COMPONENT_MESSAGE_TYPE = { +//Типы сообщений компонентов +const P8P_COMPONENT_INLINE_MESSAGE_TYPE = { COMMON: "COMMON", ERROR: "ERROR" }; -//Типовые сообщения -const COMPONENT_MESSAGES = { +//Типовые сообщения компонентов +const P8P_COMPONENT_INLINE_MESSAGE = { NO_DATA_FOUND: TEXTS.NO_DATA_FOUND, - NO_SETTINGS: "Настройте компонент" + NO_SETTINGS: TEXTS.NO_SETTINGS }; //----------- @@ -33,7 +33,7 @@ const COMPONENT_MESSAGES = { //----------- //Информационное сообщение внутри компонента -const ComponentInlineMessage = ({ icon, name, message, type = COMPONENT_MESSAGE_TYPE.COMMON }) => { +const P8PComponentInlineMessage = ({ icon, name, message, type = P8P_COMPONENT_INLINE_MESSAGE_TYPE.COMMON }) => { //Формирование представления return ( @@ -45,7 +45,11 @@ const ComponentInlineMessage = ({ icon, name, message, type = COMPONENT_MESSAGE_ )} - + {message} @@ -53,15 +57,15 @@ const ComponentInlineMessage = ({ icon, name, message, type = COMPONENT_MESSAGE_ }; //Контроль свойств - Информационное сообщение внутри компонента -ComponentInlineMessage.propTypes = { +P8PComponentInlineMessage.propTypes = { icon: PropTypes.string, name: PropTypes.string, message: PropTypes.string.isRequired, - type: PropTypes.oneOf(Object.values(COMPONENT_MESSAGE_TYPE)) + type: PropTypes.oneOf(Object.values(P8P_COMPONENT_INLINE_MESSAGE_TYPE)) }; //---------------- //Интерфейс модуля //---------------- -export { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage }; +export { P8P_COMPONENT_INLINE_MESSAGE_TYPE, P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage }; diff --git a/app/components/editors/p8p_config_dialog.js b/app/components/editors/p8p_config_dialog.js new file mode 100644 index 0000000..133dd5b --- /dev/null +++ b/app/components/editors/p8p_config_dialog.js @@ -0,0 +1,40 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Компонент: Диалог настройки +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { P8PDialog } from "../p8p_dialog"; //Типовой диалог + +//----------- +//Тело модуля +//----------- + +//Диалог настройки +const P8PConfigDialog = ({ title, children, onOk, onCancel }) => { + //Формирование представления + return ( + + {children} + + ); +}; + +//Контроль свойств компонента - Диалог настройки +P8PConfigDialog.propTypes = { + title: PropTypes.string.isRequired, + children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), + onOk: PropTypes.func, + onCancel: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PConfigDialog }; diff --git a/app/components/editors/p8p_data_source.js b/app/components/editors/p8p_data_source.js new file mode 100644 index 0000000..965e7a6 --- /dev/null +++ b/app/components/editors/p8p_data_source.js @@ -0,0 +1,113 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Компонент: Источник данных +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Stack, IconButton, Icon, Typography, Chip, Button, Card, CardContent, CardActions, CardActionArea } from "@mui/material"; //Интерфейсные элементы +import { BUTTONS, TEXTS } from "../../../app.text"; //Общие текстовые ресурсы +import { STYLES as COMMON_STYLES } from "./p8p_editors_common"; //Общие ресурсы редаторов +import { P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_TYPE_NAME, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных" +import { P8PDataSourceConfigDialog } from "./p8p_data_source_config_dialog"; //Диалог настройки источника данных + +//----------- +//Тело модуля +//----------- + +//Источник данных +const P8PDataSource = ({ dataSource = null, valueProviders = {}, onChange = null } = {}) => { + //Собственное состояние - отображение диалога настройки + const [configDlg, setConfigDlg] = useState(false); + + //Уведомление родителя о смене настроек источника данных + const notifyChange = settings => onChange && onChange(settings); + + //При нажатии на настройку источника данных + const handleSetup = () => setConfigDlg(true); + + //При нажатии на настройку источника данных + const handleSetupOk = dataSource => { + setConfigDlg(false); + notifyChange(dataSource); + }; + + //При нажатии на настройку источника данных + const handleSetupCancel = () => setConfigDlg(false); + + //При удалении настроек источника данных + const handleDelete = () => notifyChange({ ...P8P_DATA_SOURCE_INITIAL }); + + //Расчет флага "настроенности" + const configured = dataSource?.type ? true : false; + + //Список аргументов + const args = + configured && + dataSource.arguments.map((argument, i) => ( + + )); + + //Формирование представления + return ( + <> + {configDlg && ( + + )} + {configured && ( + + + + + {dataSource.type === P8P_DATA_SOURCE_TYPE.USER_PROC ? dataSource.userProc : TEXTS.UNNAMED_SOURCE} + + + {P8P_DATA_SOURCE_TYPE_NAME[dataSource.type] || TEXTS.UNKNOWN_SOURCE_TYPE} + + + {args} + + + + + + delete + + + + )} + {!configured && ( + + )} + + ); +}; + +//Контроль свойств компонента - Источник данных +P8PDataSource.propTypes = { + dataSource: P8P_DATA_SOURCE_SHAPE, + valueProviders: PropTypes.object, + onChange: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PDataSource }; diff --git a/app/components/editors/p8p_data_source_common.js b/app/components/editors/p8p_data_source_common.js new file mode 100644 index 0000000..722e76d --- /dev/null +++ b/app/components/editors/p8p_data_source_common.js @@ -0,0 +1,86 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Общие ресурсы компонента "Источник данных" +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import PropTypes from "prop-types"; //Контроль свойств компонента +import client from "../../core/client"; //Клиент БД +import { CAPTIONS } from "../../../app.text"; //Общие текстовые ресурсы + +//--------- +//Константы +//--------- + +//Типы даных аргументов +const P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE = { + STR: client.SERV_DATA_TYPE_STR, + NUMB: client.SERV_DATA_TYPE_NUMB, + DATE: client.SERV_DATA_TYPE_DATE +}; + +//Структура аргумента источника данных +const P8P_DATA_SOURCE_ARGUMENT_SHAPE = PropTypes.shape({ + name: PropTypes.string.isRequired, + caption: PropTypes.string.isRequired, + dataType: PropTypes.oneOf(Object.values(P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE)), + req: PropTypes.bool.isRequired, + value: PropTypes.any, + valueSource: PropTypes.string +}); + +//Начальное состояние аргумента источника данных +const P8P_DATA_SOURCE_ARGUMENT_INITIAL = { + name: "", + caption: "", + dataType: "", + req: false, + value: "", + valueSource: "" +}; + +//Типы источников данных +const P8P_DATA_SOURCE_TYPE = { + USER_PROC: "USER_PROC", + QUERY: "QUERY" +}; + +//Типы источников данных (наименования) +const P8P_DATA_SOURCE_TYPE_NAME = { + [P8P_DATA_SOURCE_TYPE.USER_PROC]: CAPTIONS.USER_PROC, + [P8P_DATA_SOURCE_TYPE.QUERY]: CAPTIONS.QUERY +}; + +//Структура источника данных +const P8P_DATA_SOURCE_SHAPE = PropTypes.shape({ + type: PropTypes.oneOf([...Object.values(P8P_DATA_SOURCE_TYPE), ""]), + userProc: PropTypes.string, + stored: PropTypes.string, + respArg: PropTypes.string, + arguments: PropTypes.arrayOf(P8P_DATA_SOURCE_ARGUMENT_SHAPE) +}); + +//Начальное состояние истоника данных +const P8P_DATA_SOURCE_INITIAL = { + type: "", + userProc: "", + stored: "", + respArg: "", + arguments: [] +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { + P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE, + P8P_DATA_SOURCE_ARGUMENT_INITIAL, + P8P_DATA_SOURCE_SHAPE, + P8P_DATA_SOURCE_TYPE, + P8P_DATA_SOURCE_TYPE_NAME, + P8P_DATA_SOURCE_INITIAL +}; diff --git a/app/components/editors/p8p_data_source_config_dialog.js b/app/components/editors/p8p_data_source_config_dialog.js new file mode 100644 index 0000000..864a96f --- /dev/null +++ b/app/components/editors/p8p_data_source_config_dialog.js @@ -0,0 +1,185 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Компонент: Диалог настройки источника данных +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useEffect, useContext } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Stack, IconButton, Icon, TextField, InputAdornment, MenuItem, Menu } from "@mui/material"; //Интерфейсные элементы +import { ApplicationСtx } from "../../context/application"; //Контекст приложения +import { TITLES, CAPTIONS } from "../../../app.text"; //Общие текстовые ресурсы +import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки +import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_ARGUMENT_INITIAL, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных" +import { useUserProcDesc } from "./p8p_data_source_hooks"; //Хуки источников данных + +//----------- +//Тело модуля +//----------- + +//Диалог настройки источника данных +const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => { + //Собственное состояние - параметры элемента формы + const [state, setState] = useState({ ...P8P_DATA_SOURCE_INITIAL, ...dataSource }); + + //Собственное состояние - флаги обновление данных + const [refresh, setRefresh] = useState({ userProcDesc: 0 }); + + //Собственное состояние - элемент привязки меню выбора источника + const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState(null); + + //Описание выбранной пользовательской процедуры + const [userProcDesc] = useUserProcDesc({ code: state.userProc, refresh: refresh.userProcDesc }); + + //Подключение к контексту приложения + const { pOnlineShowDictionary } = useContext(ApplicationСtx); + + //Установка значения/привязки аргумента + const setArgumentValueSource = (index, value, valueSource) => + setState(pv => ({ + ...pv, + arguments: pv.arguments.map((argument, i) => ({ ...argument, ...(i == index ? { value, valueSource } : {}) })) + })); + + //Открытие/сокрытие меню выбора источника + const toggleValueProvidersMenu = target => setValueProvidersMenuAnchorEl(target instanceof Element ? target : null); + + //При нажатии на очистку наименования пользовательской процедуры + const handleUserProcClearClick = () => setState({ ...P8P_DATA_SOURCE_INITIAL }); + + //При нажатии на выбор пользовательской процедуры в качестве источника данных + const handleUserProcSelectClick = () => { + pOnlineShowDictionary({ + unitCode: "UserProcedures", + showMethod: "main", + inputParameters: [{ name: "in_CODE", value: state.userProc }], + callBack: res => { + if (res.success) { + setState(pv => ({ ...pv, type: P8P_DATA_SOURCE_TYPE.USER_PROC, userProc: res.outParameters.out_CODE })); + setRefresh(pv => ({ ...pv, userProcDesc: pv.userProcDesc + 1 })); + } + } + }); + }; + + //При закрытии дилога с сохранением + const handleOk = () => onOk && onOk({ ...state }); + + //При закртии диалога отменой + const handleCancel = () => onCancel && onCancel(); + + //При очистке значения/связывания аргумента + const handleArgumentClearClick = index => setArgumentValueSource(index, "", ""); + + //При отображении меню связывания аргумента с поставщиком данных + const handleArgumentLinkMenuClick = e => setValueProvidersMenuAnchorEl(e.currentTarget); + + //При выборе элемента меню связывания аргумента с поставщиком данных + const handleArgumentLinkClick = valueSource => { + setArgumentValueSource(valueProvidersMenuAnchorEl.id, "", valueSource); + toggleValueProvidersMenu(); + }; + + //При вводе значения аргумента + const handleArgumentChange = (index, value) => setArgumentValueSource(index, value, ""); + + //При изменении описания пользовательской процедуры + useEffect(() => { + if (userProcDesc) + setState(pv => ({ + ...pv, + stored: userProcDesc?.stored?.name, + respArg: userProcDesc?.stored?.respArg, + arguments: (userProcDesc?.arguments || []).map(argument => ({ ...P8P_DATA_SOURCE_ARGUMENT_INITIAL, ...argument })) + })); + }, [userProcDesc]); + + //Список значений + const values = Object.keys(valueProviders).reduce((res, key) => [...res, ...Object.keys(valueProviders[key])], []); + + //Наличие значений + const isValues = values && values.length > 0 ? true : false; + + //Меню привязки к поставщикам значений + const valueProvidersMenu = isValues && ( + + {values.map((value, i) => ( + handleArgumentLinkClick(value)}> + {value} + + ))} + + ); + + //Формирование представления + return ( + + + {valueProvidersMenu} + + + clear + + + list + + + ) + }} + /> + {Array.isArray(state?.arguments) && + state.arguments.map((argument, i) => ( + handleArgumentChange(i, e.target.value)} + InputLabelProps={{ shrink: true }} + InputProps={{ + endAdornment: ( + + handleArgumentClearClick(i)}> + clear + + {isValues && ( + + settings_ethernet + + )} + + ) + }} + /> + ))} + + + ); +}; + +//Контроль свойств компонента - Диалог настройки источника данных +P8PDataSourceConfigDialog.propTypes = { + dataSource: P8P_DATA_SOURCE_SHAPE, + valueProviders: PropTypes.object, + onOk: PropTypes.func, + onCancel: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PDataSourceConfigDialog }; diff --git a/app/components/editors/p8p_data_source_hooks.js b/app/components/editors/p8p_data_source_hooks.js new file mode 100644 index 0000000..60c9cf5 --- /dev/null +++ b/app/components/editors/p8p_data_source_hooks.js @@ -0,0 +1,151 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Пользовательские хуки компонента "Источник данных" +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { useState, useContext, useEffect, useRef } from "react"; //Классы React +import client from "../../core/client"; //Клиент взаимодействия с сервером приложений +import { ERRORS } from "../../../app.text"; //Общие текстовые ресурсы +import { formatErrorMessage } from "../../core/utils"; //Общие вспомогательные функции +import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE } from "./p8p_data_source_common"; //Общие ресурсы источника данных + +//----------- +//Тело модуля +//----------- + +//Описание пользовательской процедуры +const useUserProcDesc = ({ code, refresh }) => { + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - данные + const [data, setData] = useState(null); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //При необходимости обновить данные компонента + useEffect(() => { + //Загрузка данных с сервера + const loadData = async () => { + try { + setLoading(true); + const data = await executeStored({ + stored: "PKG_P8PANELS_PE.USERPROCS_DESC", + args: { SCODE: code }, + respArg: "COUT", + isArray: name => name === "arguments", + loader: false + }); + setData(data?.XUSERPROC || null); + } finally { + setLoading(false); + } + }; + //Если надо обновить и есть для чего получать данные + if (refresh > 0) + if (code) loadData(); + else setData(null); + }, [refresh, code, executeStored]); + + //Возвращаем интерфейс хука + return [data, isLoading]; +}; + +//Получение данных из источника +const useDataSource = ({ dataSource, values }) => { + //Контроллер для прерывания запросов + const abortController = useRef(null); + + //Собственное состояние - параметры исполнения + const [state, setState] = useState({ stored: null, storedArgs: [], respArg: null, reqSet: false }); + + //Собственное состояние - флаг загрузки + const [isLoading, setLoading] = useState(false); + + //Собственное состояние - данные + const [data, setData] = useState({ init: false }); + + //Собственное состояние - ошибка получения данных + const [error, setError] = useState(null); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //При необходимости обновить данные + useEffect(() => { + //Загрузка данных с сервера + const loadData = async () => { + try { + setLoading(true); + abortController.current?.abort?.(); + abortController.current = new AbortController(); + const data = await executeStored({ + stored: state.stored, + args: { ...(state.storedArgs ? state.storedArgs : {}) }, + respArg: state.respArg, + loader: false, + signal: abortController.current.signal, + showErrorMessage: false + }); + setError(null); + setData({ ...data, init: true }); + } catch (e) { + if (e.message !== client.ERR_ABORTED) { + setError(formatErrorMessage(e.message).text); + setData({ init: false }); + } + } finally { + setLoading(false); + } + }; + if (state.reqSet) { + if (state.stored) loadData(); + } else setData({ init: false }); + return () => abortController.current?.abort?.(); + }, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored]); + + //При изменении свойств + useEffect(() => { + setState(pv => { + if (dataSource?.type == P8P_DATA_SOURCE_TYPE.USER_PROC) { + const { stored, respArg } = dataSource; + let reqSet = true; + const storedArgs = {}; + dataSource.arguments.forEach(argument => { + let v = argument.valueSource ? values[argument.valueSource] : argument.value; + storedArgs[argument.name] = + argument.dataType == P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE.NUMB + ? isNaN(parseFloat(v)) + ? null + : parseFloat(v) + : argument.dataType == P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE.DATE + ? new Date(v) + : String(v === undefined ? "" : v); + if (argument.req === true && [undefined, null, ""].includes(storedArgs[argument.name])) reqSet = false; + }); + if (pv.stored != stored || pv.respArg != respArg || JSON.stringify(pv.storedArgs) != JSON.stringify(storedArgs)) { + if (!reqSet) { + setError(ERRORS.DATA_SOURCE_NO_REQ_ARGS); + setData({ init: false }); + } + return { stored, respArg, storedArgs, reqSet }; + } else return pv; + } else return pv; + }); + }, [dataSource, values]); + + //Возвращаем интерфейс хука + return [data, error, isLoading]; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { useUserProcDesc, useDataSource }; diff --git a/app/components/editors/p8p_editor_box.js b/app/components/editors/p8p_editor_box.js new file mode 100644 index 0000000..46ffcf6 --- /dev/null +++ b/app/components/editors/p8p_editor_box.js @@ -0,0 +1,54 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Компонент: Контейнер редактора +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, Divider, IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты MUI +import { BUTTONS } from "../../../app.text"; //Общие текстовые ресурсы + +//----------- +//Тело модуля +//----------- + +//Контейнер редактора +const P8PEditorBox = ({ title, children, onSave }) => { + //При нажатии на "Сохранить" + const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor); + + //Формирование представления + return ( + + {title} + + {children} + + + handleSaveClick(false)} title={BUTTONS.APPLY}> + done + + handleSaveClick(true)} title={BUTTONS.SAVE}> + done_all + + + + ); +}; + +//Контроль свойств компонента - Контейнер редактора +P8PEditorBox.propTypes = { + title: PropTypes.string.isRequired, + children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), + onSave: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PEditorBox }; diff --git a/app/components/editors/p8p_editor_sub_header.js b/app/components/editors/p8p_editor_sub_header.js new file mode 100644 index 0000000..8d97d1a --- /dev/null +++ b/app/components/editors/p8p_editor_sub_header.js @@ -0,0 +1,46 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Компонент: Заголовок раздела редактора +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Divider, Chip } from "@mui/material"; //Интерфейсные компоненты MUI + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + DIVIDER: { paddingTop: "20px" } +}; + +//----------- +//Тело модуля +//----------- + +//Заголовок раздела редактора +const P8PEditorSubHeader = ({ title }) => { + //Формирование представления + return ( + + + + ); +}; + +//Контроль свойств компонента - Заголовок раздела редактора +P8PEditorSubHeader.propTypes = { + title: PropTypes.string.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PEditorSubHeader }; diff --git a/app/components/editors/p8p_editor_toolbar.js b/app/components/editors/p8p_editor_toolbar.js new file mode 100644 index 0000000..d68e373 --- /dev/null +++ b/app/components/editors/p8p_editor_toolbar.js @@ -0,0 +1,52 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Компонент: Панель инструментов редактора +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты MUI + +//--------- +//Константы +//--------- + +//Структура элемента панели инструментов редактора +const P8P_EDITOR_TOOL_BAR_ITEM_SHAPE = PropTypes.shape({ + icon: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired +}); + +//----------- +//Тело модуля +//----------- + +//Панель инструментов редактора +const P8PEditorToolBar = ({ items = [] }) => { + //Формирование представления + return ( + + {items.map((item, i) => ( + + {item.icon} + + ))} + + ); +}; + +//Контроль свойств компонента - Панель инструментов редактора +P8PEditorToolBar.propTypes = { + items: PropTypes.arrayOf(P8P_EDITOR_TOOL_BAR_ITEM_SHAPE) +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PEditorToolBar }; diff --git a/app/components/editors/p8p_editors_common.js b/app/components/editors/p8p_editors_common.js new file mode 100644 index 0000000..8911cc2 --- /dev/null +++ b/app/components/editors/p8p_editors_common.js @@ -0,0 +1,30 @@ +/* + Парус 8 - Панели мониторинга - Редакторы панелей + Общие ресурсы редакторов +*/ + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + CHIP: (fullWidth = false, multiLine = false) => ({ + ...(multiLine ? { height: "auto" } : {}), + "& .MuiChip-label": { + ...(multiLine + ? { + display: "block", + whiteSpace: "normal" + } + : {}), + ...(fullWidth ? { width: "100%" } : {}) + } + }) +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { STYLES }; diff --git a/app/panels/panels_editor/component_editor.js b/app/panels/panels_editor/component_editor.js index ce3bc8d..0da26e6 100644 --- a/app/panels/panels_editor/component_editor.js +++ b/app/panels/panels_editor/component_editor.js @@ -11,7 +11,6 @@ import React from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Box, Typography } from "@mui/material"; //Интерфейсные элементы import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов -import "./panels_editor.css"; //Стили редактора //----------- //Тело модуля @@ -27,7 +26,7 @@ const ComponentEditor = ({ id, path, settings = {}, valueProviders = {}, onSetti //Формирование представления return ( - + {haveComponent && init && ( )} diff --git a/app/panels/panels_editor/component_view.js b/app/panels/panels_editor/component_view.js index c914010..2cac128 100644 --- a/app/panels/panels_editor/component_view.js +++ b/app/panels/panels_editor/component_view.js @@ -11,7 +11,6 @@ import React from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Box, Typography } from "@mui/material"; //Интерфейсные элементы import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов -import "./panels_editor.css"; //Стили редактора //----------- //Тело модуля diff --git a/app/panels/panels_editor/components/chart/editor.js b/app/panels/panels_editor/components/chart/editor.js index b6aa7f3..5cf3379 100644 --- a/app/panels/panels_editor/components/chart/editor.js +++ b/app/panels/panels_editor/components/chart/editor.js @@ -9,8 +9,10 @@ import React, { useEffect, useState } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента -import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов -import "../../panels_editor.css"; //Стили редактора +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"; //Источник данных //----------- //Тело модуля @@ -34,17 +36,17 @@ const ChartEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsCha //Формирование представления return ( - - - - + + + + ); }; //Контроль свойств компонента - График (редактор настроек) ChartEditor.propTypes = { id: PropTypes.string.isRequired, - dataSource: DATA_SOURCE_SHAPE, + dataSource: P8P_DATA_SOURCE_SHAPE, valueProviders: PropTypes.object, onSettingsChange: PropTypes.func }; diff --git a/app/panels/panels_editor/components/chart/view.js b/app/panels/panels_editor/components/chart/view.js index 273da15..3a31c68 100644 --- a/app/panels/panels_editor/components/chart/view.js +++ b/app/panels/panels_editor/components/chart/view.js @@ -11,10 +11,13 @@ import React from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Paper } from "@mui/material"; //Интерфейсные элементы import { P8PChart } from "../../../../components/p8p_chart"; //График -import { useComponentDataSource } from "../components_hooks"; //Хуки для данных -import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов -import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений -import "../../panels_editor.css"; //Стили редактора +import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных +import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных +import { + P8P_COMPONENT_INLINE_MESSAGE_TYPE, + P8P_COMPONENT_INLINE_MESSAGE, + P8PComponentInlineMessage +} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента //--------- //Константы @@ -38,7 +41,7 @@ const STYLES = { //График (представление) const Chart = ({ dataSource = null, values = {} } = {}) => { //Собственное состояние - данные - const [data, error] = useComponentDataSource({ dataSource, values }); + const [data, error] = useDataSource({ dataSource, values }); //Флаг настроенности графика const haveConfing = dataSource?.stored ? true : false; @@ -55,11 +58,11 @@ const Chart = ({ dataSource = null, values = {} } = {}) => { {haveConfing && haveData ? ( ) : ( - )} @@ -68,7 +71,7 @@ const Chart = ({ dataSource = null, values = {} } = {}) => { //Контроль свойств компонента - График (представление) Chart.propTypes = { - dataSource: DATA_SOURCE_SHAPE, + dataSource: P8P_DATA_SOURCE_SHAPE, values: PropTypes.object }; diff --git a/app/panels/panels_editor/components/components.js b/app/panels/panels_editor/components/components.js index 5c87a24..a17c301 100644 --- a/app/panels/panels_editor/components/components.js +++ b/app/panels/panels_editor/components/components.js @@ -11,7 +11,7 @@ const COMPONETNS = [ { name: "Форма", path: "form", - settings: { + settings2: { title: "Параметры формирования", autoApply: true, items: [ @@ -75,7 +75,7 @@ const COMPONETNS = [ { name: "Индикатор", path: "indicator", - settings: { + settings2: { dataSource: { type: "USER_PROC", userProc: "ИндКолДогКонтрТип", @@ -95,7 +95,7 @@ const COMPONETNS = [ ] } } - }, + } /*, { name: "Индикатор2", path: "indicator", @@ -119,7 +119,7 @@ const COMPONETNS = [ ] } } - } + }*/ ]; //---------------- diff --git a/app/panels/panels_editor/components/components_hooks.js b/app/panels/panels_editor/components/components_hooks.js index 518cd82..e539f52 100644 --- a/app/panels/panels_editor/components/components_hooks.js +++ b/app/panels/panels_editor/components/components_hooks.js @@ -7,17 +7,13 @@ //Подключение библиотек //--------------------- -import { useState, useContext, useEffect, useRef } from "react"; //Классы React -import client from "../../../core/client"; //Клиент взаимодействия с сервером приложений -import { formatErrorMessage } from "../../../core/utils"; //Общие вспомогательные функции -import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером -import { DATA_SOURCE_TYPE, ARGUMENT_DATA_TYPE } from "./editors_common"; //Общие объекты редакторов +import { useState, useEffect } from "react"; //Классы React //----------- //Тело модуля //----------- -//Загрузка модуля компонента из модуля (можно применять как альтернативу React.lazy) +//Отложенная загрузка модуля компонента (как альтернативу можно применять React.lazy) const useComponentModule = ({ path = null, module = "view" } = {}) => { //Собственное состояние - импортированный модуль компонента const [componentModule, setComponentModule] = useState(null); @@ -41,134 +37,8 @@ const useComponentModule = ({ path = null, module = "view" } = {}) => { return [componentModule, init]; }; -//Описание пользовательской процедуры -const useUserProcDesc = ({ code, refresh }) => { - //Собственное состояние - флаг загрузки - const [isLoading, setLoading] = useState(false); - - //Собственное состояние - данные - const [data, setData] = useState(null); - - //Подключение к контексту взаимодействия с сервером - const { executeStored } = useContext(BackEndСtx); - - //При необходимости обновить данные компонента - useEffect(() => { - //Загрузка данных с сервера - const loadData = async () => { - try { - setLoading(true); - const data = await executeStored({ - stored: "PKG_P8PANELS_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 useComponentDataSource = ({ dataSource, values }) => { - //Контроллер для прерывания запросов - const abortController = useRef(null); - - //Собственное состояние - параметры исполнения - const [state, setState] = useState({ stored: null, storedArgs: [], respArg: null, reqSet: false }); - - //Собственное состояние - флаг загрузки - const [isLoading, setLoading] = useState(false); - - //Собственное состояние - данные - const [data, setData] = useState({ init: false }); - - //Собственное состояние - ошибка получения данных - const [error, setError] = useState(null); - - //Подключение к контексту взаимодействия с сервером - const { executeStored } = useContext(BackEndСtx); - - //При необходимости обновить данные - useEffect(() => { - //Загрузка данных с сервера - const loadData = async () => { - try { - setLoading(true); - abortController.current?.abort?.(); - abortController.current = new AbortController(); - const data = await executeStored({ - stored: state.stored, - args: { ...(state.storedArgs ? state.storedArgs : {}) }, - respArg: state.respArg, - loader: false, - signal: abortController.current.signal, - showErrorMessage: false - }); - setError(null); - setData({ ...data, init: true }); - } catch (e) { - if (e.message !== client.ERR_ABORTED) { - setError(formatErrorMessage(e.message).text); - setData({ init: false }); - } - } finally { - setLoading(false); - } - }; - if (state.reqSet) { - if (state.stored) loadData(); - } else setData({ init: false }); - return () => abortController.current?.abort?.(); - }, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored]); - - //При изменении свойств - useEffect(() => { - setState(pv => { - if (dataSource?.type == DATA_SOURCE_TYPE.USER_PROC) { - const { stored, respArg } = dataSource; - let reqSet = true; - const storedArgs = {}; - dataSource.arguments.forEach(argument => { - let v = argument.valueSource ? values[argument.valueSource] : argument.value; - storedArgs[argument.name] = - argument.dataType == ARGUMENT_DATA_TYPE.NUMB - ? isNaN(parseFloat(v)) - ? null - : parseFloat(v) - : argument.dataType == ARGUMENT_DATA_TYPE.DATE - ? new Date(v) - : String(v === undefined ? "" : v); - if (argument.req === true && [undefined, null, ""].includes(storedArgs[argument.name])) reqSet = false; - }); - if (pv.stored != stored || pv.respArg != respArg || JSON.stringify(pv.storedArgs) != JSON.stringify(storedArgs)) { - if (!reqSet) { - setError("Не заданы обязательные параметры источника данных"); - setData({ init: false }); - } - return { stored, respArg, storedArgs, reqSet }; - } else return pv; - } else return pv; - }); - }, [dataSource, values]); - - //Возвращаем интерфейс хука - return [data, error, isLoading]; -}; - //---------------- //Интерфейс модуля //---------------- -export { useComponentModule, useUserProcDesc, useComponentDataSource }; +export { useComponentModule }; diff --git a/app/panels/panels_editor/components/editors_common.js b/app/panels/panels_editor/components/editors_common.js deleted file mode 100644 index 20bf1d0..0000000 --- a/app/panels/panels_editor/components/editors_common.js +++ /dev/null @@ -1,426 +0,0 @@ -/* - Парус 8 - Панели мониторинга - Редактор панелей - Общие компоненты редакторов свойств -*/ - -//--------------------- -//Подключение библиотек -//--------------------- - -import React, { useState, useContext, useEffect } from "react"; //Классы React -import PropTypes from "prop-types"; //Контроль свойств компонента -import { - Box, - Stack, - IconButton, - Icon, - Typography, - Divider, - Chip, - Button, - TextField, - InputAdornment, - MenuItem, - Menu, - Card, - CardContent, - CardActions, - CardActionArea -} from "@mui/material"; //Интерфейсные элементы -import client from "../../../core/client"; //Клиент БД -import { ApplicationСtx } from "../../../context/application"; //Контекст приложения -import { BUTTONS } from "../../../../app.text"; //Общие текстовые ресурсы -import { P8PDialog } from "../../../components/p8p_dialog"; //Типовой диалог -import { useUserProcDesc } from "./components_hooks"; //Общие хуки компонентов -import "../panels_editor.css"; //Стили редактора - -//--------- -//Константы -//--------- - -//Стили -const STYLES = { - CHIP: (fullWidth = false, multiLine = false) => ({ - ...(multiLine ? { height: "auto" } : {}), - "& .MuiChip-label": { - ...(multiLine - ? { - display: "block", - whiteSpace: "normal" - } - : {}), - ...(fullWidth ? { width: "100%" } : {}) - } - }) -}; - -//Типы даных аргументов -const ARGUMENT_DATA_TYPE = { - STR: client.SERV_DATA_TYPE_STR, - NUMB: client.SERV_DATA_TYPE_NUMB, - DATE: client.SERV_DATA_TYPE_DATE -}; - -//Типы источников данных -const DATA_SOURCE_TYPE = { - USER_PROC: "USER_PROC", - QUERY: "QUERY" -}; - -//Типы источников данных (наименования) -const DATA_SOURCE_TYPE_NAME = { - [DATA_SOURCE_TYPE.USER_PROC]: "Пользовательская процедура", - [DATA_SOURCE_TYPE.QUERY]: "Запрос" -}; - -//Структура аргумента источника данных -const DATA_SOURCE_ARGUMENT_SHAPE = PropTypes.shape({ - name: PropTypes.string.isRequired, - caption: PropTypes.string.isRequired, - dataType: PropTypes.oneOf(Object.values(ARGUMENT_DATA_TYPE)), - req: PropTypes.bool.isRequired, - value: PropTypes.any, - valueSource: PropTypes.string -}); - -//Начальное состояние аргумента источника данных -const DATA_SOURCE_ARGUMENT_INITIAL = { - name: "", - caption: "", - dataType: "", - req: false, - value: "", - valueSource: "" -}; - -//Структура источника данных -const DATA_SOURCE_SHAPE = PropTypes.shape({ - type: PropTypes.oneOf([...Object.values(DATA_SOURCE_TYPE), ""]), - userProc: PropTypes.string, - stored: PropTypes.string, - respArg: PropTypes.string, - arguments: PropTypes.arrayOf(DATA_SOURCE_ARGUMENT_SHAPE) -}); - -//Начальное состояние истоника данных -const DATA_SOURCE_INITIAL = { - type: "", - userProc: "", - stored: "", - respArg: "", - arguments: [] -}; - -//----------- -//Тело модуля -//----------- - -//Контейнер редактора -const EditorBox = ({ title, children, onSave }) => { - //При нажатии на "Сохранить" - const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor); - - //Формирование представления - return ( - - {title} - - {children} - - - handleSaveClick(false)} title={BUTTONS.APPLY}> - done - - handleSaveClick(true)} title={BUTTONS.SAVE}> - done_all - - - - ); -}; - -//Контроль свойств компонента - контейнер редактора -EditorBox.propTypes = { - title: PropTypes.string.isRequired, - children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), - onSave: PropTypes.func -}; - -//Заголовок раздела редактора -const EditorSubHeader = ({ title }) => { - //Формирование представления - return ( - - - - ); -}; - -//Контроль свойств компонента - заголовок раздела редактора -EditorSubHeader.propTypes = { - title: PropTypes.string.isRequired -}; - -//Диалог настройки -const ConfigDialog = ({ title, children, onOk, onCancel }) => { - //Формирование представления - return ( - - {children} - - ); -}; - -//Контроль свойств компонента - диалог настройки -ConfigDialog.propTypes = { - title: PropTypes.string.isRequired, - children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]), - onOk: PropTypes.func, - onCancel: PropTypes.func -}; - -//Диалог настройки источника данных -const ConfigDataSourceDialog = ({ dataSource = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => { - //Собственное состояние - параметры элемента формы - const [state, setState] = useState({ ...DATA_SOURCE_INITIAL, ...dataSource }); - - //Собственное состояние - флаги обновление данных - const [refresh, setRefresh] = useState({ userProcDesc: 0 }); - - //Собственное состояние - элемент привязки меню выбора источника - const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState(null); - - //Описание выбранной пользовательской процедуры - const [userProcDesc] = useUserProcDesc({ code: state.userProc, refresh: refresh.userProcDesc }); - - //Подключение к контексту приложения - const { pOnlineShowDictionary } = useContext(ApplicationСtx); - - //Установка значения/привязки аргумента - const setArgumentValueSource = (index, value, valueSource) => - setState(pv => ({ - ...pv, - arguments: pv.arguments.map((argument, i) => ({ ...argument, ...(i == index ? { value, valueSource } : {}) })) - })); - - //Открытие/сокрытие меню выбора источника - const toggleValueProvidersMenu = target => setValueProvidersMenuAnchorEl(target instanceof Element ? target : null); - - //При нажатии на очистку наименования пользовательской процедуры - const handleUserProcClearClick = () => setState({ ...DATA_SOURCE_INITIAL }); - - //При нажатии на выбор пользовательской процедуры в качестве источника данных - const handleUserProcSelectClick = () => { - pOnlineShowDictionary({ - unitCode: "UserProcedures", - showMethod: "main", - inputParameters: [{ name: "in_CODE", value: state.userProc }], - callBack: res => { - if (res.success) { - setState(pv => ({ ...pv, type: DATA_SOURCE_TYPE.USER_PROC, userProc: res.outParameters.out_CODE })); - setRefresh(pv => ({ ...pv, userProcDesc: pv.userProcDesc + 1 })); - } - } - }); - }; - - //При закрытии дилога с сохранением - const handleOk = () => onOk && onOk({ ...state }); - - //При закртии диалога отменой - const handleCancel = () => onCancel && onCancel(); - - //При очистке значения/связывания аргумента - const handleArgumentClearClick = index => setArgumentValueSource(index, "", ""); - - //При отображении меню связывания аргумента с поставщиком данных - const handleArgumentLinkMenuClick = e => setValueProvidersMenuAnchorEl(e.currentTarget); - - //При выборе элемента меню связывания аргумента с поставщиком данных - const handleArgumentLinkClick = valueSource => { - setArgumentValueSource(valueProvidersMenuAnchorEl.id, "", valueSource); - toggleValueProvidersMenu(); - }; - - //При вводе значения аргумента - const handleArgumentChange = (index, value) => setArgumentValueSource(index, value, ""); - - //При изменении описания пользовательской процедуры - useEffect(() => { - if (userProcDesc) - setState(pv => ({ - ...pv, - stored: userProcDesc?.stored?.name, - respArg: userProcDesc?.stored?.respArg, - arguments: (userProcDesc?.arguments || []).map(argument => ({ ...DATA_SOURCE_ARGUMENT_INITIAL, ...argument })) - })); - }, [userProcDesc]); - - //Список значений - const values = Object.keys(valueProviders).reduce((res, key) => [...res, ...Object.keys(valueProviders[key])], []); - - //Наличие значений - const isValues = values && values.length > 0 ? true : false; - - //Меню привязки к поставщикам значений - const valueProvidersMenu = isValues && ( - - {values.map((value, i) => ( - handleArgumentLinkClick(value)}> - {value} - - ))} - - ); - - //Формирование представления - return ( - - - {valueProvidersMenu} - - - clear - - - list - - - ) - }} - /> - {Array.isArray(state?.arguments) && - state.arguments.map((argument, i) => ( - handleArgumentChange(i, e.target.value)} - InputLabelProps={{ shrink: true }} - InputProps={{ - endAdornment: ( - - handleArgumentClearClick(i)}> - clear - - {isValues && ( - - settings_ethernet - - )} - - ) - }} - /> - ))} - - - ); -}; - -//Контроль свойств компонента - Диалог настройки источника данных -ConfigDataSourceDialog.propTypes = { - dataSource: DATA_SOURCE_SHAPE, - valueProviders: PropTypes.object, - onOk: PropTypes.func, - onCancel: PropTypes.func -}; - -//Источник данных -const DataSource = ({ dataSource = null, valueProviders = {}, onChange = null } = {}) => { - //Собственное состояние - отображение диалога настройки - const [configDlg, setConfigDlg] = useState(false); - - //Уведомление родителя о смене настроек источника данных - const notifyChange = settings => onChange && onChange(settings); - - //При нажатии на настройку источника данных - const handleSetup = () => setConfigDlg(true); - - //При нажатии на настройку источника данных - const handleSetupOk = dataSource => { - setConfigDlg(false); - notifyChange(dataSource); - }; - - //При нажатии на настройку источника данных - const handleSetupCancel = () => setConfigDlg(false); - - //При удалении настроек источника данных - const handleDelete = () => notifyChange({ ...DATA_SOURCE_INITIAL }); - - //Расчет флага "настроенности" - const configured = dataSource?.type ? true : false; - - //Список аргументов - const args = - configured && - dataSource.arguments.map((argument, i) => ( - - )); - - //Формирование представления - return ( - <> - {configDlg && ( - - )} - {configured && ( - - - - - {dataSource.type === DATA_SOURCE_TYPE.USER_PROC ? dataSource.userProc : "Источник без наименования"} - - - {DATA_SOURCE_TYPE_NAME[dataSource.type] || "Неизвестный тип источника"} - - - {args} - - - - - - delete - - - - )} - {!configured && ( - - )} - - ); -}; - -//Контроль свойств компонента - Источник данных -DataSource.propTypes = { - dataSource: DATA_SOURCE_SHAPE, - valueProviders: PropTypes.object, - onChange: PropTypes.func -}; - -//---------------- -//Интерфейс модуля -//---------------- - -export { STYLES, ARGUMENT_DATA_TYPE, DATA_SOURCE_TYPE, DATA_SOURCE_SHAPE, DATA_SOURCE_INITIAL, EditorBox, EditorSubHeader, ConfigDialog, DataSource }; diff --git a/app/panels/panels_editor/components/form/editor.js b/app/panels/panels_editor/components/form/editor.js index 719a82b..4020b6c 100644 --- a/app/panels/panels_editor/components/form/editor.js +++ b/app/panels/panels_editor/components/form/editor.js @@ -27,7 +27,10 @@ import { IconButton } from "@mui/material"; //Интерфейсные элементы import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения -import { STYLES as COMMON_STYLES, EditorBox, EditorSubHeader, ConfigDialog } from "../editors_common"; //Общие компоненты редакторов +import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора +import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора +import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Диалог настройки +import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов import { ITEM_SHAPE, ITEM_INITIAL, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы //--------- @@ -122,7 +125,7 @@ const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => { //Формирование представления return ( - + @@ -172,7 +175,7 @@ const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => { }} /> - + ); }; @@ -241,7 +244,7 @@ const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = f //Формирование представления return ( settings && ( - + {itemEditor.display && ( )} - + Ориентация @@ -268,7 +271,7 @@ const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = f control={} label={"Автоподтверждение"} /> - + {Array.isArray(settings?.items) && settings.items.length > 0 && settings.items.map((item, i) => ( @@ -284,7 +287,7 @@ const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = f - + ) ); }; diff --git a/app/panels/panels_editor/components/form/view.js b/app/panels/panels_editor/components/form/view.js index 77965c2..a24dd1f 100644 --- a/app/panels/panels_editor/components/form/view.js +++ b/app/panels/panels_editor/components/form/view.js @@ -11,9 +11,8 @@ import React, { useEffect, useState, useContext } from "react"; //Классы R import PropTypes from "prop-types"; //Контроль свойств компонента import { Paper, Stack, Typography, Icon, TextField, IconButton, InputAdornment } from "@mui/material"; //Интерфейсные элементы import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения -import { COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений +import { P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента import { ITEM_SHAPE, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы -import "../../panels_editor.css"; //Стили редактора //--------- //Константы @@ -50,6 +49,7 @@ const FormItem = ({ item = null, fullWidth = false, value = "", onChange = null inputParameters: [{ name: item.inputParameter, value }], callBack: res => res.success && onChange && onChange(item.name, res.outParameters[item.outputParameter]) }); + //Формирование представления return ( item && ( @@ -144,7 +144,7 @@ const Form = ({ title = null, orientation = ORIENTATION.V, autoApply = false, it ) : ( - + )} ); diff --git a/app/panels/panels_editor/components/indicator/editor.js b/app/panels/panels_editor/components/indicator/editor.js index 5d8a3f4..81b4666 100644 --- a/app/panels/panels_editor/components/indicator/editor.js +++ b/app/panels/panels_editor/components/indicator/editor.js @@ -9,8 +9,10 @@ import React, { useEffect, useState } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента -import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов -import "../../panels_editor.css"; //Стили редактора +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"; //Источник данных //----------- //Тело модуля @@ -34,17 +36,17 @@ const IndicatorEditor = ({ id, dataSource = null, valueProviders = {}, onSetting //Формирование представления return ( - - - - + + + + ); }; //Контроль свойств компонента - Индикатор (редактор настроек) IndicatorEditor.propTypes = { id: PropTypes.string.isRequired, - dataSource: DATA_SOURCE_SHAPE, + dataSource: P8P_DATA_SOURCE_SHAPE, valueProviders: PropTypes.object, onSettingsChange: PropTypes.func }; diff --git a/app/panels/panels_editor/components/indicator/view.js b/app/panels/panels_editor/components/indicator/view.js index 82495ad..6526a5d 100644 --- a/app/panels/panels_editor/components/indicator/view.js +++ b/app/panels/panels_editor/components/indicator/view.js @@ -11,10 +11,13 @@ import React from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { Paper } from "@mui/material"; //Интерфейсные элементы import { P8PIndicator } from "../../../../components/p8p_indicator"; //Компонент индикатора -import { useComponentDataSource } from "../components_hooks"; //Хуки для данных -import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов -import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений -import "../../panels_editor.css"; //Стили редактора +import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных +import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных +import { + P8P_COMPONENT_INLINE_MESSAGE_TYPE, + P8P_COMPONENT_INLINE_MESSAGE, + P8PComponentInlineMessage +} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента //--------- //Константы @@ -38,7 +41,7 @@ const STYLES = { //Индикатор (представление) const Indicator = ({ dataSource = null, values = {} } = {}) => { //Собственное состояние - данные - const [data, error] = useComponentDataSource({ dataSource, values }); + const [data, error] = useDataSource({ dataSource, values }); //Флаг настроенности индикатора const haveConfing = dataSource?.stored ? true : false; @@ -60,11 +63,11 @@ const Indicator = ({ dataSource = null, values = {} } = {}) => { {haveConfing && haveData ? ( ) : ( - )} @@ -73,7 +76,7 @@ const Indicator = ({ dataSource = null, values = {} } = {}) => { //Контроль свойств компонента - Индикатор (представление) Indicator.propTypes = { - dataSource: DATA_SOURCE_SHAPE, + dataSource: P8P_DATA_SOURCE_SHAPE, values: PropTypes.object }; diff --git a/app/panels/panels_editor/components/table/editor.js b/app/panels/panels_editor/components/table/editor.js index d0dba28..b61a220 100644 --- a/app/panels/panels_editor/components/table/editor.js +++ b/app/panels/panels_editor/components/table/editor.js @@ -9,8 +9,10 @@ import React, { useEffect, useState } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента -import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов -import "../../panels_editor.css"; //Стили редактора +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"; //Источник данных //----------- //Тело модуля @@ -34,17 +36,17 @@ const TableEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsCha //Формирование представления return ( - - - - + + + + ); }; //Контроль свойств компонента - Таблица (редактор настроек) TableEditor.propTypes = { id: PropTypes.string.isRequired, - dataSource: DATA_SOURCE_SHAPE, + dataSource: P8P_DATA_SOURCE_SHAPE, valueProviders: PropTypes.object, onSettingsChange: PropTypes.func }; diff --git a/app/panels/panels_editor/components/table/view.js b/app/panels/panels_editor/components/table/view.js index 4b86d1c..d9b976f 100644 --- a/app/panels/panels_editor/components/table/view.js +++ b/app/panels/panels_editor/components/table/view.js @@ -13,10 +13,13 @@ import { Paper } from "@mui/material"; //Интерфейсные элемент import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили import { P8PDataGrid } from "../../../../components/p8p_data_grid"; //Таблица данных import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../../config_wrapper"; //Подключение компонентов к настройкам приложения -import { useComponentDataSource } from "../components_hooks"; //Хуки для данных -import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов -import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений -import "../../panels_editor.css"; //Стили редактора +import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных +import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных +import { + P8P_COMPONENT_INLINE_MESSAGE_TYPE, + P8P_COMPONENT_INLINE_MESSAGE, + P8PComponentInlineMessage +} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента //--------- //Константы @@ -45,7 +48,7 @@ const STYLES = { //Таблица (представление) const Table = ({ dataSource = null, values = {} } = {}) => { //Собственное состояние - данные - const [data, error] = useComponentDataSource({ dataSource, values }); + const [data, error] = useDataSource({ dataSource, values }); //Флаг настроенности таблицы const haveConfing = dataSource?.stored ? true : false; @@ -72,11 +75,11 @@ const Table = ({ dataSource = null, values = {} } = {}) => { containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 0 }} /> ) : ( - )} @@ -85,7 +88,7 @@ const Table = ({ dataSource = null, values = {} } = {}) => { //Контроль свойств компонента - Таблица (представление) Table.propTypes = { - dataSource: DATA_SOURCE_SHAPE, + dataSource: P8P_DATA_SOURCE_SHAPE, values: PropTypes.object }; diff --git a/app/panels/panels_editor/layout_item.js b/app/panels/panels_editor/layout_item.js index db6f650..f4754d4 100644 --- a/app/panels/panels_editor/layout_item.js +++ b/app/panels/panels_editor/layout_item.js @@ -10,7 +10,6 @@ import React from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные элементы -import "./panels_editor.css"; //Кастомные стили //--------- //Константы diff --git a/app/panels/panels_editor/panels_editor.css b/app/panels/panels_editor/panels_editor.css index 0729e89..8c8c221 100644 --- a/app/panels/panels_editor/panels_editor.css +++ b/app/panels/panels_editor/panels_editor.css @@ -12,17 +12,6 @@ border-radius: 4px; } -.component-editor__wrap { -} - -.component-editor__container { - padding: 10px; -} - -.component-editor__divider { - padding-top: 20px; -} - .component-view__wrap { height: 100%; } diff --git a/app/panels/panels_editor/panels_editor.js b/app/panels/panels_editor/panels_editor.js index 04ce0a3..0af2219 100644 --- a/app/panels/panels_editor/panels_editor.js +++ b/app/panels/panels_editor/panels_editor.js @@ -9,9 +9,10 @@ import React, { useEffect, useState, useContext } from "react"; //Классы React import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет -import { Box, Grid, Stack, Menu, MenuItem, IconButton, Icon, Fab } from "@mui/material"; //Интерфейсные элементы +import { Box, Grid, Menu, MenuItem, Icon, Fab } from "@mui/material"; //Интерфейсные элементы import { ApplicationСtx } from "../../context/application"; //Контекст приложения import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения +import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора import { genGUID } from "../../core/utils"; //Общие вспомогательные функции import { LayoutItem } from "./layout_item"; //Элемент макета import { ComponentView } from "./component_view"; //Представление компонента панели @@ -19,6 +20,7 @@ import { ComponentEditor } from "./component_editor"; //Редактор сво import { COMPONETNS } from "./components/components"; //Описание доступных компонентов import "react-grid-layout/css/styles.css"; //Стили для адаптивного макета import "react-resizable/css/styles.css"; //Стили для адаптивного макета +import "./panels_editor.css"; //Стили редактора панелей //--------- //Константы @@ -140,9 +142,9 @@ const PanelsEditor = () => { //При подключении к странице useEffect(() => { - addComponent(COMPONETNS[0]); - addComponent(COMPONETNS[3]); - addComponent(COMPONETNS[4]); + //addComponent(COMPONETNS[0]); + //addComponent(COMPONETNS[3]); + //addComponent(COMPONETNS[4]); //addComponent(COMPONETNS[1]); //addComponent(COMPONETNS[2]); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -171,14 +173,16 @@ const PanelsEditor = () => { //Панель инструмментов const toolBar = ( - - - play_arrow - - - add - - + ); //Генерация содержимого