forked from CITKParus/P8-Panels
Compare commits
29 Commits
fef41f5186
...
676a20dd32
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
676a20dd32 | ||
| 659dfc7c10 | |||
|
|
a46cd28656 | ||
|
|
6a6e58e88e | ||
|
|
0767a12fa6 | ||
|
|
be22cde138 | ||
|
|
dae416cd83 | ||
|
|
c66216e47b | ||
|
|
fbb0db8179 | ||
|
|
3b03f00cc2 | ||
|
|
aa9568c2fa | ||
|
|
5706d59a92 | ||
|
|
ef1a63b4b6 | ||
|
|
538643591f | ||
|
|
f698bc1789 | ||
|
|
f96425c80d | ||
|
|
7515a9cce3 | ||
|
|
86aa639ca0 | ||
|
|
b09ae4d83b | ||
|
|
eef77cbd2d | ||
|
|
38af01c9ef | ||
|
|
abaf9455a9 | ||
|
|
a20de79a40 | ||
|
|
fa00940776 | ||
|
|
4115961742 | ||
|
|
f6b29d5339 | ||
|
|
b3e7d7bf7b | ||
|
|
f9b2f1ae34 | ||
|
|
836a3db5e2 |
@ -15,7 +15,8 @@ export const TITLES = {
|
||||
DEFAULT_PANELS_GROUP: "Без привязки к группе", //Заголовок группы панелей по умолчанию
|
||||
DATA_SOURCE_CONFIG: "Настройка источника данных", //Заголовок для настройки источника данных
|
||||
INSERT: "Добавление", //Заголовок для диалогов/форм добавления
|
||||
UPDATE: "Исправление" //Заголовок для диалогов/форм исправления
|
||||
UPDATE: "Исправление", //Заголовок для диалогов/форм исправления
|
||||
CONFIG: "Настройка" //Заголовок для диалога настройки
|
||||
};
|
||||
|
||||
//Текст
|
||||
|
||||
@ -17,7 +17,8 @@ import { Divider, Chip } from "@mui/material"; //Интерфейсные ком
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIVIDER: { paddingTop: "20px" }
|
||||
DIVIDER: pt => ({ paddingTop: pt || pt === 0 ? `${pt}px` : "20px" }),
|
||||
CHIP: maxWidth => ({ cursor: "default", ...(maxWidth ? { maxWidth } : {}) })
|
||||
};
|
||||
|
||||
//-----------
|
||||
@ -25,18 +26,20 @@ const STYLES = {
|
||||
//-----------
|
||||
|
||||
//Заголовок раздела редактора
|
||||
const P8PEditorSubHeader = ({ title }) => {
|
||||
const P8PEditorSubHeader = ({ title, paddingTop, maxWidth }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<Divider sx={STYLES.DIVIDER}>
|
||||
<Chip label={title} size={"small"} />
|
||||
<Divider sx={STYLES.DIVIDER(paddingTop)}>
|
||||
<Chip label={title} size={"small"} title={title} sx={STYLES.CHIP(maxWidth)} />
|
||||
</Divider>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Заголовок раздела редактора
|
||||
P8PEditorSubHeader.propTypes = {
|
||||
title: PropTypes.string.isRequired
|
||||
title: PropTypes.string.isRequired,
|
||||
paddingTop: PropTypes.number,
|
||||
maxWidth: PropTypes.string
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
||||
@ -13,20 +13,49 @@ import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from "@mui/
|
||||
import { BUTTONS } from "../../app.text"; //Общие текстовые ресурсы
|
||||
import { P8P_INPUT, P8PInput } from "./p8p_input"; //Поле ввода
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Типовая ширина диалога
|
||||
const P8P_DIALOG_WIDTH = {
|
||||
XS: "xs",
|
||||
SM: "sm",
|
||||
MD: "md",
|
||||
LG: "lg",
|
||||
XL: "xl"
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
|
||||
//Формирование объекта вида {ключ: значение} из текущего состояния элементов ввода формы
|
||||
const buildFormValues = inputsState =>
|
||||
inputsState.reduce((res, input) => ({ ...res, [input.name]: input.value == undefined ? null : input.value }), {});
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог
|
||||
const P8PDialog = ({ title, inputs = [], children, onOk, onCancel, onClose }) => {
|
||||
//Состояние диалога
|
||||
const [state, setState] = useState({});
|
||||
const P8PDialog = ({ title, width, fullWidth, inputs, children, onOk, onCancel, onClose, onInputChange }) => {
|
||||
//Состояние элементов ввода диалога
|
||||
const [inputsState, setInputsState] = useState([]);
|
||||
|
||||
//При изменении элемента ввода
|
||||
const handleInputChange = (name, value) => setState(pv => ({ ...pv, [name]: value }));
|
||||
const handleInputChange = (name, value) => {
|
||||
//Если есть функция пересчета формы - вызовем её
|
||||
const doNotChangeInputsState = onInputChange ? onInputChange(name, value, inputsState) : false;
|
||||
//И ориентируясь на то, пересчитала ли она элементы ввода обновим собственное состояние.
|
||||
//Если функция пересчета вернула "true", значит она пересчитала что-то, тогда новые настройки элементов придут через свойство inputs и будут обработаны в useEffect ниже.
|
||||
//Следовательно, и нам здесь не надо состояние выставлять, т.к. всё будет перезаписано useEffectом.
|
||||
if (!doNotChangeInputsState)
|
||||
setInputsState(pv => pv.reduce((accum, cur) => [...accum, { ...cur, value: cur.name === name ? value : cur.value }], []));
|
||||
};
|
||||
|
||||
//При нажатии на "ОК" диалога
|
||||
const handleOk = () => onOk && onOk(state);
|
||||
const handleOk = () => onOk && onOk(buildFormValues(inputsState));
|
||||
|
||||
//При нажатии на "Отмена" диалога
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
@ -34,20 +63,23 @@ const P8PDialog = ({ title, inputs = [], children, onOk, onCancel, onClose }) =>
|
||||
//При нажатии на "Закрыть" диалога
|
||||
const handleClose = () => (onClose ? onClose() : onCancel ? onCancel() : null);
|
||||
|
||||
//При подключении к старнице
|
||||
//При изменении полей для ввода
|
||||
useEffect(() => {
|
||||
setState(inputs.reduce((res, input) => ({ ...res, [input.name]: input.value == undefined ? null : input.value }), {}));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
if (inputs && Array.isArray(inputs) && inputs.length > 0) setInputsState(inputs.map(input => ({ ...input })));
|
||||
}, [inputs]);
|
||||
|
||||
//Расчет объектного представления текущих значений формы
|
||||
const formValues = buildFormValues(inputsState);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Dialog onClose={handleClose} open>
|
||||
<Dialog onClose={handleClose} open {...{ ...(width ? { maxWidth: width } : {}), ...(fullWidth === true ? { fullWidth: true } : {}) }}>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
{inputs.map((input, i) => (
|
||||
<P8PInput key={i} {...input} value={state[input.name]} formValues={state} onChange={handleInputChange} />
|
||||
{inputsState.map((input, i) => (
|
||||
<P8PInput key={i} {...input} formValues={formValues} onChange={handleInputChange} />
|
||||
))}
|
||||
|
||||
{children}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@ -62,15 +94,18 @@ const P8PDialog = ({ title, inputs = [], children, onOk, onCancel, onClose }) =>
|
||||
//Контроль свойств - Диалог
|
||||
P8PDialog.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
width: PropTypes.oneOf(Object.values(P8P_DIALOG_WIDTH)),
|
||||
fullWidth: PropTypes.bool,
|
||||
inputs: PropTypes.arrayOf(PropTypes.shape(P8P_INPUT)),
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onClose: PropTypes.func
|
||||
onClose: PropTypes.func,
|
||||
onInputChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PDialog };
|
||||
export { P8PDialog, P8P_DIALOG_WIDTH };
|
||||
|
||||
@ -35,26 +35,24 @@ const P8P_INPUT = {
|
||||
|
||||
//Поле ввода
|
||||
const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSolo = false, disabled = false, formValues, ...other }) => {
|
||||
//Значение элемента
|
||||
const [currentValue, setCurrentValue] = useState(value);
|
||||
//Значение и тип элемента
|
||||
const [current, setCurrent] = useState({ type: undefined, value: "" });
|
||||
|
||||
//При получении нового значения из вне
|
||||
useEffect(() => {
|
||||
setCurrentValue(value);
|
||||
}, [value]);
|
||||
//При получении нового значения или типа из вне
|
||||
useEffect(() => setCurrent({ value, type }), [type, value]);
|
||||
|
||||
//Выбор значения из словаря
|
||||
const handleDictionaryClick = () => dictionary && dictionary(formValues, res => (res ? res.map(i => handleChangeByName(i.name, i.value)) : null));
|
||||
|
||||
//Изменение значения элемента (по событию)
|
||||
const handleChange = e => {
|
||||
setCurrentValue(e.target.value);
|
||||
setCurrent(pv => ({ ...pv, value: e.target.value }));
|
||||
if (onChange) onChange(e.target.name, e.target.value);
|
||||
};
|
||||
|
||||
//Изменение значения элемента (по имени и значению)
|
||||
const handleChangeByName = (targetName, value) => {
|
||||
if (targetName === name) setCurrentValue(value);
|
||||
if (targetName === name) setCurrent(pv => ({ ...pv, value }));
|
||||
if (onChange) onChange(targetName, value);
|
||||
};
|
||||
|
||||
@ -69,7 +67,7 @@ const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSo
|
||||
name={name}
|
||||
freeSolo
|
||||
disabled={disabled}
|
||||
inputValue={currentValue ? currentValue : ""}
|
||||
inputValue={current.value ? current.value : ""}
|
||||
onChange={(event, newValue) => handleChangeByName(name, newValue)}
|
||||
onInputChange={(event, newInputValue) => handleChangeByName(name, newInputValue)}
|
||||
options={list}
|
||||
@ -85,7 +83,7 @@ const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSo
|
||||
id={name}
|
||||
name={name}
|
||||
label={label}
|
||||
value={[undefined, null].includes(currentValue) ? "" : currentValue}
|
||||
value={[undefined, null].includes(current.value) ? "" : current.value}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
displayEmpty
|
||||
@ -100,13 +98,13 @@ const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSo
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<InputLabel {...(type == "date" ? { shrink: true } : {})} htmlFor={name}>
|
||||
<InputLabel {...(current.type == "date" ? { shrink: true } : {})} htmlFor={name}>
|
||||
{label}
|
||||
</InputLabel>
|
||||
<Input
|
||||
id={name}
|
||||
name={name}
|
||||
value={currentValue ? currentValue : ""}
|
||||
value={current.value ? current.value : ""}
|
||||
endAdornment={
|
||||
dictionary ? (
|
||||
<InputAdornment position="end">
|
||||
@ -116,7 +114,7 @@ const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSo
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}
|
||||
{...(type ? { type } : {})}
|
||||
{...(current.type ? { type: current.type } : {})}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
@ -105,11 +105,14 @@ const getDisplaySize = () => {
|
||||
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj)));
|
||||
|
||||
//Конвертация объекта в Base64 XML
|
||||
const object2Base64XML = (obj, builderOptions) => {
|
||||
const object2XML = (obj, builderOptions) => {
|
||||
const builder = new XMLBuilder(builderOptions);
|
||||
return btoa(unescape(encodeURIComponent(builder.build(obj))));
|
||||
return builder.build(obj);
|
||||
};
|
||||
|
||||
//Конвертация объекта в Base64 XML
|
||||
const object2Base64XML = (obj, builderOptions) => btoa(unescape(encodeURIComponent(object2XML(obj, builderOptions))));
|
||||
|
||||
//Конвертация XML в JSON
|
||||
const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attributeValueProcessor, useDefaultPatterns = true }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -200,6 +203,7 @@ export {
|
||||
hasValue,
|
||||
getDisplaySize,
|
||||
deepCopyObject,
|
||||
object2XML,
|
||||
object2Base64XML,
|
||||
xml2JSON,
|
||||
formatDateRF,
|
||||
|
||||
@ -10,14 +10,33 @@
|
||||
//Типы данных
|
||||
const DATA_TYPE = { STR: 0, NUMB: 1, DATE: 2 };
|
||||
|
||||
//Иконки типов данных
|
||||
const DATA_TYPE_ICON = {
|
||||
[DATA_TYPE.STR]: "format_align_left",
|
||||
[DATA_TYPE.NUMB]: "pin",
|
||||
[DATA_TYPE.DATE]: "calendar_month"
|
||||
};
|
||||
|
||||
//Типы элементов диаграммы
|
||||
const NODE_TYPE = {
|
||||
ENTITY: "entity",
|
||||
ATTRIBUTE: "attribute"
|
||||
};
|
||||
|
||||
//Типы сущностей
|
||||
const ENTITY_TYPE = {
|
||||
VIEW: "VIEW",
|
||||
TABLE: "TABLE"
|
||||
};
|
||||
|
||||
//Иконки типов сущностей
|
||||
const ENTITY_TYPE_ICON = {
|
||||
[ENTITY_TYPE.VIEW]: "table_view",
|
||||
[ENTITY_TYPE.TABLE]: "table_rows"
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { DATA_TYPE, NODE_TYPE };
|
||||
export { DATA_TYPE, DATA_TYPE_ICON, NODE_TYPE, ENTITY_TYPE, ENTITY_TYPE_ICON };
|
||||
|
||||
89
app/panels/query_editor/components/argument/argument.js
Normal file
89
app/panels/query_editor/components/argument/argument.js
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компоненты: Аргумент запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, ListItem, IconButton, Icon, ListItemButton, ListItemText, Typography, ListItemIcon, Chip } from "@mui/material"; //Компоненты UI
|
||||
import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редаторов
|
||||
import { DATA_TYPE, DATA_TYPE_ICON } from "../../common"; //Общие ресурсы и константы редактора запросов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Варианты представления
|
||||
const ARGUMENT_VARIANT = {
|
||||
LIST_ITEM: "LIST_ITEM",
|
||||
CHIP: "CHIP"
|
||||
};
|
||||
|
||||
//Структура аргумента
|
||||
const ARGUMENT_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
dataType: PropTypes.oneOf(Object.values(DATA_TYPE)),
|
||||
mandatory: PropTypes.oneOf([0, 1]).isRequired
|
||||
});
|
||||
|
||||
//Иконки
|
||||
const ICONS = { ...DATA_TYPE_ICON, DEFAULT: "category" };
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Аргумент запроса
|
||||
const Argument = ({ arg, variant, onClick = null, onDelete = null }) => {
|
||||
//Заголовок аргумента
|
||||
const title = `${arg.mandatory == 1 ? "*" : ""}${arg.title}`;
|
||||
|
||||
//Иконка аргумента
|
||||
const icon = ICONS[arg.dataType] || ICONS.DEFAULT;
|
||||
|
||||
//Формирование представления
|
||||
return variant == ARGUMENT_VARIANT.LIST_ITEM ? (
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton onClick={() => onClick && onClick(arg)} dense>
|
||||
<ListItemIcon>
|
||||
<Icon>{icon}</Icon>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={title}
|
||||
secondaryTypographyProps={{ component: "div" }}
|
||||
secondary={
|
||||
<Stack direction={"column"}>
|
||||
<Typography variant={"caption"}>{arg.name}</Typography>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Stack direction={"row"}>
|
||||
<IconButton onClick={e => onDelete && onDelete(e, arg)} title={"Удалить"}>
|
||||
<Icon>delete</Icon>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
) : variant == ARGUMENT_VARIANT.CHIP ? (
|
||||
<Chip icon={<Icon>{icon}</Icon>} label={title} variant={"outlined"} sx={COMMON_STYLES.CHIP(true)} />
|
||||
) : null;
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Аргумент запроса
|
||||
Argument.propTypes = {
|
||||
arg: ARGUMENT_SHAPE,
|
||||
variant: PropTypes.oneOf(Object.values(ARGUMENT_VARIANT)).isRequired,
|
||||
onClick: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { Argument, ARGUMENT_VARIANT, ARGUMENT_SHAPE };
|
||||
@ -11,7 +11,7 @@ import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Handle, Position, useStore } from "reactflow"; //Библиотека редактора диаграмм
|
||||
import { Box, Stack, Icon, Typography } from "@mui/material"; //Компоненты UI
|
||||
import { DATA_TYPE } from "../../common"; //Общие ресурсы и константы редактора
|
||||
import { DATA_TYPE, DATA_TYPE_ICON } from "../../common"; //Общие ресурсы и константы редактора запросов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -19,11 +19,12 @@ import { DATA_TYPE } from "../../common"; //Общие ресурсы и кон
|
||||
|
||||
//Типовые цвета точек привязки
|
||||
const HANDLE_BORDER_COLOR = "#69db7c";
|
||||
const HANDLE_BORDER_COLOR_INVALID = "#ff0000";
|
||||
const HANDLE_BORDER_COLOR_DISABLED = "#adb5bd";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex", width: "100%", height: "100%" },
|
||||
CONTAINER: { display: "flex", width: "100%", height: "100%", cursor: "default" },
|
||||
HANDLE_SOURCE: isConnecting => ({
|
||||
width: 14,
|
||||
height: 14,
|
||||
@ -32,34 +33,55 @@ const STYLES = {
|
||||
borderRadius: 7,
|
||||
background: "white"
|
||||
}),
|
||||
HANDLE_TARGET: isConnecting => ({
|
||||
HANDLE_TARGET: (isConnecting, isValidConnection) => ({
|
||||
width: isConnecting ? 14 : 0,
|
||||
height: 14,
|
||||
left: isConnecting ? -7 : 0,
|
||||
border: `2px solid ${HANDLE_BORDER_COLOR}`,
|
||||
border: `2px solid ${isValidConnection ? HANDLE_BORDER_COLOR : HANDLE_BORDER_COLOR_INVALID}`,
|
||||
borderRadius: 7,
|
||||
background: "white",
|
||||
visibility: isConnecting ? "visible" : "hidden"
|
||||
}),
|
||||
CONTENT_STACK: { width: "100%" },
|
||||
TITLE_NAME_STACK: { width: "100%", containerType: "inline-size" }
|
||||
TITLE_NAME_STACK: { width: "100%", containerType: "inline-size" },
|
||||
ATTR_PROP_ICON: { fontSize: "0.9rem" }
|
||||
};
|
||||
|
||||
//Иконки
|
||||
const ICONS = {
|
||||
[DATA_TYPE.STR]: "format_align_left",
|
||||
[DATA_TYPE.NUMB]: "pin",
|
||||
[DATA_TYPE.DATE]: "calendar_month",
|
||||
DEFAULT: "category"
|
||||
};
|
||||
const ICONS = { ...DATA_TYPE_ICON, DEFAULT: "category" };
|
||||
|
||||
//Структура данных об атрибуте сущности
|
||||
const ATTRIBUTE_DATA_SHAPE = PropTypes.shape({
|
||||
const ATTRIBUTE_SHAPE = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
parentEntity: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
dataType: PropTypes.number.isRequired
|
||||
dataType: PropTypes.oneOf(Object.values(DATA_TYPE)),
|
||||
agg: PropTypes.string,
|
||||
alias: PropTypes.string,
|
||||
use: PropTypes.oneOf([0, 1]).isRequired,
|
||||
show: PropTypes.oneOf([0, 1]).isRequired
|
||||
});
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
|
||||
//Получение атрибутики состояния включения атрибута в запрос
|
||||
const attrGetUse = (attr, callToAction = false) => {
|
||||
return [attr.use === 1, `${attr.use === 1 ? "Включен в запрос" : "Не включен в запрос"}${callToAction ? "- нажмите, чтобы изменить" : ""}`];
|
||||
};
|
||||
|
||||
//Получение атрибутики состояния отображения атрибута в результатах запроса
|
||||
const attrGetShow = (attr, callToAction = false) => {
|
||||
return [
|
||||
`${attr.show == 1 ? "Отображается в результатах запроса" : "Не отображается в результатах запроса"}${
|
||||
callToAction ? "- нажмите, чтобы изменить" : ""
|
||||
}`,
|
||||
attr.show == 1 ? "visibility" : "visibility_off"
|
||||
];
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
@ -67,25 +89,45 @@ const ATTRIBUTE_DATA_SHAPE = PropTypes.shape({
|
||||
//Атрибут сущности
|
||||
const Attribute = ({ data }) => {
|
||||
//Поиск идентификатора соединяемого элемента
|
||||
const connectionNodeId = useStore(state => state.connectionNodeId);
|
||||
const [connectionNodeId, targetConnectionNode, connectionStatus] = useStore(state => [
|
||||
state.connectionNodeId,
|
||||
state?.connectionEndHandle?.nodeId,
|
||||
state.connectionStatus
|
||||
]);
|
||||
|
||||
//Флаг выполнения соединения сущностей
|
||||
const isConnecting = Boolean(connectionNodeId);
|
||||
|
||||
//Флаг корректности соединения сущностей
|
||||
const isValidConnection = !(data.id == targetConnectionNode && connectionStatus == "invalid");
|
||||
|
||||
//Получим атрибуты состояния отображения
|
||||
const [showTitle, showIcon] = attrGetShow(data);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box p={1} sx={STYLES.CONTAINER}>
|
||||
<Handle type={"source"} position={Position.Right} style={STYLES.HANDLE_SOURCE(isConnecting)} />
|
||||
<Handle type={"target"} position={Position.Left} isConnectableStart={false} style={STYLES.HANDLE_TARGET(isConnecting)} />
|
||||
<Handle
|
||||
type={"target"}
|
||||
position={Position.Left}
|
||||
isConnectableStart={false}
|
||||
style={STYLES.HANDLE_TARGET(isConnecting, isValidConnection)}
|
||||
/>
|
||||
<Stack direction={"row"} alignItems={"center"} spacing={1} sx={STYLES.CONTENT_STACK}>
|
||||
<Icon color={"action"}>{ICONS[data.dataType] || ICONS.DEFAULT}</Icon>
|
||||
<Stack direction={"column"} alignItems={"left"} sx={STYLES.TITLE_NAME_STACK}>
|
||||
<Typography variant={"body2"} noWrap title={data.title}>
|
||||
{data.title}
|
||||
</Typography>
|
||||
<Typography variant={"caption"} color={"text.secondary"} noWrap title={data.name}>
|
||||
{data.name}
|
||||
</Typography>
|
||||
<Stack direction={"row"} alignItems={"center"} spacing={0.5}>
|
||||
<Typography component={"div"} variant={"caption"} color={"text.secondary"} noWrap title={data.name}>
|
||||
{`${data.name},`}
|
||||
</Typography>
|
||||
<Icon color={"action"} sx={STYLES.ATTR_PROP_ICON} title={showTitle}>
|
||||
{showIcon}
|
||||
</Icon>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
@ -94,11 +136,11 @@ const Attribute = ({ data }) => {
|
||||
|
||||
//Контроль свойств компонента - Атрибут сущности
|
||||
Attribute.propTypes = {
|
||||
data: ATTRIBUTE_DATA_SHAPE
|
||||
data: ATTRIBUTE_SHAPE
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { Attribute };
|
||||
export { Attribute, ATTRIBUTE_SHAPE, attrGetUse, attrGetShow };
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
.entity__wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid var(--border-color-dark);
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--shadow-entity);
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.entity__wrapper[data-selected="true"] {
|
||||
outline: 1px solid var(--outline-color);
|
||||
border-color: var(--outline-color);
|
||||
}
|
||||
|
||||
.entity__title {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
align-content: center;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
background-color: var(--entity-title-bg);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.entity__name {
|
||||
width: 100%;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
color: gray;
|
||||
}
|
||||
@ -9,42 +9,136 @@
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import "./entity.css"; //Стили компомнента
|
||||
import { Box, Stack, ListItem, Icon, ListItemButton, Typography } from "@mui/material"; //Компоненты UI
|
||||
import { ENTITY_TYPE, ENTITY_TYPE_ICON } from "../../common"; //Общие ресурсы и константы редактора запросов
|
||||
import { ATTRIBUTE_SHAPE } from "../attribute/attribute"; //Описание атрибута сущности
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Варианты представления сущности
|
||||
const ENTITY_VARIANT = {
|
||||
LIST_ITEM: "LIST_ITEM",
|
||||
DIAGRAM: "DIAGRAM"
|
||||
};
|
||||
|
||||
//Иконки
|
||||
const ICONS = { ...ENTITY_TYPE_ICON, DEFAULT: "border_clear" };
|
||||
|
||||
//Структура данных о сущности запроса
|
||||
const ENTITY_DATA_SHAPE = PropTypes.shape({
|
||||
const ENTITY_SHAPE = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
title: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf(Object.values(ENTITY_TYPE)).isRequired,
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired,
|
||||
attrs: PropTypes.arrayOf(ATTRIBUTE_SHAPE).isRequired
|
||||
});
|
||||
|
||||
//Структура данных о сущности запроса (краткая)
|
||||
const ENTITY_BRIEF_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf(Object.values(ENTITY_TYPE)).isRequired
|
||||
});
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: selected => ({
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
border: "1px solid var(--border-color-dark)",
|
||||
borderRadius: "6px",
|
||||
boxShadow: "var(--shadow-entity)",
|
||||
overflow: "hidden",
|
||||
backgroundColor: "white",
|
||||
cursor: "move",
|
||||
...(selected
|
||||
? {
|
||||
outline: "1px solid var(--outline-color)",
|
||||
borderColor: "var(--outline-color)"
|
||||
}
|
||||
: {})
|
||||
}),
|
||||
CONTENT_STACK: variant => ({
|
||||
width: "100%",
|
||||
backgroundColor: "var(--entity-title-bg)",
|
||||
...(variant === ENTITY_VARIANT.DIAGRAM ? { height: "50px" } : {})
|
||||
}),
|
||||
CAPTIONS_TYPOGRAPHY: variant => ({ ...(variant === ENTITY_VARIANT.DIAGRAM ? { maxWidth: "100px", overflow: "hidden" } : {}) })
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Сущность запроса
|
||||
const Entity = ({ data, selected }) => {
|
||||
return (
|
||||
<div className="entity__wrapper" data-selected={selected}>
|
||||
<div className="entity__title">
|
||||
<span>{data.title}</span>
|
||||
<div className="entity__name">{data.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
const Entity = ({ data, variant = ENTITY_VARIANT.DIAGRAM, selected = false, onClick = null }) => {
|
||||
//Иконка
|
||||
const icon = ICONS[data.type] || ICONS.DEFAULT;
|
||||
|
||||
//Всплывающая подсказка
|
||||
const iconTitle = data.type === ENTITY_TYPE.VIEW ? "Представление" : "Таблица";
|
||||
|
||||
//Содержимое самой сущности
|
||||
const entContent = (
|
||||
<Stack
|
||||
direction={"row"}
|
||||
alignItems={"center"}
|
||||
justifyContent={variant === ENTITY_VARIANT.DIAGRAM ? "center" : "left"}
|
||||
p={1}
|
||||
sx={STYLES.CONTENT_STACK(variant)}
|
||||
>
|
||||
<Icon color={"action"} title={iconTitle}>
|
||||
{icon}
|
||||
</Icon>
|
||||
<Stack direction={"column"} alignItems={"left"} pl={1}>
|
||||
<Typography
|
||||
variant={variant === ENTITY_VARIANT.DIAGRAM ? "subtitle2" : "body2"}
|
||||
noWrap={variant === ENTITY_VARIANT.DIAGRAM ? true : false}
|
||||
title={data.title}
|
||||
sx={STYLES.CAPTIONS_TYPOGRAPHY(variant)}
|
||||
>
|
||||
{data.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
component={"div"}
|
||||
variant={"caption"}
|
||||
color={"text.secondary"}
|
||||
noWrap={variant === ENTITY_VARIANT.DIAGRAM ? true : false}
|
||||
title={data.name}
|
||||
sx={STYLES.CAPTIONS_TYPOGRAPHY(variant)}
|
||||
>
|
||||
{data.name}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
//Формирование представления
|
||||
return variant == ENTITY_VARIANT.LIST_ITEM ? (
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton onClick={() => onClick && onClick(data)} dense>
|
||||
{entContent}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
) : variant == ENTITY_VARIANT.DIAGRAM ? (
|
||||
<Box sx={STYLES.CONTAINER(selected)}>{entContent}</Box>
|
||||
) : null;
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Сущность запроса
|
||||
Entity.propTypes = {
|
||||
data: ENTITY_DATA_SHAPE,
|
||||
selected: PropTypes.bool.isRequired
|
||||
data: PropTypes.oneOfType([ENTITY_SHAPE, ENTITY_BRIEF_SHAPE]).isRequired,
|
||||
variant: PropTypes.string,
|
||||
selected: PropTypes.bool,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { Entity };
|
||||
export { Entity, ENTITY_VARIANT, ENTITY_SHAPE, ENTITY_BRIEF_SHAPE };
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Диалог добавления сущности запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог добавления сущности запроса
|
||||
const EntityAddDialog = ({ onOk, onCancel }) => {
|
||||
//Нажатие на кнопку "Ok"
|
||||
const handleOk = values => onOk && onOk({ ...values });
|
||||
|
||||
//Нажатие на кнопку "Отмена"
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PDialog title={`${TITLES.INSERT} сущности`} inputs={[{ name: "name", value: "", label: "Имя" }]} onOk={handleOk} onCancel={handleCancel} />
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог добавления сущности запроса
|
||||
EntityAddDialog.propTypes = {
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { EntityAddDialog };
|
||||
69
app/panels/query_editor/components/inspector/inspector.js
Normal file
69
app/panels/query_editor/components/inspector/inspector.js
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Инспектор свойств
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер параметров редактора
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора
|
||||
import { ENTITY_SHAPE } from "../entity/entity"; //Описание сущности
|
||||
import { RELATION_SHAPE } from "../relation/relation"; //Описание связи
|
||||
import { ARGUMENT_SHAPE } from "../argument/argument"; //Описание аргумента запроса
|
||||
import { InspectorQueryArguments } from "../inspector_query_args/inspector_query_args"; //Управление аргументами запроса
|
||||
import { InspectorQueryConditions } from "../inspector_query_cond/inspector_query_cond"; //Управление условиями отбора запроса
|
||||
import { InspectorQueryEntities } from "../inspector_query_ents/inspector_query_ents"; //Управление сущностями запроса
|
||||
import { InspectorQueryRelations } from "../inspector_query_rls/inspector_query_rls"; //Управление связями запроса
|
||||
import { QueryArea } from "./query_area"; //Область запроса
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Инспектор свойств
|
||||
const Inspector = ({ query, entity, relation, entities = [], args = [], cond = null, qry = "", qryMsg = "", onOptionsChanged = null }) => {
|
||||
//При изменении настроек запроса
|
||||
const handleOptionsChanged = () => onOptionsChanged && onOptionsChanged();
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PEditorBox title={"Настройки запроса"}>
|
||||
<P8PEditorSubHeader title={"Аргументы"} />
|
||||
<InspectorQueryArguments query={query} args={args} onOptionsChanged={handleOptionsChanged} />
|
||||
<P8PEditorSubHeader title={"Условия отбора"} />
|
||||
<InspectorQueryConditions query={query} cond={cond} entities={entities} args={args} onOptionsChanged={handleOptionsChanged} />
|
||||
<P8PEditorSubHeader title={"Сущности"} />
|
||||
<InspectorQueryEntities query={query} entity={entity} onOptionsChanged={handleOptionsChanged} />
|
||||
{relation && (
|
||||
<>
|
||||
<P8PEditorSubHeader title={"Связь"} />
|
||||
<InspectorQueryRelations query={query} relation={relation} onOptionsChanged={handleOptionsChanged} />
|
||||
</>
|
||||
)}
|
||||
<QueryArea qry={qry} qryMsg={qryMsg} />
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Инспектор свойств
|
||||
Inspector.propTypes = {
|
||||
query: PropTypes.number.isRequired,
|
||||
entity: ENTITY_SHAPE,
|
||||
relation: RELATION_SHAPE,
|
||||
entities: PropTypes.arrayOf(ENTITY_SHAPE),
|
||||
args: PropTypes.arrayOf(ARGUMENT_SHAPE),
|
||||
cond: PropTypes.string,
|
||||
qry: PropTypes.string,
|
||||
qryMsg: PropTypes.string,
|
||||
onOptionsChanged: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { Inspector };
|
||||
152
app/panels/query_editor/components/inspector/query_area.js
Normal file
152
app/panels/query_editor/components/inspector/query_area.js
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Область запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Fab, Icon, Drawer, IconButton, TextField, Stack, Box, Snackbar, Alert } from "@mui/material"; //Компоненты MUI
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
SQL_FAB: {
|
||||
position: "absolute",
|
||||
bottom: 16,
|
||||
right: 16
|
||||
},
|
||||
SQL_TEXT_FIELD: {
|
||||
fontSize: "0.9rem",
|
||||
...APP_STYLES.SCROLL
|
||||
},
|
||||
SNACKBAR_ALERT: { width: "100%" }
|
||||
};
|
||||
|
||||
//Начальное состояние всплывающего сообщения
|
||||
const SNACK_BAR_MESSAGE_INIT = { text: null, type: null };
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Область запроса
|
||||
const QueryArea = ({ qry = "", qryMsg = "" }) => {
|
||||
//Собственное состояние - текст всплывающего сообщения
|
||||
const [snackBarMessage, setSnackBarMessage] = useState(SNACK_BAR_MESSAGE_INIT);
|
||||
|
||||
//Собственное состояние - отображение области SQL запроса
|
||||
const [displaySQL, setDisplaySQL] = useState(true);
|
||||
|
||||
//Собственное состояние - развёрнутость
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
//При нажатии на кнопку копирования текста запроса
|
||||
const handleCopyClick = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(qry);
|
||||
setSnackBarMessage({ text: `Текст запроса скопирован в буфер обмена` });
|
||||
} catch (e) {
|
||||
setSnackBarMessage({ text: `Ошибка копирования текста запроса в буфер обмена: ${e.message}`, type: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
//При нажатии на кнопку развёртывания
|
||||
const handleExpandClick = () => setExpanded(!expanded);
|
||||
|
||||
//При нажатии на кнопку отображения SQL запроса
|
||||
const handleShowSQLClick = () => setDisplaySQL(true);
|
||||
|
||||
//При нажатии на кнопку сокрытия SQL запроса
|
||||
const handleCloseSQLClick = () => setDisplaySQL(false);
|
||||
|
||||
//При закрытии всплывающего сообщения
|
||||
const handleSnackBarClose = () => setSnackBarMessage(SNACK_BAR_MESSAGE_INIT);
|
||||
|
||||
//Расчет размеров тектовых полей
|
||||
const [qryRows, qryMsgRows] = expanded ? [15, 6] : [5, 3];
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<>
|
||||
{(qry || qryMsg) && (
|
||||
<Fab color={qryMsg ? "warning" : "default"} sx={STYLES.SQL_FAB} title={"Показать текст SQL запроса"} onClick={handleShowSQLClick}>
|
||||
<Icon>join_left</Icon>
|
||||
</Fab>
|
||||
)}
|
||||
{displaySQL && (
|
||||
<Drawer open onClose={handleCloseSQLClick} anchor={"bottom"}>
|
||||
<Box p={2}>
|
||||
<Stack direction={"row"} justifyContent={"right"} spacing={2}>
|
||||
{qry && (
|
||||
<IconButton onClick={handleCopyClick} title={"Скопировать текст запроса"}>
|
||||
<Icon>content_copy</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton onClick={handleExpandClick} title={expanded ? "Свернуть" : "Развернуть"}>
|
||||
<Icon>{expanded ? "expand_more" : "expand_less"}</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleCloseSQLClick} title={BUTTONS.HIDE}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Stack direction={"column"} spacing={2}>
|
||||
{qry && (
|
||||
<TextField
|
||||
label={"Текст запроса"}
|
||||
multiline
|
||||
fullWidth
|
||||
value={qry}
|
||||
minRows={qryRows}
|
||||
maxRows={qryRows}
|
||||
variant={"standard"}
|
||||
focused
|
||||
inputProps={{ sx: STYLES.SQL_TEXT_FIELD, disabled: true }}
|
||||
/>
|
||||
)}
|
||||
{qryMsg && (
|
||||
<TextField
|
||||
label={"Предупреждения"}
|
||||
color={"warning"}
|
||||
multiline
|
||||
fullWidth
|
||||
value={qryMsg}
|
||||
minRows={qryMsgRows}
|
||||
maxRows={qryMsgRows}
|
||||
variant={"standard"}
|
||||
focused
|
||||
inputProps={{ sx: STYLES.SQL_TEXT_FIELD, disabled: true }}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Drawer>
|
||||
)}
|
||||
<Snackbar open={Boolean(snackBarMessage.text)} autoHideDuration={3000} onClose={handleSnackBarClose}>
|
||||
<Alert severity={snackBarMessage.type || "success"} sx={STYLES.SNACKBAR_ALERT} onClose={handleSnackBarClose}>
|
||||
{snackBarMessage.text}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Область запроса
|
||||
QueryArea.propTypes = {
|
||||
qry: PropTypes.string,
|
||||
qryMsg: PropTypes.string
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { QueryArea };
|
||||
@ -0,0 +1,98 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Диалог добавления/исправления аргумента запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { DATA_TYPE } from "../../common"; //Общие константы редактора
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог добавления/исправления аргумента запроса
|
||||
const ArgIUDialog = ({ name = "", title = "", dataType = DATA_TYPE.NUMB, mandatory = 0, value = "", insert = true, onOk, onCancel }) => {
|
||||
//Собственное состояние - текущее состояние аргумента
|
||||
const [current, setCurrent] = useState({ value, dataType });
|
||||
|
||||
//Нажатие на кнопку "Ok"
|
||||
const handleOk = values => onOk && onOk({ ...values });
|
||||
|
||||
//Нажатие на кнопку "Отмена"
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//При изменении значений в поле ввода
|
||||
const handleInputChange = (name, value) => {
|
||||
//Если поменяли тип данных - надо пересмотреть поля для ввода
|
||||
if (name === "dataType") {
|
||||
//Сохраним в состоянии новый тип данных и сбросим "отладочное значение", ведь оно зависит от типа данных (от этого состояния зависит свойство inputs, передаваемое в P8PDialog ниже)
|
||||
setCurrent({ dataType: value, value: "" });
|
||||
//Мы сами пересчитали форму, туда придут новые настройки элементов ввода через свойство inputs, не надо делать пересчет состояния внутри диалога
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PDialog
|
||||
title={`${insert === true ? TITLES.INSERT : TITLES.UPDATE} аргумента`}
|
||||
inputs={[
|
||||
{ name: "name", value: name, label: "Имя", disabled: insert != true },
|
||||
{ name: "title", value: title, label: "Приглашение" },
|
||||
{
|
||||
name: "dataType",
|
||||
value: current.dataType,
|
||||
label: "Тип данных",
|
||||
list: [
|
||||
{ name: "Строка", value: DATA_TYPE.STR },
|
||||
{ name: "Число", value: DATA_TYPE.NUMB },
|
||||
{ name: "Дата", value: DATA_TYPE.DATE }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "mandatory",
|
||||
value: mandatory,
|
||||
label: "Обязательный",
|
||||
list: [
|
||||
{ name: "Нет", value: 0 },
|
||||
{ name: "Да", value: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
value: current.value,
|
||||
label: "Значение (для отладки)",
|
||||
type: current.dataType === DATA_TYPE.NUMB ? "number" : current.dataType === DATA_TYPE.DATE ? "date" : "text"
|
||||
}
|
||||
]}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
onInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог добавления/исправления аргумента запроса
|
||||
ArgIUDialog.propTypes = {
|
||||
name: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
dataType: PropTypes.number,
|
||||
mandatory: PropTypes.number,
|
||||
value: PropTypes.string,
|
||||
insert: PropTypes.bool,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { ArgIUDialog };
|
||||
@ -0,0 +1,62 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Список аргументов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { List } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения
|
||||
import { Argument, ARGUMENT_VARIANT, ARGUMENT_SHAPE } from "../argument/argument"; //Аргумент запроса
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
LIST: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Список аргументов
|
||||
const ArgsList = ({ args = [], onSelect = null, onDelete = null } = {}) => {
|
||||
//При нажатии на элемент списка
|
||||
const handleItemClick = arg => onSelect && onSelect(arg);
|
||||
|
||||
//При нажатии на удалении элемента списка
|
||||
const handleItemDeleteClick = (e, arg) => {
|
||||
e.stopPropagation();
|
||||
onDelete && onDelete(arg);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<List sx={STYLES.LIST}>
|
||||
{args &&
|
||||
args.map((arg, i) => (
|
||||
<Argument key={i} arg={arg} variant={ARGUMENT_VARIANT.LIST_ITEM} onClick={handleItemClick} onDelete={handleItemDeleteClick} />
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Список аргументов
|
||||
ArgsList.propTypes = {
|
||||
args: PropTypes.arrayOf(ARGUMENT_SHAPE),
|
||||
onSelect: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { ArgsList };
|
||||
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Пользовательские хуки настройки аргументов запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useContext, useCallback } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Работа с аргументами запроса
|
||||
const useQueryArgs = query => {
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Добавление аргумента запроса
|
||||
const addArg = useCallback(
|
||||
async (name, title, dataType, mandatory, value) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.QUERY_OPT_ARG_ADD",
|
||||
args: { NRN: query, SNAME: name, STITLE: title, NDATA_TYPE: dataType, NMANDATORY: mandatory, SVALUE: value },
|
||||
loader: false
|
||||
});
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Исправление аргумента запроса
|
||||
const editArg = useCallback(
|
||||
async (name, title, dataType, mandatory, value) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.QUERY_OPT_ARG_EDIT",
|
||||
args: { NRN: query, SNAME: name, STITLE: title, NDATA_TYPE: dataType, NMANDATORY: mandatory, SVALUE: value },
|
||||
loader: false
|
||||
});
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Удаление аргумента запроса
|
||||
const removeArg = useCallback(
|
||||
async name => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_OPT_ARG_REMOVE", args: { NRN: query, SNAME: name }, loader: false });
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return { addArg, editArg, removeArg };
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useQueryArgs };
|
||||
@ -0,0 +1,107 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент инспектора - Аргументы запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, Icon, Button, Card, CardContent, CardActionArea } from "@mui/material"; //Интерфейсные элементы
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { useQueryArgs } from "./hooks"; //Хуки для работы с аргументами запроса на сервере
|
||||
import { QueryArgsDialog } from "./query_args_dialog"; //Диалог настройки состава атрибутов
|
||||
import { Argument, ARGUMENT_SHAPE, ARGUMENT_VARIANT } from "../argument/argument"; //Аргумент запроса
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Компонент инспектора - Аргументы запроса
|
||||
const InspectorQueryArguments = ({ query, args = [], onOptionsChanged = null }) => {
|
||||
//Собственное состояние - отображение диалога настройки состава аргументов
|
||||
const [openQueryArgsDialog, setOpenQueryArgsDialog] = useState(false);
|
||||
|
||||
//Хук для взаимодействия с сервером
|
||||
const { addArg, editArg, removeArg } = useQueryArgs(query);
|
||||
|
||||
//Уведомление родителя об изменении свойств
|
||||
const notifyOptionsChanged = () => onOptionsChanged && onOptionsChanged();
|
||||
|
||||
//При нажатии на настройку аргументов
|
||||
const handleSetup = () => setOpenQueryArgsDialog(true);
|
||||
|
||||
//При добавлении аргумента
|
||||
const handleArgAdd = async (arg, cb) => {
|
||||
await addArg(arg.name, arg.title, arg.dataType, arg.mandatory, arg.value);
|
||||
cb && cb();
|
||||
notifyOptionsChanged();
|
||||
};
|
||||
|
||||
//При изменении аргумента
|
||||
const handleArgEdit = async (arg, cb) => {
|
||||
await editArg(arg.name, arg.title, arg.dataType, arg.mandatory, arg.value);
|
||||
cb && cb();
|
||||
notifyOptionsChanged();
|
||||
};
|
||||
|
||||
//При удалении аргумента
|
||||
const handleArgRemove = async arg => {
|
||||
await removeArg(arg.name);
|
||||
notifyOptionsChanged();
|
||||
};
|
||||
|
||||
//Закрытие диалога настройки состава аргументов по "Закрыть"
|
||||
const handleQueryArgsDialogClose = () => setOpenQueryArgsDialog(false);
|
||||
|
||||
//Расчет флага "настроенности"
|
||||
const configured = args && args.length > 0 ? true : false;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{openQueryArgsDialog && (
|
||||
<QueryArgsDialog
|
||||
args={args}
|
||||
onArgAdd={handleArgAdd}
|
||||
onArgEdit={handleArgEdit}
|
||||
onArgRemove={handleArgRemove}
|
||||
onClose={handleQueryArgsDialogClose}
|
||||
/>
|
||||
)}
|
||||
{configured && (
|
||||
<Card variant={"outlined"}>
|
||||
<CardActionArea onClick={handleSetup}>
|
||||
<CardContent>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{args.map((arg, i) => (
|
||||
<Argument key={i} arg={arg} variant={ARGUMENT_VARIANT.CHIP} />
|
||||
))}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
)}
|
||||
{!configured && (
|
||||
<Button startIcon={<Icon>build</Icon>} onClick={handleSetup}>
|
||||
{BUTTONS.CONFIG}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Компонент инспектора - Аргументы запроса
|
||||
InspectorQueryArguments.propTypes = {
|
||||
query: PropTypes.number.isRequired,
|
||||
args: PropTypes.arrayOf(ARGUMENT_SHAPE),
|
||||
onOptionsChanged: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { InspectorQueryArguments };
|
||||
@ -0,0 +1,80 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Диалог настройки аргументов запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||
import { ARGUMENT_SHAPE } from "../argument/argument"; //Аргумент запроса
|
||||
import { ArgsList } from "./args_list"; //Список аргументов запроса
|
||||
import { ArgIUDialog } from "./arg_iu_dialog"; //Диалог добавления/исправления аргумента
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог настройки аргументов запроса
|
||||
const QueryArgsDialog = ({ args, onArgAdd, onArgEdit, onArgRemove, onClose }) => {
|
||||
//Собственное состояние - изменяемый аргумент
|
||||
const [modArg, setModArg] = useState(null);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//Нажатие на кнопку "Закрыть"
|
||||
const handleClose = () => onClose && onClose();
|
||||
|
||||
//При выборе аргумента в списке
|
||||
const handleArgSelect = arg => setModArg({ ...arg });
|
||||
|
||||
//При добавлении аргумента
|
||||
const handleArgAdd = () => setModArg(true);
|
||||
|
||||
//Удаление аргумента
|
||||
const handleArgRemove = arg => showMsgWarn("Удалить аргумент?", () => onArgRemove && onArgRemove(arg));
|
||||
|
||||
//При закрытии диалога добавления/исправления по "ОК"
|
||||
const handleIUDialogOk = async values => {
|
||||
if (modArg === true) onArgAdd && onArgAdd(values, handleIUDialogCancel);
|
||||
else onArgEdit && onArgEdit(values, handleIUDialogCancel);
|
||||
};
|
||||
|
||||
//При закрытии диалога добавления/исправления по "Отмена"
|
||||
const handleIUDialogCancel = () => setModArg(null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PDialog title={`Аргументы запроса`} onClose={handleClose}>
|
||||
{modArg && (
|
||||
<ArgIUDialog {...(modArg === true ? {} : modArg)} insert={modArg === true} onOk={handleIUDialogOk} onCancel={handleIUDialogCancel} />
|
||||
)}
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handleArgAdd}>
|
||||
{BUTTONS.INSERT}
|
||||
</Button>
|
||||
<ArgsList args={args} onSelect={handleArgSelect} onDelete={handleArgRemove} />
|
||||
</P8PDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог настройки аргументов запроса
|
||||
QueryArgsDialog.propTypes = {
|
||||
args: PropTypes.arrayOf(ARGUMENT_SHAPE),
|
||||
onArgAdd: PropTypes.func,
|
||||
onArgEdit: PropTypes.func,
|
||||
onArgRemove: PropTypes.func,
|
||||
onClose: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { QueryArgsDialog };
|
||||
@ -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 };
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Кнопки операций условий отбора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { ButtonGroup, Button } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Кнопки операций условий отбора
|
||||
const CondOperationButtons = ({ buttons = [], onClick }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<ButtonGroup size={"small"} variant={"outlined"}>
|
||||
{buttons.map((button, i) => (
|
||||
<Button key={i} onClick={() => onClick(button.value)} title={button.title}>
|
||||
{button.caption}
|
||||
</Button>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Кнопки операций условий отбора
|
||||
CondOperationButtons.propTypes = {
|
||||
buttons: PropTypes.arrayOf(
|
||||
PropTypes.shape({ caption: PropTypes.string.isRequired, title: PropTypes.string.isRequired, value: PropTypes.string.isRequired })
|
||||
),
|
||||
onClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { CondOperationButtons };
|
||||
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Пользовательские хуки для работы с условиями отбора запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useContext, useCallback } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Работа с условиями отбора запроса
|
||||
const useQueryConditions = query => {
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Установка условий отбора в запросе
|
||||
const setCond = useCallback(
|
||||
async cond => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_OPT_COND_SET", args: { NRN: query, SCOND: cond }, loader: false });
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return { setCond };
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useQueryConditions };
|
||||
@ -0,0 +1,109 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент инспектора - Условия отбора запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Card, CardActionArea, CardContent, Stack, Chip, Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
||||
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"; //Описание аргумента запроса
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Общий стиль многострочного компонента "Chip"
|
||||
const COMMON_MULTILINE_CHIP = COMMON_STYLES.CHIP(true, true);
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
COND_CHIP: {
|
||||
...COMMON_MULTILINE_CHIP,
|
||||
"& .MuiChip-label": {
|
||||
...COMMON_MULTILINE_CHIP["& .MuiChip-label"],
|
||||
paddingTop: "5px",
|
||||
paddingBottom: "5px"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Компонент инспектора - Условия отбора запроса
|
||||
const InspectorQueryConditions = ({ query, cond, entities = [], args = [], onOptionsChanged }) => {
|
||||
//Собственное состояние - отображение диалога настройки условий отбора
|
||||
const [openQueryCondDialog, setOpenQueryCondDialog] = useState(false);
|
||||
|
||||
//Работа со связями на сервере
|
||||
const { setCond } = useQueryConditions(query);
|
||||
|
||||
//Уведомление родителя об изменении свойств
|
||||
const notifyOptionsChanged = () => onOptionsChanged && onOptionsChanged();
|
||||
|
||||
//При нажатии на кнопку настройки условий отбора
|
||||
const handleSetup = () => setOpenQueryCondDialog(true);
|
||||
|
||||
//При закрытии диалога изменений условий отбора по "ОК"
|
||||
const handleQueryCondDialogOk = async cond => {
|
||||
await setCond(cond);
|
||||
notifyOptionsChanged();
|
||||
setOpenQueryCondDialog(false);
|
||||
};
|
||||
|
||||
//При закрытии диалога изменений условий отбора по "Отмена"
|
||||
const handleQueryCondDialogCancel = () => setOpenQueryCondDialog(false);
|
||||
|
||||
//Расчет флага сконфигурированности
|
||||
const configured = cond ? true : false;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{openQueryCondDialog && (
|
||||
<QueryCondDialog cond={cond} entities={entities} args={args} onOk={handleQueryCondDialogOk} onCancel={handleQueryCondDialogCancel} />
|
||||
)}
|
||||
{configured && (
|
||||
<Card variant={"outlined"}>
|
||||
<CardActionArea onClick={handleSetup}>
|
||||
<CardContent>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
<Chip sx={STYLES.COND_CHIP} label={cond} />
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
)}
|
||||
{!configured && (
|
||||
<Button startIcon={<Icon>build</Icon>} onClick={handleSetup}>
|
||||
{BUTTONS.CONFIG}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Компонент инспектора - Условия отбора запроса
|
||||
InspectorQueryConditions.propTypes = {
|
||||
query: PropTypes.number.isRequired,
|
||||
cond: PropTypes.string,
|
||||
entities: PropTypes.arrayOf(ENTITY_SHAPE),
|
||||
args: PropTypes.arrayOf(ARGUMENT_SHAPE),
|
||||
onOptionsChanged: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { InspectorQueryConditions };
|
||||
@ -0,0 +1,186 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Диалог настройки условий отбора запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useRef, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
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, entities, args, onOk, onCancel }) => {
|
||||
//Собственное состояние - условия отбора
|
||||
const [conditions, setConditions] = useState(cond || "");
|
||||
|
||||
//Ссылка на элемент ввода условия
|
||||
const coditionInputRef = useRef(null);
|
||||
|
||||
//Перемещение курсора в конец поля ввода условия
|
||||
const moveCondCursorToEnd = () => {
|
||||
if (coditionInputRef.current) {
|
||||
const length = coditionInputRef.current.value.length;
|
||||
coditionInputRef.current.setSelectionRange(length, length);
|
||||
coditionInputRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
//Нажатие на кнопку "ОК"
|
||||
const handleOk = () => onOk && onOk(conditions);
|
||||
|
||||
//Нажатие на кнопку "Отмена"
|
||||
const handleCancel = () => onCancel && 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 (
|
||||
<P8PDialog title={`Условия отбора запроса`} onOk={handleOk} onCancel={handleCancel}>
|
||||
<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
|
||||
buttons={[
|
||||
{ caption: "=", title: "Равно", value: " = " },
|
||||
{ caption: "<>", title: "Не равно", value: " <> " },
|
||||
{ caption: ">", title: "Больше", value: " > " },
|
||||
{ caption: ">=", title: "Больше или равно", value: " >= " },
|
||||
{ caption: "<", title: "Меньше", value: " < " },
|
||||
{ caption: "<=", title: "Меньше или равно", value: " <= " },
|
||||
{ caption: "(", title: "Открывающая скобка", value: "(" },
|
||||
{ caption: ")", title: "Закрывающая скобка", value: ")" }
|
||||
]}
|
||||
onClick={handleOperationButtonClick}
|
||||
/>
|
||||
<CondOperationButtons
|
||||
buttons={[
|
||||
{ caption: "И", title: "Логическое умножение (конъюнкция)", value: " and " },
|
||||
{ caption: "ИЛИ", title: "Логическое сложение (дизъюнкция)", value: " or " },
|
||||
{ caption: "НЕ", title: "Отрицание (инверсия)", value: " not " },
|
||||
{ caption: "B", title: "Входит в диапазон", value: " in (0, 1, ...) " }
|
||||
]}
|
||||
onClick={handleOperationButtonClick}
|
||||
/>
|
||||
</Stack>
|
||||
<TextField
|
||||
inputRef={coditionInputRef}
|
||||
focused={true}
|
||||
autoFocus
|
||||
value={conditions}
|
||||
placeholder={"Настройте условия отбора..."}
|
||||
multiline
|
||||
minRows={10}
|
||||
fullWidth
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</P8PDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог настройки условий отбора запроса
|
||||
QueryCondDialog.propTypes = {
|
||||
cond: PropTypes.string,
|
||||
onOk: PropTypes.func,
|
||||
entities: PropTypes.arrayOf(ENTITY_SHAPE),
|
||||
args: PropTypes.arrayOf(ARGUMENT_SHAPE),
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { QueryCondDialog };
|
||||
@ -0,0 +1,73 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Диалог настройки атрибута сущности
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { DATA_TYPE } from "../../common"; //Общие константы редактора
|
||||
import { ATTRIBUTE_SHAPE } from "../attribute/attribute"; //Описание атрибута
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог добавления/исправления аргумента запроса
|
||||
const AttrSetupDialog = ({ attr, onOk, onCancel }) => {
|
||||
//Достанем необходимое из атрибута
|
||||
const { name, title, dataType, alias } = attr;
|
||||
|
||||
//Нажатие на кнопку "Ok"
|
||||
const handleOk = values => onOk && onOk({ ...values });
|
||||
|
||||
//Нажатие на кнопку "Отмена"
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PDialog
|
||||
title={`${TITLES.CONFIG} атрибута "${title}"`}
|
||||
inputs={[
|
||||
{ name: "name", value: name, label: "Имя", disabled: true },
|
||||
{ name: "title", value: title, label: "Наименование", disabled: true },
|
||||
{
|
||||
name: "dataType",
|
||||
value: dataType,
|
||||
label: "Тип данных",
|
||||
list: [
|
||||
{ name: "Строка", value: DATA_TYPE.STR },
|
||||
{ name: "Число", value: DATA_TYPE.NUMB },
|
||||
{ name: "Дата", value: DATA_TYPE.DATE }
|
||||
],
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
name: "alias",
|
||||
value: alias,
|
||||
label: "Псевдоним"
|
||||
}
|
||||
]}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог настройки атрибута сущности
|
||||
AttrSetupDialog.propTypes = {
|
||||
attr: ATTRIBUTE_SHAPE.isRequired,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { AttrSetupDialog };
|
||||
@ -0,0 +1,101 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Список атрибутов сущности
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, ListItemIcon, Checkbox, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { ATTRIBUTE_SHAPE, attrGetUse, attrGetShow } from "../attribute/attribute"; //Атрибут сущности
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
SMALL_TOOL_ICON: {
|
||||
fontSize: 20
|
||||
},
|
||||
LIST: { height: "100%", width: "100%", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Список атрибутов сущности
|
||||
const AttrsList = ({ attrs = [], onSelect = null, onShow = null, onSetup = null } = {}) => {
|
||||
//При выборе элемента списка
|
||||
const handleSelectClick = attr => {
|
||||
onSelect && onSelect(attr);
|
||||
};
|
||||
|
||||
//При нажатии на кнопку изменения видимости атрибута
|
||||
const handleShowClick = (e, attr) => {
|
||||
e.stopPropagation();
|
||||
onShow && onShow(attr);
|
||||
};
|
||||
|
||||
//При нажатии на кнопку настройки атрибута
|
||||
const handleSetupClick = (e, attr) => {
|
||||
e.stopPropagation();
|
||||
onSetup && onSetup(attr);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<List sx={STYLES.LIST}>
|
||||
{attrs &&
|
||||
attrs.map((attr, i) => {
|
||||
const [selected, selectedTitle] = attrGetUse(attr, true);
|
||||
const [showTitle, showIcon] = attrGetShow(attr, true);
|
||||
return (
|
||||
<ListItem key={i} disablePadding>
|
||||
<ListItemButton onClick={() => handleSelectClick(attr)} selected={selected} dense>
|
||||
<ListItemIcon>
|
||||
<Checkbox edge={"start"} checked={selected} tabIndex={-1} disableRipple title={selectedTitle} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={attr.title}
|
||||
secondaryTypographyProps={{ component: "div" }}
|
||||
secondary={
|
||||
<Stack direction={"column"}>
|
||||
<Typography variant={"caption"}>{`${attr.name}${attr.alias ? ` (${attr.alias})` : ""}`}</Typography>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
<Stack direction={"row"}>
|
||||
<IconButton onClick={e => handleShowClick(e, attr)} title={showTitle}>
|
||||
<Icon>{showIcon}</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={e => handleSetupClick(e, attr)} title={"Настроить"}>
|
||||
<Icon>settings</Icon>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Список атрибутов сущности
|
||||
AttrsList.propTypes = {
|
||||
attrs: PropTypes.arrayOf(ATTRIBUTE_SHAPE),
|
||||
onSelect: PropTypes.func,
|
||||
onShow: PropTypes.func,
|
||||
onSetup: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { AttrsList };
|
||||
@ -0,0 +1,113 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Диалог добавления сущности запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, TextField, InputAdornment, Icon, IconButton } from "@mui/material"; //Интерфейсные элементы MUI
|
||||
import { P8PDialog, P8P_DIALOG_WIDTH } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||
import { P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Типовое встраиваемое сообщение
|
||||
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { EntsList } from "./ents_list"; //Список сущостей
|
||||
import { useEntities, useDebounce } from "./hooks"; //Хуки для работы с сущностями
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Максимальная длина поисковой фразы для начала поиска (символов)
|
||||
const FILTER_MIN_LENGTH = 3;
|
||||
|
||||
//Таймаут ожидания ввода поисковой фразы до начала поиска на сервере (мс)
|
||||
const FILTER_TIMEOUT = 1000;
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
LIST_CONTAINER: { width: "100%", height: "500px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог добавления сущности запроса
|
||||
const EntityAddDialog = ({ onSelect, onClose }) => {
|
||||
//Собственное состояние - фильтр сущностей
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
//Значение для отложенного поиска
|
||||
const debouncedFilter = useDebounce(filter, FILTER_TIMEOUT);
|
||||
|
||||
//Доступные сущности
|
||||
const [ents] = useEntities(debouncedFilter, FILTER_MIN_LENGTH);
|
||||
|
||||
//При выборе сущности в списке
|
||||
const handleSelect = ent => onSelect && onSelect({ ...ent });
|
||||
|
||||
//Нажатие на кнопку "Закыть"
|
||||
const handleClose = () => onClose && onClose();
|
||||
|
||||
//При изменении значения фильтра
|
||||
const handleFilterChange = e => setFilter(e.target.value);
|
||||
|
||||
//При очистке фильтра
|
||||
const handleFilterClear = () => setFilter("");
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PDialog title={`${TITLES.INSERT} сущности`} width={P8P_DIALOG_WIDTH.SM} fullWidth onClose={handleClose}>
|
||||
<TextField
|
||||
margin={"normal"}
|
||||
variant={"standard"}
|
||||
fullWidth
|
||||
placeholder={"Поиск сущности..."}
|
||||
value={filter}
|
||||
onChange={handleFilterChange}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position={"start"}>
|
||||
<Icon>search</Icon>
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position={"end"}>
|
||||
<IconButton onClick={handleFilterClear}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Box sx={STYLES.LIST_CONTAINER} justifyContent={"center"} alignItems={"center"} display={"flex"}>
|
||||
{ents && <EntsList ents={ents} onSelect={handleSelect} />}
|
||||
{!ents && (
|
||||
<P8PComponentInlineMessage
|
||||
name={"Нет данных, соответствующих фильтру"}
|
||||
message={
|
||||
filter || String(filter).length > FILTER_MIN_LENGTH
|
||||
? "Измените значение фильтра для повторного поиска"
|
||||
: `Начните ввод в строку поиска (не менее ${FILTER_MIN_LENGTH} символов)`
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</P8PDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог добавления сущности запроса
|
||||
EntityAddDialog.propTypes = {
|
||||
onSelect: PropTypes.func,
|
||||
onClose: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { EntityAddDialog };
|
||||
@ -0,0 +1,160 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Диалог настройки атрибутов сущности
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, TextField, InputAdornment, Icon, IconButton } from "@mui/material"; //Интерфейсные элементы MUI
|
||||
import { P8PDialog, P8P_DIALOG_WIDTH } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||
import { P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Типовое встраиваемое сообщение
|
||||
import { AttrsList } from "./attrs_list"; //Список атрибутов сущности
|
||||
import { AttrSetupDialog } from "./attr_setup_dialog"; //Диалог настройки атрибута
|
||||
import { useEntityAttrs } from "./hooks"; //Хуки диалога настройки атрибутов сущности
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
LIST_CONTAINER: { width: "100%", height: "500px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог настройки атрибутов сущности
|
||||
const EntityAttrsDialog = ({ query, id, title, onOk, onCancel }) => {
|
||||
//Собственное состояние - фильтр атрибутов
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
//Собственное состояние - список атрибутов
|
||||
const [attrs, setAttrs] = useState([]);
|
||||
|
||||
//Собственное состояние - настраиваемый атрибут
|
||||
const [setupAttr, setSetupAttr] = useState(null);
|
||||
|
||||
//Хук для взаимодействия с сервером
|
||||
const [srvAttrs, saveAttrs] = useEntityAttrs(query, id);
|
||||
|
||||
//Нажатие на кнопку "Ok"
|
||||
const handleOk = async () => {
|
||||
await saveAttrs(attrs);
|
||||
onOk && onOk();
|
||||
};
|
||||
|
||||
//Нажатие на кнопку "Отмена"
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//Выбор/исключение атрибута из запроса
|
||||
const handleAttrSelect = attr =>
|
||||
setAttrs(
|
||||
attrs.map(a => ({
|
||||
...(a.id === attr.id ? { ...a, use: a.use === 1 ? 0 : 1, show: a.use === 1 ? 0 : a.show } : a)
|
||||
}))
|
||||
);
|
||||
|
||||
//Отображение/сокрытие атрибута в запросе
|
||||
const handleAttrShow = attr =>
|
||||
setAttrs(
|
||||
attrs.map(a => ({
|
||||
...(a.id === attr.id ? { ...a, show: a.show === 1 ? 0 : 1, use: a.show === 0 ? 1 : a.use } : a)
|
||||
}))
|
||||
);
|
||||
|
||||
//Настройка атрибута
|
||||
const handleAttrSetup = attr => setSetupAttr({ ...attr });
|
||||
|
||||
//При закрытии диалога настройки атрибута по "ОК"
|
||||
const handleAttrSetupOk = values => {
|
||||
setAttrs(
|
||||
attrs.map(a => ({
|
||||
...(a.id === setupAttr.id ? { ...a, use: 1, alias: values.alias } : a)
|
||||
}))
|
||||
);
|
||||
setSetupAttr(null);
|
||||
};
|
||||
|
||||
const handleAttrSetupCancel = () => setSetupAttr(null);
|
||||
|
||||
//При изменении значения фильтра
|
||||
const handleFilterChange = e => setFilter(e.target.value);
|
||||
|
||||
//При очистке фильтра
|
||||
const handleFilterClear = () => setFilter("");
|
||||
|
||||
//При загрузке данных с сервера
|
||||
useEffect(() => {
|
||||
if (srvAttrs) setAttrs(srvAttrs.map(srvAttr => ({ ...srvAttr })));
|
||||
}, [srvAttrs]);
|
||||
|
||||
//Рег. выражение для фильтра
|
||||
const filterRegExp = filter ? new RegExp(filter, "i") : null;
|
||||
|
||||
//Отфильтрованные для отображения атрибуты
|
||||
const filteredAttrs = attrs.filter(attr => (filterRegExp ? filterRegExp.test(attr.name) || filterRegExp.test(attr.title) : true));
|
||||
|
||||
//Флаг отображения списка атрибутов
|
||||
const displayAttrsList = attrs.length == 0 ? null : filteredAttrs.length > 0 ? true : false;
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PDialog title={`Атрибуты сущности "${title}"`} width={P8P_DIALOG_WIDTH.SM} fullWidth onOk={handleOk} onCancel={handleCancel}>
|
||||
{setupAttr && <AttrSetupDialog attr={setupAttr} onOk={handleAttrSetupOk} onCancel={handleAttrSetupCancel} />}
|
||||
<TextField
|
||||
margin={"normal"}
|
||||
variant={"standard"}
|
||||
fullWidth
|
||||
placeholder={"Поиск атрибута..."}
|
||||
value={filter}
|
||||
onChange={handleFilterChange}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position={"start"}>
|
||||
<Icon>search</Icon>
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position={"end"}>
|
||||
<IconButton onClick={handleFilterClear}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Box sx={STYLES.LIST_CONTAINER} justifyContent={"center"} alignItems={"center"} display={"flex"}>
|
||||
{displayAttrsList === true && (
|
||||
<AttrsList attrs={filteredAttrs} filter={filter} onSelect={handleAttrSelect} onShow={handleAttrShow} onSetup={handleAttrSetup} />
|
||||
)}
|
||||
{displayAttrsList === false && (
|
||||
<P8PComponentInlineMessage
|
||||
name={"Нет данных, соответствующих фильтру"}
|
||||
message={"Измените значение фильтра для повторного поиска"}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</P8PDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог настройки атрибутов сущности
|
||||
EntityAttrsDialog.propTypes = {
|
||||
query: PropTypes.number.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { EntityAttrsDialog };
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент: Список сущностей
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { List } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения
|
||||
import { Entity, ENTITY_VARIANT, ENTITY_BRIEF_SHAPE } from "../entity/entity"; //Сущности
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
LIST: { height: "100%", width: "100%", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Список сущностей
|
||||
const EntsList = ({ ents = [], onSelect = null } = {}) => {
|
||||
//При нажатии на элемент списка
|
||||
const handleItemClick = ent => onSelect && onSelect(ent);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<List sx={STYLES.LIST}>
|
||||
{ents && ents.map((ent, i) => <Entity key={i} data={ent} variant={ENTITY_VARIANT.LIST_ITEM} onClick={handleItemClick} />)}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Список сущностей
|
||||
EntsList.propTypes = {
|
||||
ents: PropTypes.arrayOf(ENTITY_BRIEF_SHAPE),
|
||||
onSelect: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { EntsList };
|
||||
203
app/panels/query_editor/components/inspector_query_ents/hooks.js
Normal file
203
app/panels/query_editor/components/inspector_query_ents/hooks.js
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Пользовательские хуки для работы с сущностями
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useRef, useContext, useCallback, useEffect, useState } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { object2Base64XML } from "../../../../core/utils"; //Вспомогательные функции
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Работа с сущностями системы
|
||||
const useEntities = (filter, minFilterLen) => {
|
||||
//Контроллер для прерывания запросов
|
||||
const abortController = useRef(null);
|
||||
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Обновление данных
|
||||
const doRefresh = () => setRefresh(true);
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
abortController.current?.abort?.();
|
||||
abortController.current = new AbortController();
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.ENTITY_LIST",
|
||||
args: { SSEARCH: filter },
|
||||
respArg: "COUT",
|
||||
isArray: name => ["XENT"].includes(name),
|
||||
attributeValueProcessor: (name, val) => (["name", "title"].includes(name) ? undefined : val),
|
||||
loader: false,
|
||||
signal: abortController.current.signal
|
||||
});
|
||||
setData(data?.XENTS?.XENT || null);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
//Если надо обновить
|
||||
if (refresh) {
|
||||
//Если фильтр задан и он нас удовлетворяет - получим данные
|
||||
if (filter && String(filter).length >= minFilterLen) loadData();
|
||||
//Нет фильтра - нет данных
|
||||
else {
|
||||
setData(null);
|
||||
}
|
||||
}
|
||||
//Сбрасываем запрос при смене зависимостей или отмонтировании
|
||||
return () => abortController.current?.abort?.();
|
||||
}, [refresh, filter, minFilterLen, executeStored]);
|
||||
|
||||
//При изменении входных свойств - поднимаем флаг обновления
|
||||
useEffect(() => setRefresh(true), [filter, minFilterLen]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, doRefresh, isLoading];
|
||||
};
|
||||
|
||||
//Работа с сущностями запроса
|
||||
const useQueryEntities = query => {
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Добавление сущности в запрос
|
||||
const addEnt = useCallback(
|
||||
async (name, type) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_ADD", args: { NRN: query, SNAME: name, STYPE: type }, loader: false });
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Удаление сущности из запроса
|
||||
const removeEnt = useCallback(
|
||||
async ent => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_REMOVE", args: { NRN: query, SID: ent }, loader: false });
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Сохранение координат сущности на диаграммем
|
||||
const setEntPosition = useCallback(
|
||||
async (ent, x, y) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_POSITION_SET", args: { NRN: query, SID: ent, NX: x, NY: y }, loader: false });
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return { addEnt, removeEnt, setEntPosition };
|
||||
};
|
||||
|
||||
//Работа с атрибутами сущности
|
||||
const useEntityAttrs = (query, entity) => {
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//Обновление данных
|
||||
const doRefresh = () => setRefresh(true);
|
||||
|
||||
//Установка атрибутов сущности
|
||||
const setAttrs = useCallback(
|
||||
async attrs => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.QUERY_ENT_ATTRS_SET",
|
||||
args: {
|
||||
NRN: query,
|
||||
SID: entity,
|
||||
CATTRS: {
|
||||
VALUE: object2Base64XML(attrs, { arrayNodeName: "XATTR", ignoreAttributes: false, attributeNamePrefix: "" }),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
},
|
||||
loader: false
|
||||
});
|
||||
},
|
||||
[query, entity, executeStored, SERV_DATA_TYPE_CLOB]
|
||||
);
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.QUERY_ENT_ATTRS_GET",
|
||||
args: { NRN: query, SID: entity },
|
||||
respArg: "COUT",
|
||||
isArray: name => ["XATTR"].includes(name),
|
||||
attributeValueProcessor: (name, val) => (["name", "title", "agg"].includes(name) ? undefined : val),
|
||||
loader: true
|
||||
});
|
||||
setData(data?.XATTRS?.XATTR || []);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
//Если надо обновить
|
||||
if (refresh)
|
||||
//Получим данные
|
||||
loadData();
|
||||
}, [refresh, query, entity, executeStored]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, setAttrs, doRefresh, isLoading];
|
||||
};
|
||||
|
||||
//Отложенное значение
|
||||
const useDebounce = (value, delay) => {
|
||||
//Собственное состояние
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
//При изменении интервала или значения
|
||||
useEffect(() => {
|
||||
//Взводим таймер
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
//Сбрасываем таймер при отмонтировании или изменении зависимостей
|
||||
return () => clearTimeout(handler);
|
||||
}, [value, delay]);
|
||||
|
||||
//Возвращаем отложенное значение
|
||||
return debouncedValue;
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useEntities, useQueryEntities, useEntityAttrs, useDebounce };
|
||||
@ -0,0 +1,112 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент инспектора - Сущности запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useContext, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
||||
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений приложения
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { ENTITY_SHAPE } from "../entity/entity"; //Описание сущности
|
||||
import { EntityAddDialog } from "./entity_add_dialog"; //Диалог добавления сущности
|
||||
import { EntityAttrsDialog } from "./entity_attrs_dialog"; //Диалог настройки атрибутов сущности
|
||||
import { useQueryEntities } from "./hooks"; //Хуки для работы с сущностями на сервере
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Компонент инспектора - Сущности запроса
|
||||
const InspectorQueryEntities = ({ query, entity, onOptionsChanged }) => {
|
||||
//Отображение диалога добавления сущности
|
||||
const [openEntityAddDialog, setOpenEntityAddDialog] = useState(false);
|
||||
|
||||
//Отображение диалога настройки атрибутов сущности
|
||||
const [openEntityAttrsDialog, setOpenEntityAttrsDialog] = useState(false);
|
||||
|
||||
//Работа с сущностями на сервере
|
||||
const { addEnt, removeEnt } = useQueryEntities(query);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//Уведомление родителя об изменении свойств
|
||||
const notifyOptionsChanged = () => onOptionsChanged && onOptionsChanged();
|
||||
|
||||
//При нажатии на кнопку добавлении сущности в запрос
|
||||
const handleEntityAddClick = () => setOpenEntityAddDialog(true);
|
||||
|
||||
//При нажатии на кнопку настройки атрибутов сущности
|
||||
const handleEntityAttrsClick = () => setOpenEntityAttrsDialog(true);
|
||||
|
||||
//При нажатии на кнопку даления сущности из запроса
|
||||
const handleEntityRemoveClick = () =>
|
||||
showMsgWarn(`Удалить сущность "${entity.title}"?`, async () => {
|
||||
if (entity?.id) {
|
||||
await removeEnt(entity.id);
|
||||
notifyOptionsChanged();
|
||||
}
|
||||
});
|
||||
|
||||
//Закрытие диалога добавления сущности по "Закрыть"
|
||||
const handleEntityAddDialogClose = () => setOpenEntityAddDialog(false);
|
||||
|
||||
//Закрытие диалога добавления сущности по выбору сущности из списка
|
||||
const handleEntityAddDialogSelect = async ent => {
|
||||
await addEnt(ent.name, ent.type);
|
||||
setOpenEntityAddDialog(false);
|
||||
notifyOptionsChanged();
|
||||
};
|
||||
|
||||
//Закрытие диалога настройки атрибутов сущности по "Отмена"
|
||||
const handleEntityAttrsDialogCancel = () => setOpenEntityAttrsDialog(false);
|
||||
|
||||
//Закрытие диалога настройки атрибутов сущности по "ОК"
|
||||
const handleEntityAttrsDialogOk = () => {
|
||||
notifyOptionsChanged();
|
||||
setOpenEntityAttrsDialog();
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{openEntityAddDialog && <EntityAddDialog onSelect={handleEntityAddDialogSelect} onClose={handleEntityAddDialogClose} />}
|
||||
{openEntityAttrsDialog && (
|
||||
<EntityAttrsDialog query={query} {...entity} onOk={handleEntityAttrsDialogOk} onCancel={handleEntityAttrsDialogCancel} />
|
||||
)}
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handleEntityAddClick}>
|
||||
{BUTTONS.INSERT}
|
||||
</Button>
|
||||
{entity && (
|
||||
<>
|
||||
<P8PEditorSubHeader title={entity.title} maxWidth={"200px"} />
|
||||
<Button startIcon={<Icon>edit_attributes</Icon>} onClick={handleEntityAttrsClick}>
|
||||
Атрибуты
|
||||
</Button>
|
||||
<Button startIcon={<Icon>delete</Icon>} onClick={handleEntityRemoveClick}>
|
||||
{BUTTONS.DELETE}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Компонент инспектора - Сущности запроса
|
||||
InspectorQueryEntities.propTypes = {
|
||||
query: PropTypes.number.isRequired,
|
||||
entity: ENTITY_SHAPE,
|
||||
onOptionsChanged: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { InspectorQueryEntities };
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Пользовательские хуки для работы со связями
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useContext, useCallback } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Работа со связами запроса
|
||||
const useQueryRelations = query => {
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Добавление отношения сущностей в запрос
|
||||
const addRl = useCallback(
|
||||
async (source, target) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_RL_ADD", args: { NRN: query, SSOURCE: source, STARGET: target }, loader: false });
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Удаление отношения сущностей из запроса
|
||||
const removeRl = useCallback(
|
||||
async rl => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_RL_REMOVE", args: { NRN: query, SID: rl }, loader: false });
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Установка признака обязательности отношения сущности в запросе
|
||||
const setRlMandatory = useCallback(
|
||||
async (rl, mandatory) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.QUERY_RL_MANDATORY_SET",
|
||||
args: { NRN: query, SID: rl, NMANDATORY: mandatory },
|
||||
loader: false
|
||||
});
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return { addRl, removeRl, setRlMandatory };
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useQueryRelations };
|
||||
@ -0,0 +1,88 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компонент инспектора - Связи запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Icon, Button, FormControlLabel, Checkbox } from "@mui/material"; //Интерфейсные элементы
|
||||
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений приложения
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { RELATION_SHAPE } from "../relation/relation"; //Описание связи
|
||||
import { useQueryRelations } from "./hooks"; //Хуки для работы со связями
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
MANDATORY_FORMCONTROLLABEL: {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Компонент инспектора - Связи запроса
|
||||
const InspectorQueryRelations = ({ query, relation, onOptionsChanged }) => {
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//Работа со связями на сервере
|
||||
const { removeRl, setRlMandatory } = useQueryRelations(query);
|
||||
|
||||
//Уведомление родителя об изменении свойств
|
||||
const notifyOptionsChanged = () => onOptionsChanged && onOptionsChanged();
|
||||
|
||||
//При нажатии на кнопку даления связи из запроса
|
||||
const handleRelationRemoveClick = () =>
|
||||
showMsgWarn(`Удалить связь "${relation.source}" - "${relation.target}"?`, async () => {
|
||||
if (relation?.id) {
|
||||
await removeRl(relation.id);
|
||||
notifyOptionsChanged();
|
||||
}
|
||||
});
|
||||
|
||||
//При изменении состояния обязательности связи в запросе
|
||||
const handleRelationMandatoryChange = async e => {
|
||||
await setRlMandatory(relation.id, e.target.checked === true ? 1 : 0);
|
||||
notifyOptionsChanged();
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
<FormControlLabel
|
||||
sx={STYLES.MANDATORY_FORMCONTROLLABEL}
|
||||
control={<Checkbox checked={relation.mandatory === 1 ? true : false} onChange={handleRelationMandatoryChange} />}
|
||||
label={"Обязательна"}
|
||||
/>
|
||||
<Button startIcon={<Icon>delete</Icon>} onClick={handleRelationRemoveClick}>
|
||||
{BUTTONS.DELETE}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Компонент инспектора - Связи запроса
|
||||
InspectorQueryRelations.propTypes = {
|
||||
query: PropTypes.number.isRequired,
|
||||
relation: RELATION_SHAPE.isRequired,
|
||||
onOptionsChanged: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { InspectorQueryRelations };
|
||||
116
app/panels/query_editor/components/queries_manager/hooks.js
Normal file
116
app/panels/query_editor/components/queries_manager/hooks.js
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Пользовательские хуки для работы с запросами
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Работа с запросами
|
||||
const useQueries = () => {
|
||||
//Собственное состояние - флаг инициализированности
|
||||
const [isInit, setInit] = useState(false);
|
||||
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Обновление данных
|
||||
const doRefresh = () => setRefresh(true);
|
||||
|
||||
//Добавление запроса
|
||||
const insertQuery = useCallback(
|
||||
async (code, name) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_INSERT", args: { SCODE: code, SNAME: name }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Изменение запроса
|
||||
const updateQuery = useCallback(
|
||||
async (query, code, name) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_UPDATE", args: { NRN: query, SCODE: code, SNAME: name }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Удаление запроса
|
||||
const deleteQuery = useCallback(
|
||||
async query => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_DELETE", args: { NRN: query }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Установка флага готовности запроса
|
||||
const setQueryReady = useCallback(
|
||||
async (query, ready) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_READY_SET", args: { NRN: query, NREADY: ready }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Установка флага публичности запроса
|
||||
const setQueryPbl = useCallback(
|
||||
async (query, pbl) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_PBL_SET", args: { NRN: query, NPBL: pbl }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.QUERY_LIST",
|
||||
respArg: "COUT",
|
||||
isArray: name => ["XQUERY"].includes(name),
|
||||
attributeValueProcessor: (name, val) => (["code", "name"].includes(name) ? undefined : val),
|
||||
loader: true
|
||||
});
|
||||
setData(data?.XQUERIES?.XQUERY || []);
|
||||
setInit(true);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
//Если надо обновить
|
||||
if (refresh)
|
||||
//Получим данные
|
||||
loadData();
|
||||
}, [refresh, executeStored]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, insertQuery, updateQuery, deleteQuery, setQueryReady, setQueryPbl, doRefresh, isLoading, isInit];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useQueries };
|
||||
@ -10,6 +10,7 @@
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
|
||||
//---------
|
||||
@ -20,14 +21,15 @@ import { BUTTONS } from "../../../../../app.text"; //Общие текстовы
|
||||
const STYLES = {
|
||||
SMALL_TOOL_ICON: {
|
||||
fontSize: 20
|
||||
}
|
||||
},
|
||||
LIST: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL }
|
||||
};
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Структура данных о сущности запроса
|
||||
//Структура элемента списка запросов
|
||||
const QUERIES_LIST_ITEM_SHAPE = PropTypes.shape({
|
||||
rn: PropTypes.number.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
@ -43,7 +45,7 @@ const QUERIES_LIST_ITEM_SHAPE = PropTypes.shape({
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог открытия запроса
|
||||
//Список запросов
|
||||
const QueriesList = ({ queries = [], current = null, onSelect = null, onPbl = null, onReady = null, onEdit = null, onDelete = null } = {}) => {
|
||||
//При выборе элемента списка
|
||||
const handleSelectClick = query => {
|
||||
@ -76,7 +78,7 @@ const QueriesList = ({ queries = [], current = null, onSelect = null, onPbl = nu
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<List sx={{ height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto" }}>
|
||||
<List sx={STYLES.LIST}>
|
||||
{queries.map((query, i) => {
|
||||
const selected = query.rn === current;
|
||||
const disabled = !query.modify;
|
||||
@ -85,8 +87,8 @@ const QueriesList = ({ queries = [], current = null, onSelect = null, onPbl = nu
|
||||
const readyTitle = `${query.ready === 1 ? "Готов" : "Не готов"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
||||
const readyIcon = query.ready === 1 ? "touch_app" : "do_not_touch";
|
||||
return (
|
||||
<ListItem key={i}>
|
||||
<ListItemButton onClick={() => handleSelectClick(query)} selected={selected}>
|
||||
<ListItem key={i} disablePadding>
|
||||
<ListItemButton onClick={() => handleSelectClick(query)} selected={selected} dense>
|
||||
<ListItemText
|
||||
primary={query.name}
|
||||
secondaryTypographyProps={{ component: "div" }}
|
||||
|
||||
@ -13,7 +13,7 @@ import { Button, Icon } from "@mui/material"; //Интерфейсные ком
|
||||
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Типовой диалог настройки
|
||||
import { useQuery } from "../../hooks"; //Пользовательские хуки
|
||||
import { useQueries } from "./hooks"; //Хуки для работы с запросами
|
||||
import { QueriesList } from "./queries_list"; //Список запросов
|
||||
import { QueryIUDialog } from "./query_iu_dialog"; //Диалог добавления/исправления запроса
|
||||
|
||||
@ -27,7 +27,7 @@ const QueriesManager = ({ current = null, onQuerySelect = null, onCancel = null
|
||||
const [modQuery, setModQuery] = useState(null);
|
||||
|
||||
//Работа со списком запросов
|
||||
const [queries, insertQuery, updateQuery, deleteQuery, setQueryReady, setQueryPbl] = useQuery();
|
||||
const [queries, insertQuery, updateQuery, deleteQuery, setQueryReady, setQueryPbl] = useQueries();
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
@ -36,7 +36,7 @@ const QueriesManager = ({ current = null, onQuerySelect = null, onCancel = null
|
||||
const handleQueryAdd = () => setModQuery(true);
|
||||
|
||||
//При выборе запроса
|
||||
const handleQuerySelect = query => onQuerySelect && onQuerySelect(query.rn);
|
||||
const handleQuerySelect = query => onQuerySelect && onQuerySelect({ ...query });
|
||||
|
||||
//При установке признака публичности
|
||||
const handleQueryPblSet = query => setQueryPbl(query.rn, query.pbl === 1 ? 0 : 1);
|
||||
|
||||
@ -8,10 +8,11 @@
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useCallback, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import ReactFlow, { addEdge, Controls, getOutgoers, applyNodeChanges, applyEdgeChanges } from "reactflow"; //Библиотека редактора диаграмм
|
||||
import { NODE_TYPE } from "../../common"; //Общие ресурсы и константы редактора
|
||||
import { Entity } from "../entity/entity"; //Сущность запроса
|
||||
import { Attribute } from "../attribute/attribute"; //Атрибут сущности
|
||||
import { Entity, ENTITY_SHAPE } from "../entity/entity"; //Сущность запроса
|
||||
import { Attribute, ATTRIBUTE_SHAPE } from "../attribute/attribute"; //Атрибут сущности
|
||||
import "reactflow/dist/style.css"; //Типовые стили библиотеки редактора диаграмм
|
||||
import "./query_diagram.css"; //Стили компонента
|
||||
|
||||
@ -36,42 +37,48 @@ const NODE_TYPES_COMPONENTS = {
|
||||
[NODE_TYPE.ATTRIBUTE]: Attribute
|
||||
};
|
||||
|
||||
//Структура элемента диаграммы
|
||||
const NODE_SHAPE = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf([NODE_TYPE.ENTITY, NODE_TYPE.ATTRIBUTE]).isRequired,
|
||||
style: PropTypes.object,
|
||||
position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }),
|
||||
draggable: PropTypes.bool.isRequired,
|
||||
data: PropTypes.oneOfType([ENTITY_SHAPE, ATTRIBUTE_SHAPE])
|
||||
});
|
||||
|
||||
//Структура связи диаграммы
|
||||
const EDGE_SHAPE = PropTypes.shape({ id: PropTypes.string.isRequired, source: PropTypes.string.isRequired, target: PropTypes.string.isRequired });
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Проверка зацикленности связи
|
||||
const hasCycle = (connection, target, nodes, edges, visited = new Set()) => {
|
||||
if (visited.has(target.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (visited.has(target.id)) return false;
|
||||
visited.add(target.id);
|
||||
|
||||
for (const outgoer of getOutgoers(target, nodes, edges)) {
|
||||
if (outgoer.id === connection.source || hasCycle(connection, outgoer, nodes, edges, visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const outgoer of getOutgoers(target, nodes, edges))
|
||||
if (outgoer.id === connection.source || hasCycle(connection, outgoer, nodes, edges, visited)) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
//Проверка корректности связи
|
||||
const isValidConnection = (connection, nodes, edges) => {
|
||||
if (!connection.source || !connection.target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tableId = connection.source.split("-")[0];
|
||||
const isSameTable = connection.target.startsWith(tableId);
|
||||
if (isSameTable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Должны быть заданы источник и приёмник
|
||||
if (!connection.source || !connection.target) return false;
|
||||
//Найдем источник и приёмник
|
||||
const source = nodes.find(node => node.id === connection.source);
|
||||
const target = nodes.find(node => node.id === connection.target);
|
||||
if (!target || target.id === connection.source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Приёмник и источник должны существовать
|
||||
if (!target || !source) return false;
|
||||
//Нельзя ссылаться на самого себя
|
||||
if (source?.data?.parentEntity == target?.data?.parentEntity) return false;
|
||||
//Типы данны источника и приёмника должны совпадать
|
||||
if (source?.data?.dataType != target?.data?.dataType) return false;
|
||||
//Приёмник должен не должен быть источником
|
||||
if (target.id === connection.source) return false;
|
||||
//Нельзя зацикливаться
|
||||
return !hasCycle(connection, target, nodes, edges);
|
||||
};
|
||||
|
||||
@ -80,12 +87,23 @@ const isValidConnection = (connection, nodes, edges) => {
|
||||
//-----------
|
||||
|
||||
//Диаграмма запроса
|
||||
const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRemove, onRelationAdd, onRelationRemove }) => {
|
||||
const QueryDiagram = ({
|
||||
nodes = [],
|
||||
edges = [],
|
||||
onEntityClick,
|
||||
onEntityAttrClick,
|
||||
onEntityPositionChange,
|
||||
onEntityRemove,
|
||||
onRelactionClick,
|
||||
onRelationAdd,
|
||||
onRelationRemove,
|
||||
onPaneClick
|
||||
}) => {
|
||||
//Собственное состояние - элементы
|
||||
const [nodes, setNodes] = useState(entities);
|
||||
const [nodesCurrent, setNodesCurrent] = useState(nodes);
|
||||
|
||||
//Собственное состояние - связи
|
||||
const [edges, setEdges] = useState(relations);
|
||||
const [edgesCurrent, setEdgesCurrent] = useState(edges);
|
||||
|
||||
//Собственное состояние - перемещённый элемент
|
||||
const [movedNode, setMovedNode] = useState(null);
|
||||
@ -93,56 +111,90 @@ const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRem
|
||||
//При изменении элементов на диаграмме
|
||||
const handleNodesChange = useCallback(
|
||||
changes => {
|
||||
setNodes(nodesSnapshot => applyNodeChanges(changes, nodesSnapshot));
|
||||
//При выборе атрибута подсветим всю сущность
|
||||
const tmpChanges = changes.reduce((prevChanges, curChanges) => {
|
||||
const tmp = { ...curChanges };
|
||||
if (tmp.type == "select") {
|
||||
const chEnt = nodes.find(n => n.id === tmp.id);
|
||||
if (chEnt && chEnt?.data?.parentEntity) {
|
||||
prevChanges.push({ ...curChanges, id: chEnt.data.parentEntity });
|
||||
tmp.selected = false;
|
||||
}
|
||||
}
|
||||
prevChanges.push(tmp);
|
||||
return prevChanges;
|
||||
}, []);
|
||||
//Применим изменения в диаграмме
|
||||
setNodesCurrent(nodesSnapshot => applyNodeChanges(tmpChanges, nodesSnapshot));
|
||||
//Если двигали сущность - запомним начало движения
|
||||
if (changes.length == 1 && changes[0].type == "position" && changes[0].dragging)
|
||||
setMovedNode({ id: changes[0].id, position: { ...changes[0].position } });
|
||||
//Если закончили двигать сущность и ранее запомнили начало движения - вызываем колбэки для нажатия и двиения сущности
|
||||
if (changes.length == 1 && changes[0].type == "position" && !changes[0].dragging && movedNode) {
|
||||
if (onEntityPositionChange) onEntityPositionChange(movedNode.id, movedNode.position);
|
||||
if (onEntityClick) onEntityClick(movedNode.id);
|
||||
setMovedNode(null);
|
||||
}
|
||||
if (changes[0].type == "remove" && entities.find(e => e.id == changes[0].id && e.type == NODE_TYPE.ENTITY) && onEntityRemove)
|
||||
//Если удалили сущность - вызываем колбэк для удаления
|
||||
if (changes[0].type == "remove" && nodes.find(n => n.id == changes[0].id && n.type == NODE_TYPE.ENTITY) && onEntityRemove)
|
||||
onEntityRemove(changes[0].id);
|
||||
},
|
||||
[movedNode, entities, onEntityPositionChange, onEntityRemove]
|
||||
[movedNode, nodes, onEntityClick, onEntityPositionChange, onEntityRemove]
|
||||
);
|
||||
|
||||
//При выборе элемента диаграммы
|
||||
const handleNodeClick = useCallback(
|
||||
(e, node) =>
|
||||
node?.type == NODE_TYPE.ENTITY
|
||||
? onEntityClick && onEntityClick(node?.id)
|
||||
: onEntityAttrClick && onEntityAttrClick(node?.parentId, node?.id),
|
||||
[onEntityClick, onEntityAttrClick]
|
||||
);
|
||||
|
||||
//При связывании элементов на диаграмме
|
||||
const handleConnect = connection => {
|
||||
setEdges(state => addEdge({ ...connection, id: `${connection.source}-${connection.target}` }, state));
|
||||
setEdgesCurrent(state => addEdge({ ...connection, id: `${connection.source}-${connection.target}` }, state));
|
||||
onRelationAdd && onRelationAdd(connection.source, connection.target);
|
||||
};
|
||||
|
||||
//При выборе связи диаграммы
|
||||
const handleEdgeClick = useCallback((e, edge) => onRelactionClick && onRelactionClick(edge.id), [onRelactionClick]);
|
||||
|
||||
//При изменении связей на диаграмме
|
||||
const handleEdgesChange = useCallback(
|
||||
changes => {
|
||||
setEdges(edgesSnapshot => applyEdgeChanges(changes, edgesSnapshot));
|
||||
setEdgesCurrent(edgesSnapshot => applyEdgeChanges(changes, edgesSnapshot));
|
||||
if (changes.length == 1 && changes[0].type == "remove" && onRelationRemove) onRelationRemove(changes[0].id);
|
||||
},
|
||||
[onRelationRemove]
|
||||
);
|
||||
|
||||
const validateConnection = connection => {
|
||||
return isValidConnection(connection, nodes, edges);
|
||||
};
|
||||
//При нажатии на холст диаграммы
|
||||
const handlePaneClick = () => onPaneClick && onPaneClick();
|
||||
|
||||
//Валидация связи
|
||||
const validateConnection = connection => isValidConnection(connection, nodesCurrent, edgesCurrent);
|
||||
|
||||
//Подсветка выбранной сущности
|
||||
|
||||
//При изменении состава сущностей
|
||||
useEffect(() => setNodes(entities), [entities]);
|
||||
useEffect(() => setNodesCurrent(nodes), [nodes]);
|
||||
|
||||
//При изменении состава связей
|
||||
useEffect(() => setEdges(relations), [relations]);
|
||||
useEffect(() => setEdgesCurrent(edges), [edges]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
nodes={nodesCurrent}
|
||||
nodeTypes={NODE_TYPES_COMPONENTS}
|
||||
edges={edges}
|
||||
edges={edgesCurrent}
|
||||
onNodeClick={handleNodeClick}
|
||||
onNodesChange={handleNodesChange}
|
||||
onEdgeClick={handleEdgeClick}
|
||||
onEdgesChange={handleEdgesChange}
|
||||
defaultEdgeOptions={{
|
||||
animated: true,
|
||||
style: STYLES.EDGE
|
||||
}}
|
||||
onPaneClick={handlePaneClick}
|
||||
defaultEdgeOptions={{ style: STYLES.EDGE }}
|
||||
connectionLineStyle={STYLES.CONNECTION_LINE}
|
||||
onConnect={handleConnect}
|
||||
isValidConnection={validateConnection}
|
||||
@ -153,6 +205,20 @@ const QueryDiagram = ({ entities, relations, onEntityPositionChange, onEntityRem
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диаграмма запроса
|
||||
QueryDiagram.propTypes = {
|
||||
nodes: PropTypes.arrayOf(NODE_SHAPE),
|
||||
edges: PropTypes.arrayOf(EDGE_SHAPE),
|
||||
onEntityClick: PropTypes.func,
|
||||
onEntityAttrClick: PropTypes.func,
|
||||
onEntityPositionChange: PropTypes.func,
|
||||
onEntityRemove: PropTypes.func,
|
||||
onRelactionClick: PropTypes.func,
|
||||
onRelationAdd: PropTypes.func,
|
||||
onRelationRemove: PropTypes.func,
|
||||
onPaneClick: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
28
app/panels/query_editor/components/relation/relation.js
Normal file
28
app/panels/query_editor/components/relation/relation.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Компоненты: Связь сущностей запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Структура данных о связи сущностей запроса
|
||||
const RELATION_SHAPE = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
target: PropTypes.string.isRequired,
|
||||
mandatory: PropTypes.number.isRequired
|
||||
});
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { RELATION_SHAPE };
|
||||
@ -1,86 +1,96 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор запросов
|
||||
Пользовательские хуки
|
||||
Пользовательские хуки для работы с метаданными запроса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||
import { useState, useContext, useEffect } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { NODE_TYPE } from "./common"; //Общие ресурсы и константы редактора
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Ширина элемента диаграммы
|
||||
const NODE_WIDTH = 250;
|
||||
|
||||
//Высота единицы состава группового элемента диаграммы
|
||||
const GROUP_NODE_ITEM_HEIGHT = 50;
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
ATTRIBUTE: isLast => ({ borderBottom: isLast ? "none" : "1px solid #dee2e6" })
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Конвертация серверного описания сущностей запроса в элементы диаграммы
|
||||
const serverEntity2QueryDiagramNodes = entity => {
|
||||
const groupWidth = 250;
|
||||
const nameColumnHeight = 50;
|
||||
const columns = entity?.XATTRS?.XATTR || [];
|
||||
const columnsCount = columns.length;
|
||||
const groupHeight = nameColumnHeight + columnsCount * 50;
|
||||
|
||||
const groupNode = {
|
||||
//Ссылка на атрибуты
|
||||
const attrs = entity.attrs || [];
|
||||
//Количество атрибутов
|
||||
const attrsCount = attrs.length;
|
||||
//Высота группового элемента диаграммы
|
||||
const entityNodeHeight = GROUP_NODE_ITEM_HEIGHT + attrsCount * GROUP_NODE_ITEM_HEIGHT;
|
||||
//Элемент диаграммы для сущности (групповой элемент)
|
||||
const entityNode = {
|
||||
id: entity.id,
|
||||
type: NODE_TYPE.ENTITY,
|
||||
data: { ...entity },
|
||||
position: { x: entity.x, y: entity.y },
|
||||
style: {
|
||||
width: groupWidth,
|
||||
height: groupHeight
|
||||
width: NODE_WIDTH,
|
||||
height: entityNodeHeight
|
||||
},
|
||||
draggable: true
|
||||
};
|
||||
|
||||
const columnNodes = columns.map((column, index, columns) => {
|
||||
const x = 1;
|
||||
const y = 50 * (index + 1);
|
||||
const width = groupWidth - 2;
|
||||
const height = 50;
|
||||
|
||||
const isLast = index === columns.length - 1;
|
||||
const defaultColumnStyles = {
|
||||
borderBottom: "1px solid #dee2e6"
|
||||
};
|
||||
const lastColumnStyles = {
|
||||
borderBottom: "none"
|
||||
};
|
||||
const otherStyles = isLast ? lastColumnStyles : defaultColumnStyles;
|
||||
|
||||
return {
|
||||
id: column.id,
|
||||
type: NODE_TYPE.ATTRIBUTE,
|
||||
data: {
|
||||
...column,
|
||||
included: false,
|
||||
parentEntity: entity.id
|
||||
},
|
||||
position: { x, y },
|
||||
parentId: entity.id,
|
||||
extent: "parent",
|
||||
style: {
|
||||
width,
|
||||
height,
|
||||
...otherStyles
|
||||
},
|
||||
draggable: false,
|
||||
selectable: true
|
||||
};
|
||||
});
|
||||
|
||||
return [groupNode, ...columnNodes];
|
||||
//Элементы диаграммы для атрибутов сущности (состав группового элемента)
|
||||
const attrsNodes = attrs.map((attr, index, attrs) => ({
|
||||
id: attr.id,
|
||||
type: NODE_TYPE.ATTRIBUTE,
|
||||
data: { ...attr },
|
||||
position: { x: 1, y: GROUP_NODE_ITEM_HEIGHT * (index + 1) },
|
||||
parentId: entity.id,
|
||||
extent: "parent",
|
||||
style: {
|
||||
width: NODE_WIDTH - 2,
|
||||
height: GROUP_NODE_ITEM_HEIGHT,
|
||||
...STYLES.ATTRIBUTE(index === attrs.length - 1)
|
||||
},
|
||||
draggable: false,
|
||||
selectable: true
|
||||
}));
|
||||
//Возвращаем элемент для сущности (групповой) и элементы для атрибутов (состав группового)
|
||||
return [entityNode, ...attrsNodes];
|
||||
};
|
||||
|
||||
//Конвертация серверного описания запроса в данные для редактора диаграмм
|
||||
const serverQueryData2QueryDiagram = (entities, relations) => {
|
||||
const result = { entities: [], relations: [...relations] };
|
||||
entities.forEach(entity => {
|
||||
const nodes = serverEntity2QueryDiagramNodes(entity);
|
||||
result.entities = [...result.entities, ...nodes];
|
||||
//Инициализация результата
|
||||
const result = { entities: [], relations: [], nodes: [], edges: [] };
|
||||
//Сущности (почти как есть на сервере, только массив XATTRS.XATTR перемещается в attrs)
|
||||
result.entities = entities.map(e => {
|
||||
const tmp = { ...e };
|
||||
tmp.attrs = tmp?.XATTRS?.XATTR?.map(a => ({ ...a })) || [];
|
||||
delete tmp.XATTRS;
|
||||
return tmp;
|
||||
});
|
||||
//Связи сущностей
|
||||
result.relations = relations.map(r => ({ ...r }));
|
||||
//Элементы для диаграммы
|
||||
result.entities.forEach(entity => {
|
||||
const nodes = serverEntity2QueryDiagramNodes(entity);
|
||||
result.nodes = [...result.nodes, ...nodes];
|
||||
});
|
||||
//Грани для диаграммы
|
||||
result.edges = relations.map(r => ({ ...r, animated: r.mandatory === 1 ? false : true }));
|
||||
//Вернем итоговый результат
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -88,8 +98,8 @@ const serverQueryData2QueryDiagram = (entities, relations) => {
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Работа с запросами
|
||||
const useQuery = () => {
|
||||
//Работа с метаданными запроса
|
||||
const useQuery = query => {
|
||||
//Собственное состояние - флаг инициализированности
|
||||
const [isInit, setInit] = useState(false);
|
||||
|
||||
@ -99,8 +109,14 @@ const useQuery = () => {
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState(null);
|
||||
//Собственное состояние - данные диаграммы
|
||||
const [queryDiagram, setQueryDiagram] = useState(null);
|
||||
|
||||
//Собственное состояние - данные настроек
|
||||
const [queryOption, setQueryOption] = useState(null);
|
||||
|
||||
//Собственное состояние - данные SQL-запроса
|
||||
const [querySQL, setQuerySQL] = useState(null);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
@ -108,51 +124,6 @@ const useQuery = () => {
|
||||
//Обновление данных
|
||||
const doRefresh = () => setRefresh(true);
|
||||
|
||||
//Добавление запроса
|
||||
const insertQuery = useCallback(
|
||||
async (code, name) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_INSERT", args: { SCODE: code, SNAME: name }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Изменение запроса
|
||||
const updateQuery = useCallback(
|
||||
async (query, code, name) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_UPDATE", args: { NRN: query, SCODE: code, SNAME: name }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Удаление запроса
|
||||
const deleteQuery = useCallback(
|
||||
async query => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_DELETE", args: { NRN: query }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Установка флага готовности запроса
|
||||
const setQueryReady = useCallback(
|
||||
async (query, ready) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_READY_SET", args: { NRN: query, NREADY: ready }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Установка флага публичности запроса
|
||||
const setQueryPbl = useCallback(
|
||||
async (query, pbl) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_PBL_SET", args: { NRN: query, NPBL: pbl }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
@ -160,107 +131,16 @@ const useQuery = () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.QUERY_LIST",
|
||||
respArg: "COUT",
|
||||
isArray: name => ["XQUERY"].includes(name),
|
||||
attributeValueProcessor: (name, val) => (["code", "name"].includes(name) ? undefined : val),
|
||||
loader: true
|
||||
});
|
||||
setData(data?.XQUERIES?.XQUERY || []);
|
||||
setInit(true);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
//Если надо обновить
|
||||
if (refresh)
|
||||
//Получим данные
|
||||
loadData();
|
||||
}, [refresh, executeStored]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, insertQuery, updateQuery, deleteQuery, setQueryReady, setQueryPbl, doRefresh, isLoading, isInit];
|
||||
};
|
||||
|
||||
//Работа с содержимым запроса
|
||||
const useQueryDesc = query => {
|
||||
//Собственное состояние - флаг инициализированности
|
||||
const [isInit, setInit] = useState(false);
|
||||
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Обновление данных
|
||||
const doRefresh = () => setRefresh(true);
|
||||
|
||||
//Добавление сущности в запрос
|
||||
const addEnt = useCallback(
|
||||
async (name, type) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_ADD", args: { NRN: query, SNAME: name, STYPE: type }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Удаление сущности из запроса
|
||||
const removeEnt = useCallback(
|
||||
async ent => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_REMOVE", args: { NRN: query, SID: ent }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Сохранение координат сущности на диаграммем
|
||||
const setEntPosition = useCallback(
|
||||
async (ent, x, y) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_ENT_POSITION_SET", args: { NRN: query, SID: ent, NX: x, NY: y }, loader: false });
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Добавление отношения сущностей в запрос
|
||||
const addRl = useCallback(
|
||||
async (source, target) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_RL_ADD", args: { NRN: query, SSOURCE: source, STARGET: target }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//Удаление отношения сущностей из запроса
|
||||
const removeRl = useCallback(
|
||||
async rl => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_QE.QUERY_RL_REMOVE", args: { NRN: query, SID: rl }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[query, executeStored]
|
||||
);
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_QE.QUERY_DESC",
|
||||
stored: "PKG_P8PANELS_QE.QUERY",
|
||||
args: { NRN: query },
|
||||
respArg: "COUT",
|
||||
isArray: name => ["XENT", "XATTR", "XRL"].includes(name),
|
||||
isArray: name => ["XENT", "XATTR", "XRL", "XARG"].includes(name),
|
||||
attributeValueProcessor: (name, val) => (["value"].includes(name) ? undefined : val),
|
||||
loader: true
|
||||
});
|
||||
setData(serverQueryData2QueryDiagram(data?.XENTS?.XENT || [], data?.XRLS?.XRL || []));
|
||||
setQueryDiagram(serverQueryData2QueryDiagram(data?.XENTS?.XENT || [], data?.XRLS?.XRL || []));
|
||||
setQueryOption({ args: data?.XOPT?.XARGS?.XARG || [], cond: data?.XOPT?.XCOND || null });
|
||||
setQuerySQL({ qry: data?.XQRY, qryMsg: data?.XQRY_MSG });
|
||||
setInit(true);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
@ -273,18 +153,22 @@ const useQueryDesc = query => {
|
||||
//Если есть для чего получать данные
|
||||
loadData();
|
||||
//Нет идентификатора запроса - нет данных
|
||||
else setData(null);
|
||||
else {
|
||||
setQueryDiagram(null);
|
||||
setQueryOption(null);
|
||||
setQuerySQL(null);
|
||||
}
|
||||
}, [refresh, query, executeStored]);
|
||||
|
||||
//При изменении входных свойств - поднимаем флаг обновления
|
||||
useEffect(() => setRefresh(true), [query]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, addEnt, removeEnt, setEntPosition, addRl, removeRl, doRefresh, isLoading, isInit];
|
||||
return [queryDiagram, queryOption, querySQL, doRefresh, isLoading, isInit];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useQuery, useQueryDesc };
|
||||
export { useQuery };
|
||||
|
||||
@ -7,22 +7,25 @@
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import { Box, Grid, Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import { Box, Grid } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст взаимодействия с приложением
|
||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Компоненты рабочего стола
|
||||
import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
|
||||
import { BUTTONS } from "../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { QueryDiagram } from "./components/query_diagram/query_diagram"; //Диаграмма запроса
|
||||
import { Inspector } from "./components/inspector/inspector"; //Инспектор свойств
|
||||
import { QueriesManager } from "./components/queries_manager/queries_manager"; //Менеджер запросов
|
||||
import { EntityAddDialog } from "./components/entity_add_dialog/entity_add_dialog"; //Диалог добавления сущности
|
||||
import { P8PEditorBox } from "../../components/editors/p8p_editor_box"; //Контейнер параметров редактора
|
||||
import { P8PEditorSubHeader } from "../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора
|
||||
import { useQueryDesc } from "./hooks"; //Пользовательские хуки
|
||||
import { useQuery } from "./hooks"; //Хуки для работы с метаданными запроса на сервере
|
||||
import { useQueryRelations } from "./components/inspector_query_rls/hooks"; //Хуки для работы со связями запроса на сервере
|
||||
import { useQueryEntities } from "./components/inspector_query_ents/hooks"; //Хуки для работы с сущностями запроса на сервере
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Заголовок панели по умолчанию
|
||||
const APP_BAR_TITLE_DEFAULT = "Редактор запросов";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex" },
|
||||
@ -39,26 +42,82 @@ const QueryEditor = () => {
|
||||
//Текущий запрос
|
||||
const [query, setQuery] = useState(null);
|
||||
|
||||
//Текущая сущность
|
||||
const [entity, setEntity] = useState(null);
|
||||
|
||||
//Текущая связь
|
||||
const [relation, setRelation] = useState(null);
|
||||
|
||||
//Отображения менеджера запросов
|
||||
const [openQueriesManager, setOpenQueriesManager] = useState(true);
|
||||
|
||||
//Отображение диалога добавления сущности
|
||||
const [openEntityAddDialog, setOpenEntityAddDialog] = useState(false);
|
||||
//Получение метаданных с описанием запроса
|
||||
const [queryDiagram, queryOption, querySQL, doRefresh] = useQuery(query);
|
||||
|
||||
//Получение данных запроса
|
||||
const [queryDiagram, addEnt, removeEnt, setEntPosition, addRl, removeRl] = useQueryDesc(query);
|
||||
//Работа с сущностями на сервере
|
||||
const { removeEnt, setEntPosition } = useQueryEntities(query);
|
||||
|
||||
//Работа со связями на сервере
|
||||
const { addRl, removeRl } = useQueryRelations(query);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { setAppBarTitle } = useContext(ApplicationСtx);
|
||||
|
||||
//Выбор сущности
|
||||
const selectEntity = ent => {
|
||||
setRelation(null);
|
||||
const queryEnt = queryDiagram.entities.find(e => e.id === ent);
|
||||
if (queryEnt) setEntity({ ...queryEnt });
|
||||
};
|
||||
|
||||
//Выбор связи
|
||||
const selectRelation = rl => {
|
||||
setEntity(null);
|
||||
const queryRl = queryDiagram.relations.find(r => r.id === rl);
|
||||
if (queryRl) setRelation({ ...queryRl });
|
||||
};
|
||||
|
||||
//Сброс выбора связи и сущности
|
||||
const cleanupEnRlSelection = () => {
|
||||
setRelation(null);
|
||||
setEntity(null);
|
||||
};
|
||||
|
||||
//Обработка изменения положения сущности на диаграмме
|
||||
const handleEntityPositionChange = (ent, position) => setEntPosition(ent, position.x, position.y);
|
||||
|
||||
//Обработка удаления сущности из запроса
|
||||
const handleEntityRemove = ent => removeEnt(ent);
|
||||
const handleEntityRemove = async ent => {
|
||||
await removeEnt(ent);
|
||||
if (entity && entity?.id === ent) cleanupEnRlSelection();
|
||||
doRefresh();
|
||||
};
|
||||
|
||||
//Обработка выделения сущности
|
||||
const handleEntityClick = ent => selectEntity(ent);
|
||||
|
||||
//Обработка выделения тарибута сущности
|
||||
const handleEntityAttrClick = ent => selectEntity(ent);
|
||||
|
||||
//Обработка выделения связи
|
||||
const handleRelationClick = rl => selectRelation(rl);
|
||||
|
||||
//Обработка добавления отношения cущностей
|
||||
const handleRelationAdd = (source, target) => addRl(source, target);
|
||||
const handleRelationAdd = async (source, target) => {
|
||||
cleanupEnRlSelection();
|
||||
await addRl(source, target);
|
||||
doRefresh();
|
||||
};
|
||||
|
||||
//Обработка удаления отношения cущностей
|
||||
const handleRelationRemove = rl => removeRl(rl);
|
||||
const handleRelationRemove = async rl => {
|
||||
await removeRl(rl);
|
||||
if (relation && relation?.id === rl) cleanupEnRlSelection();
|
||||
doRefresh();
|
||||
};
|
||||
|
||||
//При нажатии на панели (пустом месте) диаграммы запроса
|
||||
const handlePaneClick = () => cleanupEnRlSelection();
|
||||
|
||||
//Открытие менеджера запросов
|
||||
const handleOpenQueriesManager = () => setOpenQueriesManager(true);
|
||||
@ -67,26 +126,26 @@ const QueryEditor = () => {
|
||||
const handleCancelQueriesManager = () => setOpenQueriesManager(false);
|
||||
|
||||
//Закрытие запроса
|
||||
const handleQueryClose = () => setQuery(null);
|
||||
const handleQueryClose = () => {
|
||||
setAppBarTitle(APP_BAR_TITLE_DEFAULT);
|
||||
cleanupEnRlSelection();
|
||||
setQuery(null);
|
||||
};
|
||||
|
||||
//При выборе запроса
|
||||
const handleQuerySelect = query => {
|
||||
setQuery(query);
|
||||
const handleQuerySelect = ({ rn, name }) => {
|
||||
setAppBarTitle(`Запрос [${name}]`);
|
||||
setQuery(rn);
|
||||
setOpenQueriesManager(false);
|
||||
cleanupEnRlSelection();
|
||||
};
|
||||
|
||||
//При добавлении сущности в запрос
|
||||
const handleEntityAdd = () => setOpenEntityAddDialog(true);
|
||||
|
||||
//Закрытие диалога добавления сущности по "ОК"
|
||||
const handleEntityAddDialogOk = async values => {
|
||||
await addEnt(values.name, "VIEW");
|
||||
setOpenEntityAddDialog(false);
|
||||
//При изменении свойств запроса
|
||||
const handleQueryOptionsChanged = () => {
|
||||
cleanupEnRlSelection();
|
||||
doRefresh();
|
||||
};
|
||||
|
||||
//Закрытие диалога добавления сущности по "ОК"
|
||||
const handleEntityAddDialogCancel = () => setOpenEntityAddDialog(false);
|
||||
|
||||
//Панель инструмментов
|
||||
const toolBar = (
|
||||
<P8PEditorToolBar
|
||||
@ -101,28 +160,35 @@ const QueryEditor = () => {
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{openQueriesManager && <QueriesManager current={query} onQuerySelect={handleQuerySelect} onCancel={handleCancelQueriesManager} />}
|
||||
{openEntityAddDialog && <EntityAddDialog onOk={handleEntityAddDialogOk} onCancel={handleEntityAddDialogCancel} />}
|
||||
<Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
|
||||
<Grid item xs={20}>
|
||||
{queryDiagram && (
|
||||
<QueryDiagram
|
||||
{...queryDiagram}
|
||||
nodes={queryDiagram?.nodes}
|
||||
edges={queryDiagram?.edges}
|
||||
onEntityClick={handleEntityClick}
|
||||
onEntityAttrClick={handleEntityAttrClick}
|
||||
onEntityPositionChange={handleEntityPositionChange}
|
||||
onEntityRemove={handleEntityRemove}
|
||||
onRelactionClick={handleRelationClick}
|
||||
onRelationAdd={handleRelationAdd}
|
||||
onRelationRemove={handleRelationRemove}
|
||||
onPaneClick={handlePaneClick}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
|
||||
{toolBar}
|
||||
{query && (
|
||||
<P8PEditorBox title={"Параметры запроса"}>
|
||||
<P8PEditorSubHeader title={"Сущности"} />
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handleEntityAdd}>
|
||||
{BUTTONS.INSERT}
|
||||
</Button>
|
||||
</P8PEditorBox>
|
||||
<Inspector
|
||||
{...queryOption}
|
||||
{...querySQL}
|
||||
query={query}
|
||||
entity={entity}
|
||||
relation={relation}
|
||||
entities={queryDiagram?.entities}
|
||||
onOptionsChanged={handleQueryOptionsChanged}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@ -11,10 +11,11 @@ create table P8PNL_QE_QUERY
|
||||
CH_DATE date not null, -- Дата последнего изменения
|
||||
READY number(1) default 0 not null, -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||
PBL number(1) default 0 not null, -- Флаг публичности (0 - нет, 1 - да)
|
||||
OPTS clob, -- Параметры запроса
|
||||
OPT clob, -- Настройка запроса
|
||||
ENTS clob, -- Сущности запроса
|
||||
RLS clob, -- Отношения сущностей запроса
|
||||
QRY clob, -- Запрос
|
||||
QRY clob, -- Запрос (SQL выражение)
|
||||
QRY_MSG clob, -- Сообщение при формировании запроса (предупреждения и ошибки формирования)
|
||||
constraint C_P8PNL_QE_QUERY_RN_PK primary key (RN),
|
||||
constraint C_P8PNL_QE_QUERY_CODE_NB check (rtrim(CODE) is not null),
|
||||
constraint C_P8PNL_QE_QUERY_NAME_NB check (rtrim(NAME) is not null),
|
||||
|
||||
@ -1789,6 +1789,9 @@ create or replace package body PKG_P8PANELS_CLNTTSKBRD as
|
||||
)
|
||||
is
|
||||
NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Организация сеанса
|
||||
SEVENT PKG_STD.TSTRING; -- Полный номер события
|
||||
NCONFIRM_REQUIRED PKG_STD.TNUMBER; -- Флаг необходимости подтверждения перехода
|
||||
SCONFIRM_TEXT PKG_STD.TSTRING; -- Текст подтверждения перехода
|
||||
NTMP PKG_STD.TREF; -- Заглушка
|
||||
NSTEP_CURRENT PKG_STD.TREF; -- Номер текущего шага
|
||||
NNOTE_RN PKG_STD.TREF; -- Рег. номер добавленного примечания
|
||||
@ -1854,6 +1857,7 @@ create or replace package body PKG_P8PANELS_CLNTTSKBRD as
|
||||
NIDENT => NIDENT,
|
||||
NSTEP => NSTEP,
|
||||
NEVENT => NEVENT,
|
||||
SEVENT => SEVENT,
|
||||
SEVENT_TYPE => SEVENT_TYPE,
|
||||
SEVENT_STAT => SEVENT_STAT,
|
||||
SINIT_PERSON => SINIT_PERSON,
|
||||
@ -1863,6 +1867,8 @@ create or replace package body PKG_P8PANELS_CLNTTSKBRD as
|
||||
NPOINT => NPOINT,
|
||||
NPASS => NPASS,
|
||||
NSELECT_EXEC => NSELECT_EXEC,
|
||||
NCONFIRM_REQUIRED => NCONFIRM_REQUIRED,
|
||||
SCONFIRM_TEXT => SCONFIRM_TEXT,
|
||||
SEXECUTEMETHOD => SEXECUTEMETHOD,
|
||||
SSEND_CLIENT => SSEND_CLIENT,
|
||||
SSEND_DIVISION => SSEND_DIVISION,
|
||||
@ -1881,6 +1887,7 @@ create or replace package body PKG_P8PANELS_CLNTTSKBRD as
|
||||
NIDENT => NIDENT,
|
||||
NSTEP => NSTEP,
|
||||
NEVENT => NEVENT,
|
||||
SEVENT => SEVENT,
|
||||
SEVENT_TYPE => SEVENT_TYPE,
|
||||
SEVENT_STAT => SEVENT_STAT,
|
||||
SINIT_PERSON => SINIT_PERSON,
|
||||
@ -1894,6 +1901,8 @@ create or replace package body PKG_P8PANELS_CLNTTSKBRD as
|
||||
SEVENT_STAT => SEVENT_STAT,
|
||||
SNEXT_STAT => SNEXT_STAT),
|
||||
NSELECT_EXEC => NSELECT_EXEC,
|
||||
NCONFIRM_REQUIRED => NCONFIRM_REQUIRED,
|
||||
SCONFIRM_TEXT => SCONFIRM_TEXT,
|
||||
SEXECUTEMETHOD => SEXECUTEMETHOD,
|
||||
SSEND_CLIENT => SSEND_CLIENT,
|
||||
SSEND_DIVISION => SSEND_DIVISION,
|
||||
|
||||
@ -1,11 +1,25 @@
|
||||
create or replace package PKG_P8PANELS_QE as
|
||||
|
||||
/* Получение списка доступных сущностей */
|
||||
procedure ENTITY_LIST
|
||||
(
|
||||
SSEARCH in varchar2, -- Поисковый запрос
|
||||
COUT out clob -- Сериализованный список доступных сущностей
|
||||
);
|
||||
|
||||
/* Получение данных о запросе */
|
||||
procedure QUERY
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
COUT out clob -- Сериализованное описание запроса
|
||||
);
|
||||
|
||||
/* Получение списка запросов */
|
||||
procedure QUERY_LIST
|
||||
(
|
||||
COUT out clob -- Сериализованный список запросов
|
||||
);
|
||||
|
||||
|
||||
/* Добавление запроса */
|
||||
procedure QUERY_INSERT
|
||||
(
|
||||
@ -28,11 +42,18 @@ create or replace package PKG_P8PANELS_QE as
|
||||
NRN in number -- Рег. номер запроса
|
||||
);
|
||||
|
||||
/* Получение данных о запросе */
|
||||
procedure QUERY_DESC
|
||||
/* Установка признака "готовности" запроса */
|
||||
procedure QUERY_READY_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
COUT out clob -- Сериализованное описание запроса
|
||||
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||
);
|
||||
|
||||
/* Установка признака "публичности" запроса */
|
||||
procedure QUERY_PBL_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||
);
|
||||
|
||||
/* Добавление сущности в запрос */
|
||||
@ -59,6 +80,22 @@ create or replace package PKG_P8PANELS_QE as
|
||||
NY in number -- Координата по оси ординат
|
||||
);
|
||||
|
||||
/* Получение состава атрибутов сущности */
|
||||
procedure QUERY_ENT_ATTRS_GET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2, -- Идентификатор сущности
|
||||
COUT out clob -- Сериализованное описание атрибутов сущности
|
||||
);
|
||||
|
||||
/* Установка состава атрибутов сущности */
|
||||
procedure QUERY_ENT_ATTRS_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2, -- Идентификатор сущности
|
||||
CATTRS in clob -- Сериализованное описание атрибутов сущности (BASE64 XML)
|
||||
);
|
||||
|
||||
/* Добавление связи в запрос */
|
||||
procedure QUERY_RL_ADD
|
||||
(
|
||||
@ -74,24 +111,86 @@ create or replace package PKG_P8PANELS_QE as
|
||||
SID in varchar2 -- Идентификатор связи
|
||||
);
|
||||
|
||||
/* Установка признака "готовности" запроса */
|
||||
procedure QUERY_READY_SET
|
||||
/* Установка признака обязательности связи */
|
||||
procedure QUERY_RL_MANDATORY_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2, -- Идентификатор связи
|
||||
NMANDATORY in number -- Флаг обязательности (1 - да, 0 - нет)
|
||||
);
|
||||
|
||||
/* Установка признака "публичности" запроса */
|
||||
procedure QUERY_PBL_SET
|
||||
/* Добавление аргумента запроса */
|
||||
procedure QUERY_OPT_ARG_ADD
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SNAME in varchar2, -- Имя
|
||||
STITLE in varchar2, -- Заголовок
|
||||
NDATA_TYPE in number, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
|
||||
NMANDATORY in number, -- Флаг обязательности (1 - да, 0 - нет)
|
||||
SVALUE in varchar2 -- Значение для отладки (строковое представление для всех типов данных)
|
||||
);
|
||||
|
||||
/* Изменение аргумента запроса */
|
||||
procedure QUERY_OPT_ARG_EDIT
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SNAME in varchar2, -- Имя
|
||||
STITLE in varchar2, -- Заголовок
|
||||
NDATA_TYPE in number, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
|
||||
NMANDATORY in number, -- Флаг обязательности (1 - да, 0 - нет)
|
||||
SVALUE in varchar2 -- Значение для отладки (строковое представление для всех типов данных)
|
||||
);
|
||||
|
||||
/* Удаление аргумента запроса */
|
||||
procedure QUERY_OPT_ARG_REMOVE
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||
SNAME in varchar2 -- Имя
|
||||
);
|
||||
|
||||
/* Установка условий отбора запроса */
|
||||
procedure QUERY_OPT_COND_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SCOND in varchar2 -- Условия отбора
|
||||
);
|
||||
|
||||
end PKG_P8PANELS_QE;
|
||||
/
|
||||
create or replace package body PKG_P8PANELS_QE as
|
||||
|
||||
/* Константы - Минимальная длина поисковой фразы для сущностей */
|
||||
NENT_MIN_SEARCH_LEN constant PKG_STD.TNUMBER := 3; -- Количество символов
|
||||
|
||||
/* Получение списка доступных сущностей */
|
||||
procedure ENTITY_LIST
|
||||
(
|
||||
SSEARCH in varchar2, -- Поисковый запрос
|
||||
COUT out clob -- Сериализованный список доступных сущностей
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Если фильтр задан и он нас утраивает */
|
||||
if ((SSEARCH is not null) and (LENGTH(SSEARCH) >= NENT_MIN_SEARCH_LEN)) then
|
||||
/* Получим список доступных сущностей */
|
||||
COUT := PKG_P8PANELS_QE_BASE.ENTITY_LIST(SSEARCH => SSEARCH, NBRIEF => 1, NINCLUDE_ATTRS => 0);
|
||||
end if;
|
||||
end ENTITY_LIST;
|
||||
|
||||
/* Получение описания запроса */
|
||||
procedure QUERY
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
COUT out clob -- Сериализованное описание запроса
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Получим описание запроса */
|
||||
COUT := PKG_P8PANELS_QE_BASE.QUERY(NRN => NRN);
|
||||
end QUERY;
|
||||
|
||||
/* Получение списка запросов */
|
||||
procedure QUERY_LIST
|
||||
(
|
||||
@ -100,7 +199,7 @@ create or replace package body PKG_P8PANELS_QE as
|
||||
is
|
||||
begin
|
||||
/* Базово сформируем список */
|
||||
COUT := PKG_P8PANELS_QE_BASE.QUERY_LIST_GET(SUSER => UTILIZER());
|
||||
COUT := PKG_P8PANELS_QE_BASE.QUERY_LIST(SUSER => UTILIZER());
|
||||
end QUERY_LIST;
|
||||
|
||||
/* Добавление запроса */
|
||||
@ -144,172 +243,6 @@ create or replace package body PKG_P8PANELS_QE as
|
||||
PKG_P8PANELS_QE_BASE.QUERY_DELETE(NRN => NRN);
|
||||
end QUERY_DELETE;
|
||||
|
||||
/* Получение описания запроса */
|
||||
procedure QUERY_DESC
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
COUT out clob -- Сериализованное описание запроса
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Получим описание запроса */
|
||||
COUT := PKG_P8PANELS_QE_BASE.QUERY_DESC_GET(NRN => NRN);
|
||||
end QUERY_DESC;
|
||||
|
||||
/* Добавление сущности в запрос */
|
||||
procedure QUERY_ENT_ADD
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SNAME in varchar2, -- Имя
|
||||
STYPE in varchar2 -- Тип (см. константы SENT_TYPE_*)
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие сущности */
|
||||
RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||
/* Формируем описание новой сущности и добавляем её в коллекцию */
|
||||
PKG_P8PANELS_QE_BASE.TENTS_APPEND(RENTS => RENTS, SNAME => SNAME, STYPE => STYPE);
|
||||
/* Сохраняем обновленный набор сущностей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS);
|
||||
end QUERY_ENT_ADD;
|
||||
|
||||
/* Удаление сущности из запроса */
|
||||
procedure QUERY_ENT_REMOVE
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2 -- Идентификатор сущности
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||
RENT PKG_P8PANELS_QE_BASE.TENT; -- Удаляемая сущность
|
||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||
RRLS_TMP PKG_P8PANELS_QE_BASE.TRLS; -- Буфер для коллекции удаляемых связей
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие сущности */
|
||||
RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||
/* Читаем свзяи */
|
||||
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||
/* Находим удаляемую сущность */
|
||||
begin
|
||||
RENT := RENTS(PKG_P8PANELS_QE_BASE.TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID));
|
||||
exception
|
||||
when VALUE_ERROR then
|
||||
P_EXCEPTION(0,
|
||||
'Сущность с идентификатором "%s" в запросе "%s" не определена.',
|
||||
COALESCE(SID, '<НЕ УКАЗАН>'),
|
||||
TO_CHAR(NRN));
|
||||
end;
|
||||
/* Удаляем сущность из коллекции */
|
||||
PKG_P8PANELS_QE_BASE.TENTS_REMOVE(RENTS => RENTS, SID => SID);
|
||||
/* Обходим атрибуты сущности */
|
||||
if ((RENT.RATTRS is not null) and (RENT.RATTRS.COUNT > 0)) then
|
||||
for I in RENT.RATTRS.FIRST .. RENT.RATTRS.LAST
|
||||
loop
|
||||
/* Если атрибут есть в связях (как источник или как приёмник) */
|
||||
for J in 0 .. 1
|
||||
loop
|
||||
RRLS_TMP := PKG_P8PANELS_QE_BASE.TRLS_LIST_BY_ST(RRLS => RRLS,
|
||||
SSOURCE_TARGET => RENT.RATTRS(I).SID,
|
||||
NLIST_TYPE => J);
|
||||
/* То связь должна быть удалена */
|
||||
if ((RRLS_TMP is not null) and (RRLS_TMP.COUNT > 0)) then
|
||||
for K in RRLS_TMP.FIRST .. RRLS_TMP.LAST
|
||||
loop
|
||||
PKG_P8PANELS_QE_BASE.TRLS_REMOVE(RRLS => RRLS, SID => RRLS_TMP(K).SID);
|
||||
end loop;
|
||||
end if;
|
||||
end loop;
|
||||
end loop;
|
||||
end if;
|
||||
/* Сохраняем обновленный набор сущностей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS);
|
||||
/* Сохраняем обновленный набор связей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||
end QUERY_ENT_REMOVE;
|
||||
|
||||
/* Установка координат сущности в редакторе диаграммы запроса */
|
||||
procedure QUERY_ENT_POSITION_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2, -- Идентификатор сущности
|
||||
NX in number, -- Координата по оси абсцисс
|
||||
NY in number -- Координата по оси ординат
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||
begin
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Сохранять расположение будем только если это автор - так остальные могут перемещать на экране диаграмму, но не запоминать расположение сущностей */
|
||||
if (RQ.AUTHOR = UTILIZER()) then
|
||||
/* Читаем существующие сущности */
|
||||
RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||
/* Меняем координаты сущности в коллекции */
|
||||
PKG_P8PANELS_QE_BASE.TENTS_POSITION_SET(RENTS => RENTS, SID => SID, NX => NX, NY => NY);
|
||||
/* Сохраняем обновленный набор сущностей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS);
|
||||
end if;
|
||||
end QUERY_ENT_POSITION_SET;
|
||||
|
||||
/* Добавление связи в запрос */
|
||||
procedure QUERY_RL_ADD
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SSOURCE in varchar2, -- Идентификатор атрибута-источника
|
||||
STARGET in varchar2 -- Идентификатор атрибута-приёмника
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие связи */
|
||||
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||
/* Формируем описание новой связи и добавляем её в коллекцию */
|
||||
PKG_P8PANELS_QE_BASE.TRLS_APPEND(RRLS => RRLS, SSOURCE => SSOURCE, STARGET => STARGET);
|
||||
/* Сохраняем обновленный набор связей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||
end QUERY_RL_ADD;
|
||||
|
||||
/* Удаление связи из запроса */
|
||||
procedure QUERY_RL_REMOVE
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2 -- Идентификатор связи
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие связи */
|
||||
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||
/* Удаляем связи из коллекции */
|
||||
PKG_P8PANELS_QE_BASE.TRLS_REMOVE(RRLS => RRLS, SID => SID);
|
||||
/* Сохраняем обновленный набор связей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||
end QUERY_RL_REMOVE;
|
||||
|
||||
/* Установка признака "готовности" запроса */
|
||||
procedure QUERY_READY_SET
|
||||
(
|
||||
@ -337,6 +270,401 @@ create or replace package body PKG_P8PANELS_QE as
|
||||
/* Базовая установка признака публичности */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_PBL_SET(NRN => NRN, NPBL => NPBL);
|
||||
end QUERY_PBL_SET;
|
||||
|
||||
/* Добавление сущности в запрос */
|
||||
procedure QUERY_ENT_ADD
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SNAME in varchar2, -- Имя
|
||||
STYPE in varchar2 -- Тип (см. константы SENT_TYPE_*)
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||
SQRY PKG_STD.TLSTRING; -- SQL-выражение запроса
|
||||
SQRY_MSG PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие сущности */
|
||||
RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||
/* Формируем описание новой сущности и добавляем её в коллекцию */
|
||||
PKG_P8PANELS_QE_BASE.TENTS_ADD(RENTS => RENTS, SNAME => SNAME, STYPE => STYPE);
|
||||
/* Сохраняем обновленный набор сущностей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS);
|
||||
/* Переформируем SQL-выражение */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_SQL_BUILD(NRN => RQ.RN, SQRY => SQRY, SQRY_MSG => SQRY_MSG);
|
||||
PKG_P8PANELS_QE_BASE.QUERY_QRY_SET(NRN => RQ.RN, CQRY => SQRY, CQRY_MSG => SQRY_MSG);
|
||||
end QUERY_ENT_ADD;
|
||||
|
||||
/* Удаление сущности из запроса */
|
||||
procedure QUERY_ENT_REMOVE
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2 -- Идентификатор сущности
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||
RENT PKG_P8PANELS_QE_BASE.TENT; -- Удаляемая сущность
|
||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||
SQRY PKG_STD.TLSTRING; -- SQL-выражение запроса
|
||||
SQRY_MSG PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие сущности */
|
||||
RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||
/* Читаем свзяи */
|
||||
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||
/* Находим удаляемую сущность */
|
||||
begin
|
||||
RENT := RENTS(PKG_P8PANELS_QE_BASE.TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID));
|
||||
exception
|
||||
when VALUE_ERROR then
|
||||
P_EXCEPTION(0,
|
||||
'Сущность с идентификатором "%s" в запросе "%s" не определена.',
|
||||
COALESCE(SID, '<НЕ УКАЗАН>'),
|
||||
TO_CHAR(NRN));
|
||||
end;
|
||||
/* Удаляем сущность из коллекции */
|
||||
PKG_P8PANELS_QE_BASE.TENTS_REMOVE(RENTS => RENTS, SID => SID);
|
||||
/* Обходим атрибуты сущности */
|
||||
if ((RENT.RATTRS is not null) and (RENT.RATTRS.COUNT > 0)) then
|
||||
for I in RENT.RATTRS.FIRST .. RENT.RATTRS.LAST
|
||||
loop
|
||||
/* Удаляем связи в которых он задействован */
|
||||
PKG_P8PANELS_QE_BASE.TRLS_CLEANUP_BY_ATTR(RRLS => RRLS, SATTR_ID => RENT.RATTRS(I).SID);
|
||||
end loop;
|
||||
end if;
|
||||
/* Сохраняем обновленный набор сущностей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS);
|
||||
/* Сохраняем обновленный набор связей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||
/* Переформируем SQL-выражение */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_SQL_BUILD(NRN => RQ.RN, SQRY => SQRY, SQRY_MSG => SQRY_MSG);
|
||||
PKG_P8PANELS_QE_BASE.QUERY_QRY_SET(NRN => RQ.RN, CQRY => SQRY, CQRY_MSG => SQRY_MSG);
|
||||
end QUERY_ENT_REMOVE;
|
||||
|
||||
/* Установка координат сущности в редакторе диаграммы запроса */
|
||||
procedure QUERY_ENT_POSITION_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2, -- Идентификатор сущности
|
||||
NX in number, -- Координата по оси абсцисс
|
||||
NY in number -- Координата по оси ординат
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||
begin
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Сохранять расположение будем только если это автор - так остальные могут перемещать на экране диаграмму, но не запоминать расположение сущностей */
|
||||
if (RQ.AUTHOR = UTILIZER()) then
|
||||
/* Читаем существующие сущности */
|
||||
RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||
/* Меняем координаты сущности в коллекции */
|
||||
PKG_P8PANELS_QE_BASE.TENTS_POSITION_SET(RENTS => RENTS, SID => SID, NX => NX, NY => NY);
|
||||
/* Сохраняем обновленный набор сущностей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS);
|
||||
end if;
|
||||
end QUERY_ENT_POSITION_SET;
|
||||
|
||||
/* Получение состава атрибутов сущности */
|
||||
procedure QUERY_ENT_ATTRS_GET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2, -- Идентификатор сущности
|
||||
COUT out clob -- Сериализованное описание атрибутов сущности
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Сформируем описание атрибутов */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ENT_ATTRS_GET(NRN => NRN, SID => SID, COUT => COUT);
|
||||
end QUERY_ENT_ATTRS_GET;
|
||||
|
||||
/* Установка состава атрибутов сущности */
|
||||
procedure QUERY_ENT_ATTRS_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2, -- Идентификатор сущности
|
||||
CATTRS in clob -- Сериализованное описание атрибутов сущности (BASE64 XML)
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RENTS PKG_P8PANELS_QE_BASE.TENTS; -- Коллекция существующих сущностей
|
||||
NENT_INDEX PKG_STD.TNUMBER; -- Индекс изменяемой сущности
|
||||
RATTRS PKG_P8PANELS_QE_BASE.TATTRS; -- Коллекция полученных атриубтов
|
||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||
SCHECK_MSG PKG_STD.TLSTRING; -- Сообщение при проверке атрибутов
|
||||
SQRY PKG_STD.TLSTRING; -- SQL-выражение запроса
|
||||
SQRY_MSG PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие сущности */
|
||||
RENTS := PKG_P8PANELS_QE_BASE.QUERY_ENTS_GET(CENTS => RQ.ENTS);
|
||||
/* Читаем свзяи */
|
||||
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||
/* Находим изменяемую сущность */
|
||||
NENT_INDEX := PKG_P8PANELS_QE_BASE.TENTS_INDEX_BY_ID(RENTS => RENTS, SID => SID);
|
||||
if (NENT_INDEX is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Сущность с идентификатором "%s" в запросе "%s" не определена.',
|
||||
COALESCE(SID, '<НЕ УКАЗАН>'),
|
||||
TO_CHAR(NRN));
|
||||
end if;
|
||||
/* Десериализуем новый набор атрибутов */
|
||||
RATTRS := PKG_P8PANELS_QE_BASE.TATTRS_FROM_XML_BASE64(CXML => CATTRS,
|
||||
SCHARSET => PKG_CHARSET.CHARSET_UTF_(),
|
||||
BADD_ROOT => true);
|
||||
/* Сбросим текущие атрибуты сущности */
|
||||
RENTS(NENT_INDEX).RATTRS := PKG_P8PANELS_QE_BASE.TATTRS();
|
||||
/* Наполним полученными и зачистим связи */
|
||||
if (RATTRS is not null) and (RATTRS.COUNT > 0) then
|
||||
for I in RATTRS.FIRST .. RATTRS.LAST
|
||||
loop
|
||||
/* Если атрибут используется - кладём в обновленную коллекцию атрибутов сущности */
|
||||
if (RATTRS(I).NUSE = 1) then
|
||||
RENTS(NENT_INDEX).RATTRS.EXTEND();
|
||||
RENTS(NENT_INDEX).RATTRS(RENTS(NENT_INDEX).RATTRS.LAST) := RATTRS(I);
|
||||
else
|
||||
/* Атрибут не используется - необходимо очистись связи в которых он задействован */
|
||||
PKG_P8PANELS_QE_BASE.TRLS_CLEANUP_BY_ATTR(RRLS => RRLS, SATTR_ID => RATTRS(I).SID);
|
||||
end if;
|
||||
end loop;
|
||||
end if;
|
||||
/* Проверим, что новый набор атрибутов сущности корректен */
|
||||
PKG_P8PANELS_QE_BASE.TATTRS_CHECK(RATTRS => RENTS(NENT_INDEX).RATTRS, SCHECK_MSG => SCHECK_MSG);
|
||||
if (SCHECK_MSG is not null) then
|
||||
P_EXCEPTION(0, SCHECK_MSG);
|
||||
end if;
|
||||
/* Сохраняем обновленный набор сущностей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ENTS_SET(NRN => RQ.RN, RENTS => RENTS);
|
||||
/* Сохраняем обновленный набор связей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||
/* Переформируем SQL-выражение */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_SQL_BUILD(NRN => RQ.RN, SQRY => SQRY, SQRY_MSG => SQRY_MSG);
|
||||
PKG_P8PANELS_QE_BASE.QUERY_QRY_SET(NRN => RQ.RN, CQRY => SQRY, CQRY_MSG => SQRY_MSG);
|
||||
end QUERY_ENT_ATTRS_SET;
|
||||
|
||||
/* Добавление связи в запрос */
|
||||
procedure QUERY_RL_ADD
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SSOURCE in varchar2, -- Идентификатор атрибута-источника
|
||||
STARGET in varchar2 -- Идентификатор атрибута-приёмника
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||
SQRY PKG_STD.TLSTRING; -- SQL-выражение запроса
|
||||
SQRY_MSG PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие связи */
|
||||
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||
/* Формируем описание новой связи и добавляем её в коллекцию */
|
||||
PKG_P8PANELS_QE_BASE.TRLS_ADD(RRLS => RRLS, SSOURCE => SSOURCE, STARGET => STARGET, NMANDATORY => 0);
|
||||
/* Сохраняем обновленный набор связей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||
/* Переформируем SQL-выражение */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_SQL_BUILD(NRN => RQ.RN, SQRY => SQRY, SQRY_MSG => SQRY_MSG);
|
||||
PKG_P8PANELS_QE_BASE.QUERY_QRY_SET(NRN => RQ.RN, CQRY => SQRY, CQRY_MSG => SQRY_MSG);
|
||||
end QUERY_RL_ADD;
|
||||
|
||||
/* Удаление связи из запроса */
|
||||
procedure QUERY_RL_REMOVE
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2 -- Идентификатор связи
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||
SQRY PKG_STD.TLSTRING; -- SQL-выражение запроса
|
||||
SQRY_MSG PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие связи */
|
||||
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||
/* Удаляем связи из коллекции */
|
||||
PKG_P8PANELS_QE_BASE.TRLS_REMOVE(RRLS => RRLS, SID => SID);
|
||||
/* Сохраняем обновленный набор связей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||
/* Переформируем SQL-выражение */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_SQL_BUILD(NRN => RQ.RN, SQRY => SQRY, SQRY_MSG => SQRY_MSG);
|
||||
PKG_P8PANELS_QE_BASE.QUERY_QRY_SET(NRN => RQ.RN, CQRY => SQRY, CQRY_MSG => SQRY_MSG);
|
||||
end QUERY_RL_REMOVE;
|
||||
|
||||
/* Установка признака обязательности связи */
|
||||
procedure QUERY_RL_MANDATORY_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SID in varchar2, -- Идентификатор связи
|
||||
NMANDATORY in number -- Флаг обязательности (1 - да, 0 - нет)
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
RRLS PKG_P8PANELS_QE_BASE.TRLS; -- Коллекция существующих связей
|
||||
SQRY PKG_STD.TLSTRING; -- SQL-выражение запроса
|
||||
SQRY_MSG PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
|
||||
begin
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем существующие связи */
|
||||
RRLS := PKG_P8PANELS_QE_BASE.QUERY_RLS_GET(CRLS => RQ.RLS);
|
||||
/* Устанавливаем признакобязательности */
|
||||
PKG_P8PANELS_QE_BASE.TRLS_MANDATORY_SET(RRLS => RRLS, SID => SID, NMANDATORY => NMANDATORY);
|
||||
/* Сохраняем обновленный набор связей */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_RLS_SET(NRN => RQ.RN, RRLS => RRLS);
|
||||
/* Переформируем SQL-выражение */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_SQL_BUILD(NRN => RQ.RN, SQRY => SQRY, SQRY_MSG => SQRY_MSG);
|
||||
PKG_P8PANELS_QE_BASE.QUERY_QRY_SET(NRN => RQ.RN, CQRY => SQRY, CQRY_MSG => SQRY_MSG);
|
||||
end QUERY_RL_MANDATORY_SET;
|
||||
|
||||
/* Добавление аргумента запроса */
|
||||
procedure QUERY_OPT_ARG_ADD
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SNAME in varchar2, -- Имя
|
||||
STITLE in varchar2, -- Заголовок
|
||||
NDATA_TYPE in number, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
|
||||
NMANDATORY in number, -- Флаг обязательности (1 - да, 0 - нет)
|
||||
SVALUE in varchar2 -- Значение для отладки (строковое представление для всех типов данных)
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
ROPT PKG_P8PANELS_QE_BASE.TOPT; -- Настройка запроса
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем текущую настройку */
|
||||
ROPT := PKG_P8PANELS_QE_BASE.QUERY_OPT_GET(COPT => RQ.OPT);
|
||||
/* Добавляем аргумент в коллекцию */
|
||||
PKG_P8PANELS_QE_BASE.TARGS_ADD(RARGS => ROPT.RARGS,
|
||||
SNAME => SNAME,
|
||||
STITLE => STITLE,
|
||||
NDATA_TYPE => NDATA_TYPE,
|
||||
NMANDATORY => NMANDATORY,
|
||||
SVALUE => SVALUE);
|
||||
/* Сохраняем обновленную настройку */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_OPT_SET(NRN => RQ.RN, ROPT => ROPT);
|
||||
end QUERY_OPT_ARG_ADD;
|
||||
|
||||
/* Изменение аргумента запроса */
|
||||
procedure QUERY_OPT_ARG_EDIT
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SNAME in varchar2, -- Имя
|
||||
STITLE in varchar2, -- Заголовок
|
||||
NDATA_TYPE in number, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
|
||||
NMANDATORY in number, -- Флаг обязательности (1 - да, 0 - нет)
|
||||
SVALUE in varchar2 -- Значение для отладки (строковое представление для всех типов данных)
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
ROPT PKG_P8PANELS_QE_BASE.TOPT; -- Настройка запроса
|
||||
NINDEX PKG_STD.TNUMBER; -- Индекс изменяемого аргумента в коллекции
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем текущую настройку */
|
||||
ROPT := PKG_P8PANELS_QE_BASE.QUERY_OPT_GET(COPT => RQ.OPT);
|
||||
/* Находим изменяемый аргумент */
|
||||
NINDEX := PKG_P8PANELS_QE_BASE.TARGS_INDEX_BY_NAME(RARGS => ROPT.RARGS, SNAME => SNAME);
|
||||
if (NINDEX is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Аргумент с именем "%s" в запросе "%s" не определен.',
|
||||
COALESCE(SNAME, '<НЕ УКАЗАН>'),
|
||||
TO_CHAR(NRN));
|
||||
end if;
|
||||
/* Изменяем аргумент в коллекции */
|
||||
PKG_P8PANELS_QE_BASE.TARGS_EDIT(RARGS => ROPT.RARGS,
|
||||
NINDEX => NINDEX,
|
||||
SNAME => SNAME,
|
||||
STITLE => STITLE,
|
||||
NDATA_TYPE => NDATA_TYPE,
|
||||
NMANDATORY => NMANDATORY,
|
||||
SVALUE => SVALUE);
|
||||
/* Сохраняем обновленную настройку */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_OPT_SET(NRN => RQ.RN, ROPT => ROPT);
|
||||
end QUERY_OPT_ARG_EDIT;
|
||||
|
||||
/* Удаление аргумента запроса */
|
||||
procedure QUERY_OPT_ARG_REMOVE
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SNAME in varchar2 -- Имя
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
ROPT PKG_P8PANELS_QE_BASE.TOPT; -- Настройка запроса
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем текущую настройку */
|
||||
ROPT := PKG_P8PANELS_QE_BASE.QUERY_OPT_GET(COPT => RQ.OPT);
|
||||
/* Находим удаляемый аргумент */
|
||||
if (PKG_P8PANELS_QE_BASE.TARGS_INDEX_BY_NAME(RARGS => ROPT.RARGS, SNAME => SNAME) is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Аргумент с именем "%s" в запросе "%s" не определен.',
|
||||
COALESCE(SNAME, '<НЕ УКАЗАН>'),
|
||||
TO_CHAR(NRN));
|
||||
end if;
|
||||
/* Удаляем аргумент из коллекции */
|
||||
PKG_P8PANELS_QE_BASE.TARGS_REMOVE(RARGS => ROPT.RARGS, SNAME => SNAME);
|
||||
/* Сохраняем обновленную настройку */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_OPT_SET(NRN => RQ.RN, ROPT => ROPT);
|
||||
end QUERY_OPT_ARG_REMOVE;
|
||||
|
||||
/* Установка условий отбора запроса */
|
||||
procedure QUERY_OPT_COND_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер запроса
|
||||
SCOND in varchar2 -- Условия отбора
|
||||
)
|
||||
is
|
||||
RQ P8PNL_QE_QUERY%rowtype; -- Запись запроса
|
||||
ROPT PKG_P8PANELS_QE_BASE.TOPT; -- Настройка запроса
|
||||
SQRY PKG_STD.TLSTRING; -- SQL-выражение запроса
|
||||
SQRY_MSG PKG_STD.TLSTRING; -- Сообщение при формировании SQL-выражения
|
||||
begin
|
||||
/* Провим права доступа */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Читаем запись запроса */
|
||||
RQ := PKG_P8PANELS_QE_BASE.QUERY_GET(NRN => NRN);
|
||||
/* Читаем текущую настройку */
|
||||
ROPT := PKG_P8PANELS_QE_BASE.QUERY_OPT_GET(COPT => RQ.OPT);
|
||||
/* Установим новые условия отбора */
|
||||
ROPT.SCOND := SCOND;
|
||||
/* Сохраняем обновленную настройку */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_OPT_SET(NRN => RQ.RN, ROPT => ROPT);
|
||||
/* Переформируем SQL-выражение */
|
||||
PKG_P8PANELS_QE_BASE.QUERY_SQL_BUILD(NRN => RQ.RN, SQRY => SQRY, SQRY_MSG => SQRY_MSG);
|
||||
PKG_P8PANELS_QE_BASE.QUERY_QRY_SET(NRN => RQ.RN, CQRY => SQRY, CQRY_MSG => SQRY_MSG);
|
||||
end QUERY_OPT_COND_SET;
|
||||
|
||||
end PKG_P8PANELS_QE;
|
||||
/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user