forked from CITKParus/P8-Panels
ЦИТК-979 - Настройка запроса - условия отбора (выбор компонентов условий, очистка условия)
This commit is contained in:
parent
7515a9cce3
commit
f96425c80d
@ -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 (
|
||||||
|
<>
|
||||||
|
<Button onClick={handleButtonClick} endIcon={<Icon>{open ? "arrow_drop_up" : "arrow_drop_down"}</Icon>}>
|
||||||
|
{caption}
|
||||||
|
</Button>
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
PaperProps={{
|
||||||
|
style: STYLES.MENU_PAPER,
|
||||||
|
//Импорт общесистемного JS-стиля не работает здесь, только через общесистемный CSS-класс
|
||||||
|
className: "scroll"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{components.map((component, i) =>
|
||||||
|
component.type === COMPONENT_TYPE.DIVIDER ? (
|
||||||
|
<P8PEditorSubHeader key={i} title={component.title} paddingTop={0} />
|
||||||
|
) : (
|
||||||
|
<MenuItem key={i} onClick={() => handleComponentClick(component)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon fontSize={"small"}>{component.icon}</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
<Stack direction={"column"}>
|
||||||
|
<Typography noWrap variant={"body2"} title={component.title}>
|
||||||
|
{component.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography noWrap variant={"caption"} color={"text.secondary"} title={component.name}>
|
||||||
|
{component.name}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Кнопка выбора компонента условия
|
||||||
|
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 };
|
||||||
@ -17,6 +17,7 @@ import { ButtonGroup, Button } from "@mui/material"; //Интерфейсные
|
|||||||
|
|
||||||
//Кнопки операций условий отбора
|
//Кнопки операций условий отбора
|
||||||
const CondOperationButtons = ({ buttons = [], onClick }) => {
|
const CondOperationButtons = ({ buttons = [], onClick }) => {
|
||||||
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<ButtonGroup size={"small"} variant={"outlined"}>
|
<ButtonGroup size={"small"} variant={"outlined"}>
|
||||||
{buttons.map((button, i) => (
|
{buttons.map((button, i) => (
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import { BUTTONS } from "../../../../../app.text"; //Общие текстовы
|
|||||||
import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редаторов
|
import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редаторов
|
||||||
import { useQueryConditions } from "./hooks"; //Хуки для работы с условиями отбора
|
import { useQueryConditions } from "./hooks"; //Хуки для работы с условиями отбора
|
||||||
import { QueryCondDialog } from "./query_cond_dialog"; //Диалог настройки условий отбора
|
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);
|
const [openQueryCondDialog, setOpenQueryCondDialog] = useState(false);
|
||||||
|
|
||||||
@ -68,7 +70,9 @@ const InspectorQueryConditions = ({ query, cond, onOptionsChanged }) => {
|
|||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{openQueryCondDialog && <QueryCondDialog cond={cond} onOk={handleQueryCondDialogOk} onCancel={handleQueryCondDialogCancel} />}
|
{openQueryCondDialog && (
|
||||||
|
<QueryCondDialog cond={cond} entities={entities} args={args} onOk={handleQueryCondDialogOk} onCancel={handleQueryCondDialogCancel} />
|
||||||
|
)}
|
||||||
{configured && (
|
{configured && (
|
||||||
<Card variant={"outlined"}>
|
<Card variant={"outlined"}>
|
||||||
<CardActionArea onClick={handleSetup}>
|
<CardActionArea onClick={handleSetup}>
|
||||||
@ -93,6 +97,8 @@ const InspectorQueryConditions = ({ query, cond, onOptionsChanged }) => {
|
|||||||
InspectorQueryConditions.propTypes = {
|
InspectorQueryConditions.propTypes = {
|
||||||
query: PropTypes.number.isRequired,
|
query: PropTypes.number.isRequired,
|
||||||
cond: PropTypes.string,
|
cond: PropTypes.string,
|
||||||
|
entities: PropTypes.arrayOf(ENTITY_SHAPE),
|
||||||
|
args: PropTypes.arrayOf(ARGUMENT_SHAPE),
|
||||||
onOptionsChanged: PropTypes.func
|
onOptionsChanged: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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 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 { 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 { 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 [conditions, setConditions] = useState(cond || "");
|
||||||
|
|
||||||
//Ссылка на элемент ввода условия
|
//Ссылка на элемент ввода условия
|
||||||
const condInputRef = useRef(null);
|
const coditionInputRef = useRef(null);
|
||||||
|
|
||||||
//Перемещение курсора в конец поля ввода условия
|
//Перемещение курсора в конец поля ввода условия
|
||||||
const moveCondCursorToEnd = () => {
|
const moveCondCursorToEnd = () => {
|
||||||
if (condInputRef.current) {
|
if (coditionInputRef.current) {
|
||||||
const length = condInputRef.current.value.length;
|
const length = coditionInputRef.current.value.length;
|
||||||
condInputRef.current.setSelectionRange(length, length);
|
coditionInputRef.current.setSelectionRange(length, length);
|
||||||
condInputRef.current.focus();
|
coditionInputRef.current.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,16 +59,79 @@ const QueryCondDialog = ({ cond, onOk, onCancel }) => {
|
|||||||
//При изменении условия через компонент
|
//При изменении условия через компонент
|
||||||
const handleChange = e => setConditions(e.target.value);
|
const handleChange = e => setConditions(e.target.value);
|
||||||
|
|
||||||
|
//При выборе компонента условия
|
||||||
|
const handleComponentSelected = value => {
|
||||||
|
setConditions(pv => pv + value);
|
||||||
|
moveCondCursorToEnd();
|
||||||
|
};
|
||||||
|
|
||||||
//При нажатии на кнопку операции
|
//При нажатии на кнопку операции
|
||||||
const handleOperationButtonClick = value => {
|
const handleOperationButtonClick = value => {
|
||||||
setConditions(pv => pv + value);
|
setConditions(pv => pv + value);
|
||||||
moveCondCursorToEnd();
|
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 (
|
return (
|
||||||
<P8PDialog title={`Условия отбора запроса`} onOk={handleOk} onCancel={handleCancel}>
|
<P8PDialog title={`Условия отбора запроса`} onOk={handleOk} onCancel={handleCancel}>
|
||||||
<Stack sx={{ width: "550px" }} direction={"row"} spacing={1} pb={1}>
|
<Stack sx={STYLE.BUTTONS_STACK} direction={"row"} spacing={1} pb={1}>
|
||||||
|
{entsAttributesExists && (
|
||||||
|
<CondComponentSelectButton caption={"Атрибут сущности"} components={entsAttributes} onSelect={handleComponentSelected} />
|
||||||
|
)}
|
||||||
|
{queryArgumentsExists && (
|
||||||
|
<CondComponentSelectButton caption={"Аргумент запроса"} components={queryArguments} onSelect={handleComponentSelected} />
|
||||||
|
)}
|
||||||
|
<Button onClick={handleClearClick}>Очистить</Button>
|
||||||
|
</Stack>
|
||||||
|
<Stack sx={STYLE.BUTTONS_STACK} direction={"row"} spacing={1} pb={1}>
|
||||||
<CondOperationButtons
|
<CondOperationButtons
|
||||||
buttons={[
|
buttons={[
|
||||||
{ caption: "=", title: "Равно", value: " = " },
|
{ caption: "=", title: "Равно", value: " = " },
|
||||||
@ -77,7 +156,7 @@ const QueryCondDialog = ({ cond, onOk, onCancel }) => {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<TextField
|
<TextField
|
||||||
inputRef={condInputRef}
|
inputRef={coditionInputRef}
|
||||||
focused={true}
|
focused={true}
|
||||||
autoFocus
|
autoFocus
|
||||||
value={conditions}
|
value={conditions}
|
||||||
@ -95,6 +174,8 @@ const QueryCondDialog = ({ cond, onOk, onCancel }) => {
|
|||||||
QueryCondDialog.propTypes = {
|
QueryCondDialog.propTypes = {
|
||||||
cond: PropTypes.string,
|
cond: PropTypes.string,
|
||||||
onOk: PropTypes.func,
|
onOk: PropTypes.func,
|
||||||
|
entities: PropTypes.arrayOf(ENTITY_SHAPE),
|
||||||
|
args: PropTypes.arrayOf(ARGUMENT_SHAPE),
|
||||||
onCancel: PropTypes.func
|
onCancel: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user