From f96425c80d2e6fb18a8113f22f1adad9bb5c7a17 Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Wed, 3 Sep 2025 15:17:26 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-979=20-=20=D0=9D=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B0=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=B0=20-=20=D1=83=D1=81=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BE=D1=82=D0=B1=D0=BE=D1=80=D0=B0=20(=D0=B2?= =?UTF-8?q?=D1=8B=D0=B1=D0=BE=D1=80=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20=D1=83=D1=81=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D0=B9,=20=D0=BE=D1=87=D0=B8=D1=81=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=83=D1=81=D0=BB=D0=BE=D0=B2=D0=B8=D1=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cond_component_select_button.js | 138 ++++++++++++++++++ .../cond_operation_buttons.js | 1 + .../inspector_query_cond.js | 10 +- .../inspector_query_cond/query_cond_dialog.js | 101 +++++++++++-- 4 files changed, 238 insertions(+), 12 deletions(-) create mode 100644 app/panels/query_editor/components/inspector_query_cond/cond_component_select_button.js diff --git a/app/panels/query_editor/components/inspector_query_cond/cond_component_select_button.js b/app/panels/query_editor/components/inspector_query_cond/cond_component_select_button.js new file mode 100644 index 0000000..e6f6822 --- /dev/null +++ b/app/panels/query_editor/components/inspector_query_cond/cond_component_select_button.js @@ -0,0 +1,138 @@ +/* + Парус 8 - Панели мониторинга - Редактор запросов + Компонент: Кнопка выбора компонента условия +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Button, Menu, MenuItem, Icon, ListItemIcon, Stack, Typography } from "@mui/material"; //Интерфейсные компоненты MUI +import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Подзаголовок + +//--------- +//Константы +//--------- + +//Типы компонентов +const COMPONENT_TYPE = { + DIVIDER: "divider", + ITEM: "item" +}; + +//Высота элемента меню для компонента +const ITEM_HEIGHT = 48; + +//Количество одновременно отображаемых элементов (остальные - прокруткой) +const ITEM_DSPL_COUNT = 10; + +//Стили +const STYLES = { + MENU_PAPER: { + maxHeight: ITEM_HEIGHT * ITEM_DSPL_COUNT, + maxWidth: "400px", + overflow: "auto" + } +}; + +//----------------------- +//Вспомогательные функции +//----------------------- + +//Проверка строкового свойства, обязательного для компонента типа "ITEM" +const checkReqStringPropForItem = (props, propName, componentName) => { + if (props.type === COMPONENT_TYPE.ITEM && !props[propName]) { + return new Error(`Prop \`${propName}\` is required when \`type\` is \`${COMPONENT_TYPE.ITEM}\` in \`${componentName}\`.`); + } + if (props[propName] && typeof props[propName] !== "string" && !(props[propName] instanceof String)) { + return new Error(`Prop \`${propName}\` must be a string`); + } + return null; +}; + +//----------- +//Тело модуля +//----------- + +//Кнопка выбора компонента условия +const CondComponentSelectButton = ({ caption, components = [], onSelect }) => { + //Собственное состояние - элемент привязки меню + const [anchorEl, setAnchorEl] = useState(null); + + //При нажатии на кнопку + const handleButtonClick = event => setAnchorEl(event.currentTarget); + + //При закрытии меню + const handleClose = () => setAnchorEl(null); + + //При нажатии на элемент меню + const handleComponentClick = component => { + handleClose(); + onSelect && onSelect(component.value); + }; + + //Расчет флага открытия меню + const open = Boolean(anchorEl); + + //Формирование представления + return ( + <> + + + {components.map((component, i) => + component.type === COMPONENT_TYPE.DIVIDER ? ( + + ) : ( + handleComponentClick(component)}> + + {component.icon} + + + + {component.title} + + + {component.name} + + + + ) + )} + + + ); +}; + +//Контроль свойств - Кнопка выбора компонента условия +CondComponentSelectButton.propTypes = { + caption: PropTypes.string.isRequired, + components: PropTypes.arrayOf( + PropTypes.shape({ + type: PropTypes.oneOf(Object.values(COMPONENT_TYPE)).isRequired, + title: PropTypes.string.isRequired, + name: checkReqStringPropForItem, + icon: checkReqStringPropForItem, + value: PropTypes.any + }) + ), + onSelect: PropTypes.func.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { CondComponentSelectButton, COMPONENT_TYPE }; diff --git a/app/panels/query_editor/components/inspector_query_cond/cond_operation_buttons.js b/app/panels/query_editor/components/inspector_query_cond/cond_operation_buttons.js index 4008995..6fb088e 100644 --- a/app/panels/query_editor/components/inspector_query_cond/cond_operation_buttons.js +++ b/app/panels/query_editor/components/inspector_query_cond/cond_operation_buttons.js @@ -17,6 +17,7 @@ import { ButtonGroup, Button } from "@mui/material"; //Интерфейсные //Кнопки операций условий отбора const CondOperationButtons = ({ buttons = [], onClick }) => { + //Формирование представления return ( {buttons.map((button, i) => ( diff --git a/app/panels/query_editor/components/inspector_query_cond/inspector_query_cond.js b/app/panels/query_editor/components/inspector_query_cond/inspector_query_cond.js index acee7e8..6e35224 100644 --- a/app/panels/query_editor/components/inspector_query_cond/inspector_query_cond.js +++ b/app/panels/query_editor/components/inspector_query_cond/inspector_query_cond.js @@ -14,6 +14,8 @@ import { BUTTONS } from "../../../../../app.text"; //Общие текстовы import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редаторов import { useQueryConditions } from "./hooks"; //Хуки для работы с условиями отбора import { QueryCondDialog } from "./query_cond_dialog"; //Диалог настройки условий отбора +import { ENTITY_SHAPE } from "../entity/entity"; //Описание сущности +import { ARGUMENT_SHAPE } from "../argument/argument"; //Описание аргумента запроса //--------- //Константы @@ -39,7 +41,7 @@ const STYLES = { //----------- //Компонент инспектора - Условия отбора запроса -const InspectorQueryConditions = ({ query, cond, onOptionsChanged }) => { +const InspectorQueryConditions = ({ query, cond, entities = [], args = [], onOptionsChanged }) => { //Собственное состояние - отображение диалога настройки условий отбора const [openQueryCondDialog, setOpenQueryCondDialog] = useState(false); @@ -68,7 +70,9 @@ const InspectorQueryConditions = ({ query, cond, onOptionsChanged }) => { //Формирование представления return ( <> - {openQueryCondDialog && } + {openQueryCondDialog && ( + + )} {configured && ( @@ -93,6 +97,8 @@ const InspectorQueryConditions = ({ query, cond, onOptionsChanged }) => { InspectorQueryConditions.propTypes = { query: PropTypes.number.isRequired, cond: PropTypes.string, + entities: PropTypes.arrayOf(ENTITY_SHAPE), + args: PropTypes.arrayOf(ARGUMENT_SHAPE), onOptionsChanged: PropTypes.func }; diff --git a/app/panels/query_editor/components/inspector_query_cond/query_cond_dialog.js b/app/panels/query_editor/components/inspector_query_cond/query_cond_dialog.js index 8874058..fab290e 100644 --- a/app/panels/query_editor/components/inspector_query_cond/query_cond_dialog.js +++ b/app/panels/query_editor/components/inspector_query_cond/query_cond_dialog.js @@ -7,30 +7,46 @@ //Подключение библиотек //--------------------- -import React, { useState, useRef } from "react"; //Классы React +import React, { useState, useRef, useEffect } from "react"; //Классы React import PropTypes from "prop-types"; //Контроль свойств компонента -import { Stack, TextField } from "@mui/material"; //Интерфейсные компоненты MUI +import { Button, Stack, TextField } from "@mui/material"; //Интерфейсные компоненты MUI import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог +import { DATA_TYPE_ICON } from "../../common"; //Общие ресурсы и константы редактора запросов +import { ENTITY_SHAPE } from "../entity/entity"; //Описание сущности +import { ARGUMENT_SHAPE } from "../argument/argument"; //Описание аргумента запроса import { CondOperationButtons } from "./cond_operation_buttons"; //Кнопки операций условия отбора +import { CondComponentSelectButton, COMPONENT_TYPE } from "./cond_component_select_button"; //Кнопка выбора компонента условия + +//--------- +//Константы +//--------- + +//Иконки +const ICONS = { ...DATA_TYPE_ICON, DEFAULT: "category" }; + +//Стили +const STYLE = { + BUTTONS_STACK: { width: "550px" } +}; //----------- //Тело модуля //----------- //Диалог настройки условий отбора запроса -const QueryCondDialog = ({ cond, onOk, onCancel }) => { +const QueryCondDialog = ({ cond, entities, args, onOk, onCancel }) => { //Собственное состояние - условия отбора const [conditions, setConditions] = useState(cond || ""); //Ссылка на элемент ввода условия - const condInputRef = useRef(null); + const coditionInputRef = useRef(null); //Перемещение курсора в конец поля ввода условия const moveCondCursorToEnd = () => { - if (condInputRef.current) { - const length = condInputRef.current.value.length; - condInputRef.current.setSelectionRange(length, length); - condInputRef.current.focus(); + if (coditionInputRef.current) { + const length = coditionInputRef.current.value.length; + coditionInputRef.current.setSelectionRange(length, length); + coditionInputRef.current.focus(); } }; @@ -43,16 +59,79 @@ const QueryCondDialog = ({ cond, onOk, onCancel }) => { //При изменении условия через компонент const handleChange = e => setConditions(e.target.value); + //При выборе компонента условия + const handleComponentSelected = value => { + setConditions(pv => pv + value); + moveCondCursorToEnd(); + }; + //При нажатии на кнопку операции const handleOperationButtonClick = value => { setConditions(pv => pv + value); moveCondCursorToEnd(); }; + //При нажатии на кнопку очистки условий + const handleClearClick = () => setConditions(""); + + //При подмонтировании компонента + useEffect(() => { + //Перевод курсора в конец обёрнут в setTimeout по тому, что Input подмонтируется позднее диалога (он внутри children) + setTimeout(moveCondCursorToEnd, 100); + }, []); + + //Доступные атрибуты сущностей запроса + const entsAttributes = + entities && entities?.length > 0 + ? entities.reduce( + (components, e) => [ + ...components, + { type: COMPONENT_TYPE.DIVIDER, title: e.title }, + ...e.attrs.map(a => ({ + type: COMPONENT_TYPE.ITEM, + title: a.title, + name: a.name, + value: a.id, + icon: ICONS[a.dataType] || ICONS.DEFAULT + })) + ], + [] + ) + : []; + const entsAttributesExists = entsAttributes.find(a => a.type === COMPONENT_TYPE.ITEM) ? true : false; + + //Доступные аргументы запроса + const queryArguments = + args && args?.length > 0 + ? args.reduce( + (components, a) => [ + ...components, + { + type: COMPONENT_TYPE.ITEM, + title: a.title, + name: a.name, + value: `:${a.name}`, + icon: ICONS[a.dataType] || ICONS.DEFAULT + } + ], + [] + ) + : []; + const queryArgumentsExists = queryArguments.length > 0 ? true : false; + //Генерация содержимого return ( - + + {entsAttributesExists && ( + + )} + {queryArgumentsExists && ( + + )} + + +