forked from CITKParus/P8-Panels
ЦИТК-978 - Доработка панели "Редактор панелей"
This commit is contained in:
parent
b7f9daa258
commit
b49e0e9a80
121
app/components/editors/p8p_actions.js
Normal file
121
app/components/editors/p8p_actions.js
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Действия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PCAEditor } from "./p8p_component_action/editor"; //Редактор действия
|
||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL, P8P_CA_TYPE } from "./p8p_component_action/common"; //Общие ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
const getActionChipText = (type, params) => {
|
||||
//Определяем от типа
|
||||
switch (type) {
|
||||
//Открыть раздел
|
||||
case P8P_CA_TYPE.openUnit.code:
|
||||
return params.unitName;
|
||||
//Открыть панель
|
||||
case P8P_CA_TYPE.openPanel.code:
|
||||
return params.panelValue.value;
|
||||
//Установить переменную
|
||||
case P8P_CA_TYPE.setVariable.code:
|
||||
return params.reduce((prev, cur) => [...prev, cur.variableSource], []).join(", ");
|
||||
//Для всех остальных
|
||||
default:
|
||||
return "Не определен";
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Действия
|
||||
const P8PActions = ({ actions = P8P_CAS_INITIAL, valueProviders = {}, areas = [], valueTypes = [], onChange = null } = {}) => {
|
||||
//Собственное состояние - редактор действий
|
||||
const [actionEditor, setActionEditor] = useState({ display: false, index: null });
|
||||
|
||||
//При изменении действий
|
||||
const handleActionsChange = actions => onChange && onChange(actions);
|
||||
|
||||
//При добавлении действия
|
||||
const handleActionAdd = () => setActionEditor({ display: true, index: null });
|
||||
|
||||
//При нажатии на действие
|
||||
const handleActionClick = i => setActionEditor({ display: true, index: i });
|
||||
|
||||
//При удалении действия
|
||||
const handleActionDelete = i => {
|
||||
const newActions = [...actions];
|
||||
newActions.splice(i, 1);
|
||||
handleActionsChange(newActions);
|
||||
};
|
||||
|
||||
//При отмене сохранения изменений действия
|
||||
const handleActionCancel = () => setActionEditor({ display: false, index: null });
|
||||
|
||||
//При сохранении изменений действия
|
||||
const handleActionSave = action => {
|
||||
const newActions = [...actions];
|
||||
actionEditor.index == null ? newActions.push({ ...action }) : (newActions[actionEditor.index] = { ...action });
|
||||
handleActionsChange(newActions);
|
||||
setActionEditor({ display: false, index: null });
|
||||
};
|
||||
|
||||
//Определяем структуру действий для отображения
|
||||
const actionChips = actions.map(item => {
|
||||
//Собираем текст действия
|
||||
let text = getActionChipText(item.type, item.params);
|
||||
//Формируем структуру для отображения карточки действия
|
||||
return { text: text, title: text, icon: P8P_CA_TYPE[item.type]?.icon, iconTitle: P8P_CA_TYPE[item.type]?.name };
|
||||
});
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{actionEditor.display && (
|
||||
<P8PCAEditor
|
||||
areas={areas}
|
||||
valueTypes={valueTypes}
|
||||
action={actionEditor.index !== null ? { ...actions[actionEditor.index] } : null}
|
||||
onCancel={handleActionCancel}
|
||||
onOk={handleActionSave}
|
||||
valueProviders={valueProviders}
|
||||
/>
|
||||
)}
|
||||
<P8PChipList items={actionChips} onClick={handleActionClick} onDelete={handleActionDelete} />
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handleActionAdd}>
|
||||
Добавить действие
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - действия
|
||||
P8PActions.propTypes = {
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
valueProviders: PropTypes.object,
|
||||
areas: PropTypes.array,
|
||||
valueTypes: PropTypes.array,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PActions };
|
||||
88
app/components/editors/p8p_chip_list.js
Normal file
88
app/components/editors/p8p_chip_list.js
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Список элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Chip, Typography, Stack, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||
import { STYLES as COMMON_STYLES } from "./p8p_editors_common"; //Общие ресурсы редакторов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CHIP_ITEM: { ...COMMON_STYLES.CHIP(true, false) },
|
||||
STACK_ITEM: { alignItems: "center" },
|
||||
TYPOGRAPHY_INFO: { overflow: "hidden" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Список элементов
|
||||
const P8PChipList = ({ items, labelRender, onClick, onDelete }) => {
|
||||
//При нажатии на элемент
|
||||
const handleClick = index => onClick && onClick(index);
|
||||
|
||||
//При удалении элемента
|
||||
const handleDelete = index => onDelete && onDelete(index);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{Array.isArray(items) &&
|
||||
items.length > 0 &&
|
||||
items.map((item, i) => (
|
||||
<Chip
|
||||
key={i}
|
||||
label={
|
||||
labelRender ? (
|
||||
labelRender(item)
|
||||
) : (
|
||||
<Stack direction="row" spacing={1} sx={STYLES.STACK_ITEM}>
|
||||
{item.icon ? (
|
||||
<>
|
||||
<Icon sx={{ color: "grey" }} title={item?.iconTitle}>
|
||||
{item.icon}
|
||||
</Icon>
|
||||
<Typography variant="body2">-</Typography>
|
||||
</>
|
||||
) : null}
|
||||
<Typography variant="body2" title={item?.title} sx={STYLES.TYPOGRAPHY_INFO}>
|
||||
{item.text}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
variant={"outlined"}
|
||||
onClick={onClick && !item.disableClick ? () => handleClick(i) : null}
|
||||
onDelete={onDelete && !item.disableDelete ? () => handleDelete(i) : null}
|
||||
deleteIcon={item.deleteIcon ? <Icon>{item.deleteIcon}</Icon> : null}
|
||||
sx={STYLES.CHIP_ITEM}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - список элементов
|
||||
P8PChipList.propTypes = {
|
||||
items: PropTypes.array,
|
||||
labelRender: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PChipList };
|
||||
148
app/components/editors/p8p_component_action/common.js
Normal file
148
app/components/editors/p8p_component_action/common.js
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Общие ресурсы компонента "Редактор действия"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Значения типа по умолчанию
|
||||
const P8P_CA_DEF_TYPE_VALUE = {
|
||||
TEXT_VALUE: "Значение",
|
||||
VARIABLE: "Переменная"
|
||||
};
|
||||
|
||||
//Типы значений по умолчанию
|
||||
const P8P_CA_DEF_VALUE_TYPES = [...Object.values(P8P_CA_DEF_TYPE_VALUE)];
|
||||
|
||||
//Типы действия
|
||||
const P8P_CA_TYPE = {
|
||||
openUnit: { code: "openUnit", name: "Открыть раздел", icon: "article" },
|
||||
openPanel: { code: "openPanel", name: "Открыть панель", icon: "storage" },
|
||||
setVariable: { code: "setVariable", name: "Установить переменную", icon: "mediation" }
|
||||
};
|
||||
|
||||
//Список полей связываемых с проводником значений
|
||||
const P8P_CA_SOURCE_FIELDS = ["variableSource"];
|
||||
|
||||
//Список полей хранящих объект типа {"Тип", "Значение"}, где может храниться связь с проводником значений
|
||||
const P8P_CA_OBJECT_SOURCE_FIELDS = ["resultValue", "tabValue", "panelValue"];
|
||||
|
||||
//Структура значения с типом
|
||||
const P8P_CA_OBJECT_VALUE_SHAPE = PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
value: PropTypes.string
|
||||
});
|
||||
|
||||
//Структура входных параметров метода вызова
|
||||
const P8P_CA_INPUT_PARAM_SHAPE = PropTypes.shape({
|
||||
parameter: PropTypes.string,
|
||||
inputParameter: PropTypes.string,
|
||||
resultValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||
});
|
||||
|
||||
//Структура параметров действия "Открыть раздел"
|
||||
const P8P_CA_OPEN_UNIT_PARAMS_SHAPE = PropTypes.shape({
|
||||
unitCode: PropTypes.string,
|
||||
unitName: PropTypes.string,
|
||||
showMethod: PropTypes.string,
|
||||
showMethodName: PropTypes.string,
|
||||
inputParams: PropTypes.arrayOf(P8P_CA_INPUT_PARAM_SHAPE),
|
||||
modal: PropTypes.bool
|
||||
});
|
||||
|
||||
//Структура параметров действия "Открыть панель"
|
||||
const P8P_CA_OPEN_PANEL_PARAMS_SHAPE = PropTypes.shape({
|
||||
tabValue: P8P_CA_OBJECT_VALUE_SHAPE,
|
||||
panelValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||
});
|
||||
|
||||
//Структура связи переменной действия "Установить переменную"
|
||||
const P8P_CA_VARIABLE_LINK_SHAPE = PropTypes.shape({
|
||||
variableSource: PropTypes.string,
|
||||
resultValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||
});
|
||||
|
||||
//Структура действия
|
||||
const P8P_CA_SHAPE = PropTypes.shape({
|
||||
type: PropTypes.oneOf(Object.keys(P8P_CA_TYPE)).isRequired,
|
||||
area: PropTypes.string,
|
||||
element: PropTypes.string,
|
||||
params: PropTypes.oneOfType([P8P_CA_OPEN_UNIT_PARAMS_SHAPE, P8P_CA_OPEN_PANEL_PARAMS_SHAPE, PropTypes.arrayOf(P8P_CA_VARIABLE_LINK_SHAPE)])
|
||||
});
|
||||
|
||||
//Начальное состояние объекта значения с типом
|
||||
const P8P_CA_OBJECT_VALUE_INITIAL = {
|
||||
type: P8P_CA_DEF_TYPE_VALUE.TEXT_VALUE,
|
||||
value: ""
|
||||
};
|
||||
|
||||
//Начальное состояние входного параметра метода вызова
|
||||
const P8P_CA_INPUT_PARAM_INITIAL = {
|
||||
parameter: "",
|
||||
inputParameter: "",
|
||||
resultValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||
};
|
||||
|
||||
//Начальное состояние параметров действия "Открыть раздел"
|
||||
const P8P_CA_OPEN_UNIT_INITIAL = {
|
||||
unitCode: "",
|
||||
unitName: "",
|
||||
showMethod: "",
|
||||
showMethodName: "",
|
||||
inputParams: [],
|
||||
modal: true
|
||||
};
|
||||
|
||||
//Начальное состояние параметров действия "Открыть панель"
|
||||
const P8P_CA_OPEN_PANEL_INITIAL = {
|
||||
tabValue: { ...P8P_CA_OBJECT_VALUE_INITIAL },
|
||||
panelValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||
};
|
||||
|
||||
//Начальное состояние параметра связи действия "Установить переменную"
|
||||
const P8P_CA_VARIABLE_LINK_INITIAL = {
|
||||
variableSource: "",
|
||||
resultValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||
};
|
||||
|
||||
//Начальное состояние действия
|
||||
const P8P_CA_INITIAL = {
|
||||
type: P8P_CA_TYPE.openUnit.code,
|
||||
area: "",
|
||||
element: "",
|
||||
params: { ...P8P_CA_OPEN_UNIT_INITIAL }
|
||||
};
|
||||
|
||||
//Начальное состояние действий
|
||||
const P8P_CAS_INITIAL = [];
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export {
|
||||
P8P_CA_DEF_TYPE_VALUE,
|
||||
P8P_CA_DEF_VALUE_TYPES,
|
||||
P8P_CA_TYPE,
|
||||
P8P_CA_SOURCE_FIELDS,
|
||||
P8P_CA_OBJECT_SOURCE_FIELDS,
|
||||
P8P_CA_OBJECT_VALUE_SHAPE,
|
||||
P8P_CA_OPEN_UNIT_PARAMS_SHAPE,
|
||||
P8P_CA_OPEN_PANEL_PARAMS_SHAPE,
|
||||
P8P_CA_VARIABLE_LINK_SHAPE,
|
||||
P8P_CA_SHAPE,
|
||||
P8P_CA_INPUT_PARAM_INITIAL,
|
||||
P8P_CA_OPEN_UNIT_INITIAL,
|
||||
P8P_CA_OPEN_PANEL_INITIAL,
|
||||
P8P_CA_VARIABLE_LINK_INITIAL,
|
||||
P8P_CA_INITIAL,
|
||||
P8P_CAS_INITIAL
|
||||
};
|
||||
159
app/components/editors/p8p_component_action/config_panel_open.js
Normal file
159
app/components/editors/p8p_component_action/config_panel_open.js
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор действия "Открыть панель"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8P_CA_DEF_TYPE_VALUE, P8P_CA_OPEN_PANEL_PARAMS_SHAPE } from "./common"; //Общие ресурсы действий
|
||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||
import { PanelsManager } from "../../../panels/panels_editor/components/panels_manager/panels_manager"; //Менеджер панелей
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER_UNIT: { display: "flex", flexDirection: "column", gap: "10px" },
|
||||
BOX_SETTINGS: { display: "flex", flexDirection: "row", gap: "15px" }
|
||||
};
|
||||
|
||||
//Ключ значений о странице
|
||||
const SPROP_TAB = "tabValue";
|
||||
//Ключа значений о панели
|
||||
const SPROP_PANEL = "panelValue";
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор действия "Открыть панель"
|
||||
const P8PCAPanelOpenOptions = ({ panel, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||
//Собственное состояние - Отображение менеджера панелей
|
||||
const [openPanelsManager, setOpenPanelsManager] = useState(false);
|
||||
|
||||
//При открытии менеджера панелей
|
||||
const handlePanelsManagerOpen = () => setOpenPanelsManager(true);
|
||||
|
||||
//При закрытии менеджера панелей
|
||||
const handlePanelsManagerClose = () => setOpenPanelsManager(false);
|
||||
|
||||
//При выборе панели в менеджере панелей
|
||||
const handlePanelSelect = selectedPanel => {
|
||||
//Устанавливаем мнемокод выбранной панели
|
||||
onStateChange({ ...panel, panelValue: { ...panel.panelValue, value: selectedPanel.code } });
|
||||
//Закрываем менеджер панелей
|
||||
handlePanelsManagerClose();
|
||||
};
|
||||
|
||||
//При изменении типа значения
|
||||
const handleValueTypeChange = (value, keyProp) =>
|
||||
onStateChange({
|
||||
[keyProp]: { type: value, value: "" }
|
||||
});
|
||||
|
||||
//При изменении значения
|
||||
const handleValueChange = (value, keyProp) =>
|
||||
onStateChange({
|
||||
[keyProp]: { ...panel[keyProp], value: value }
|
||||
});
|
||||
|
||||
//При очистке значения
|
||||
const handleClearValueClick = keyProp =>
|
||||
onStateChange({
|
||||
[keyProp]: { ...panel[keyProp], value: "" }
|
||||
});
|
||||
|
||||
//При изменении переменной
|
||||
const handleValueSourceChange = (valueSource, keyProp) =>
|
||||
onStateChange({
|
||||
[keyProp]: { ...panel[keyProp], value: valueSource }
|
||||
});
|
||||
|
||||
//При нажатии на выбор переменной
|
||||
const handleValueSourceSelect = (e, keyProp) =>
|
||||
isValues
|
||||
? onValueSourceMenuClick &&
|
||||
onValueSourceMenuClick(e, valueSource => {
|
||||
handleValueSourceChange(valueSource, keyProp);
|
||||
})
|
||||
: null;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER_UNIT}>
|
||||
{openPanelsManager && (
|
||||
<PanelsManager
|
||||
current={panel.panelValue.value}
|
||||
isEditable={false}
|
||||
onPanelSelect={handlePanelSelect}
|
||||
onCancel={handlePanelsManagerClose}
|
||||
/>
|
||||
)}
|
||||
<Box sx={STYLES.BOX_SETTINGS}>
|
||||
<P8PCAFieldWithType
|
||||
valueTypes={valueTypes}
|
||||
name={SPROP_TAB}
|
||||
valueLabel={"Наименование вкладки"}
|
||||
groupLabel={"Настройки вкладки"}
|
||||
item={panel.tabValue}
|
||||
isValueReadOnly={panel.tabValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||
onTypeChange={e => handleValueTypeChange(e.target.value, SPROP_TAB)}
|
||||
onValueChange={e => handleValueChange(e.target.value, SPROP_TAB)}
|
||||
endAdornments={[
|
||||
{ icon: "clear", onClick: () => handleClearValueClick(SPROP_TAB), isDisabled: false },
|
||||
{
|
||||
icon: "settings_ethernet",
|
||||
onClick: e => handleValueSourceSelect(e, SPROP_TAB),
|
||||
isDisabled: panel.tabValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<P8PCAFieldWithType
|
||||
valueTypes={valueTypes}
|
||||
name={SPROP_PANEL}
|
||||
valueLabel={"Панель"}
|
||||
groupLabel={"Настройки панели"}
|
||||
item={panel.panelValue}
|
||||
isValueReadOnly={panel.panelValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||
onTypeChange={e => handleValueTypeChange(e.target.value, SPROP_PANEL)}
|
||||
onValueChange={e => handleValueChange(e.target.value, SPROP_PANEL)}
|
||||
endAdornments={[
|
||||
{ icon: "clear", onClick: () => handleClearValueClick(SPROP_PANEL), isDisabled: false },
|
||||
{
|
||||
icon: "list",
|
||||
onClick: handlePanelsManagerOpen,
|
||||
isDisabled: panel.panelValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
},
|
||||
{
|
||||
icon: "settings_ethernet",
|
||||
onClick: e => handleValueSourceSelect(e, SPROP_PANEL),
|
||||
isDisabled: panel.panelValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - редактор действия "Открыть панель"
|
||||
P8PCAPanelOpenOptions.propTypes = {
|
||||
panel: P8P_CA_OPEN_PANEL_PARAMS_SHAPE,
|
||||
valueTypes: PropTypes.array,
|
||||
isValues: PropTypes.bool.isRequired,
|
||||
onStateChange: PropTypes.func.isRequired,
|
||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAPanelOpenOptions };
|
||||
285
app/components/editors/p8p_component_action/config_unit_open.js
Normal file
285
app/components/editors/p8p_component_action/config_unit_open.js
Normal file
@ -0,0 +1,285 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор действия "Открыть раздел"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, Box, InputAdornment, IconButton, Icon, FormControlLabel, Checkbox } from "@mui/material"; //Интерфейсные элементы
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { P8P_CA_OPEN_UNIT_PARAMS_SHAPE, P8P_CA_INPUT_PARAM_INITIAL, P8P_CA_DEF_TYPE_VALUE } from "./common"; //Общие ресурсы действий
|
||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||
import { P8PCATablePropValues } from "./table_prop_values"; //Таблица значений свойств действия
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER_UNIT: { display: "flex", flexDirection: "column", gap: "10px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор действия "Открыть раздел"
|
||||
const P8PCAUnitOpenOptions = ({ unit, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//При изменении параметра открытия
|
||||
const handleModalChange = () => onStateChange({ modal: !unit.modal });
|
||||
|
||||
//При нажатии на очистку раздела
|
||||
const handleClearUnitClick = () =>
|
||||
onStateChange({
|
||||
unitCode: "",
|
||||
unitName: "",
|
||||
showMethod: ""
|
||||
});
|
||||
|
||||
//При нажатии на выбор раздела
|
||||
const handleSelectUnitClick = () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "Units",
|
||||
showMethod: "methods",
|
||||
inputParameters: [
|
||||
{ name: "pos_unit_name", value: unit.unitName },
|
||||
{ name: "pos_method_name", value: unit.showMethodName }
|
||||
],
|
||||
callBack: res =>
|
||||
res.success &&
|
||||
onStateChange({
|
||||
unitCode: res.outParameters.unit_code,
|
||||
unitName: res.outParameters.unit_name,
|
||||
showMethod: res.outParameters.method_code,
|
||||
showMethodName: res.outParameters.method_name
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
//При добавлении связи входного параметра
|
||||
const handleInputParameAdd = () => {
|
||||
onStateChange({
|
||||
inputParams: [...unit.inputParams, { ...P8P_CA_INPUT_PARAM_INITIAL }]
|
||||
});
|
||||
};
|
||||
|
||||
//При удалении связи входного параметра
|
||||
const handleInputParamDelete = index => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Удаляем по индексу
|
||||
newInputParams.splice(index, 1);
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При очистке входного параметра
|
||||
const handleClearInputParameterClick = index => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index]["parameter"] = "";
|
||||
newInputParams[index]["inputParameter"] = "";
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При нажатии на выбор параметра метода вызова
|
||||
const handleSelectUnitParameterClick = index => {
|
||||
unit.unitCode &&
|
||||
unit.showMethod &&
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "UnitParams",
|
||||
showMethod: "main",
|
||||
inputParameters: [
|
||||
{ name: "in_UNITCODE", value: unit.unitCode },
|
||||
{ name: "in_PARENT_METHOD_CODE", value: unit.showMethod },
|
||||
{ name: "in_PARAMNAME", value: unit.inputParams[index].parameter }
|
||||
],
|
||||
callBack: res => {
|
||||
if (res.success) {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index]["parameter"] = res.outParameters.out_PARAMNAME;
|
||||
newInputParams[index]["inputParameter"] = res.outParameters.out_IN_CODE;
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//При изменении типа значения входного параметра
|
||||
const handleInputParamValueTypeChange = (value, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, type: value, value: "" };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При очистке значения параметра
|
||||
const handleClearInputParamValueClick = index => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: "" };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При изменении значения входного параметра
|
||||
const handleInputParamValueChange = (value, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: value };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При связывании значения входного параметра с переменной
|
||||
const handleInputParamValueSourceChange = (valueSource, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: valueSource };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При нажатии на выбор переменной
|
||||
const handleValueSourceSelect = (e, index) =>
|
||||
isValues
|
||||
? onValueSourceMenuClick &&
|
||||
onValueSourceMenuClick(e, valueSource => {
|
||||
handleInputParamValueSourceChange(valueSource, index);
|
||||
})
|
||||
: null;
|
||||
|
||||
//Рендер отображения колонки свойства
|
||||
const handlePropertyCellRender = (item, index) => {
|
||||
//Значение первой колонки
|
||||
return (
|
||||
<TextField
|
||||
id={`parameter_${index}`}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={item.parameter}
|
||||
name={"parameter"}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleClearInputParameterClick(index)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={() => handleSelectUnitParameterClick(index)} disabled={!unit.showMethodName}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Рендер отображения колонки значения
|
||||
const handleValueCellRender = (item, index) => {
|
||||
//Формирование представления колонки значения
|
||||
return (
|
||||
<P8PCAFieldWithType
|
||||
valueTypes={valueTypes}
|
||||
name={`inputValue_${index}`}
|
||||
item={item.resultValue}
|
||||
isValueReadOnly={item.resultValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||
onTypeChange={e => handleInputParamValueTypeChange(e.target.value, index)}
|
||||
onValueChange={e => handleInputParamValueChange(e.target.value, index)}
|
||||
endAdornments={[
|
||||
{ icon: "clear", onClick: () => handleClearInputParamValueClick(index), isDisabled: false },
|
||||
{
|
||||
icon: "settings_ethernet",
|
||||
onClick: e => handleValueSourceSelect(e, index),
|
||||
isDisabled: item.resultValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER_UNIT}>
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={unit.unitName}
|
||||
label={"Раздел"}
|
||||
InputLabelProps={{ shrink: unit.unitName ? true : false }}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleClearUnitClick}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleSelectUnitClick}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
required={true}
|
||||
/>
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={unit.showMethodName}
|
||||
label={"Метод вызова"}
|
||||
InputLabelProps={{ shrink: unit.showMethodName ? true : false }}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
required={true}
|
||||
/>
|
||||
<P8PCATablePropValues
|
||||
name={"Входные параметры"}
|
||||
items={unit.inputParams}
|
||||
propertyCellName={"Входной параметр"}
|
||||
valueCellName={"Значение"}
|
||||
onPropertyCellRender={(item, index) => handlePropertyCellRender(item, index)}
|
||||
onValueCellRender={(item, index) => handleValueCellRender(item, index)}
|
||||
onAddRow={handleInputParameAdd}
|
||||
onRowDelete={index => handleInputParamDelete(index)}
|
||||
/>
|
||||
<FormControlLabel control={<Checkbox checked={unit.modal} onChange={handleModalChange} />} label="Открывать модально" />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - редактор действия "Открыть раздел"
|
||||
P8PCAUnitOpenOptions.propTypes = {
|
||||
unit: P8P_CA_OPEN_UNIT_PARAMS_SHAPE,
|
||||
valueTypes: PropTypes.array,
|
||||
isValues: PropTypes.bool.isRequired,
|
||||
onStateChange: PropTypes.func.isRequired,
|
||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAUnitOpenOptions };
|
||||
202
app/components/editors/p8p_component_action/config_var_set.js
Normal file
202
app/components/editors/p8p_component_action/config_var_set.js
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор действия "Установить переменную"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8P_CA_VARIABLE_LINK_INITIAL, P8P_CA_VARIABLE_LINK_SHAPE, P8P_CA_DEF_TYPE_VALUE } from "./common"; //Общие ресурсы действий
|
||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||
import { P8PCATablePropValues } from "./table_prop_values"; //Таблица значений свойств действия
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор действия "Установить переменную"
|
||||
const P8PCAVarSetOptions = ({ variables, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||
//При добавлении связи переменной
|
||||
const handleVariableAdd = () => {
|
||||
onStateChange([...variables, { ...P8P_CA_VARIABLE_LINK_INITIAL }]);
|
||||
};
|
||||
|
||||
//При удалении связи переменной
|
||||
const handleVariableDelete = index => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Удаляем по индексу
|
||||
newVariables.splice(index, 1);
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При очистке значения переменной
|
||||
const handleClearVariableClick = index => {
|
||||
//Копируем текущее состояние связываемых переменных
|
||||
let newParams = [...variables];
|
||||
//Очищаем значения у элемента по индексу
|
||||
newParams[index].variableSource = "";
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newParams]);
|
||||
};
|
||||
|
||||
//При связывании переменной
|
||||
const handleVariablesSourceChange = (valueSource, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значение по индексу
|
||||
newVariables[index].variableSource = valueSource;
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При изменении типа значения
|
||||
const handleResultValueTypeChange = (value, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значение по индексу
|
||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, type: value, value: "" };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При очистке значения
|
||||
const handleClearResultValueClick = index => {
|
||||
//Копируем текущее состояние связываемых переменных
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значения у элемента по индексу
|
||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: "" };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При ручном изменении значения
|
||||
const handleResultValueChange = (value, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значение по индексу
|
||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: value };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При связывании значения
|
||||
const handleResultValueSourceChange = (valueSource, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значение по индексу
|
||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: valueSource };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При нажатии на выбор переменной (значение)
|
||||
const handleValueSourceSelect = (e, index) =>
|
||||
isValues
|
||||
? onValueSourceMenuClick &&
|
||||
onValueSourceMenuClick(e, valueSource => {
|
||||
handleResultValueSourceChange(valueSource, index);
|
||||
})
|
||||
: null;
|
||||
|
||||
//При нажатии на выбор переменной (переменная)
|
||||
const handleVariableSourceSelect = (e, index) =>
|
||||
isValues
|
||||
? onValueSourceMenuClick &&
|
||||
onValueSourceMenuClick(e, valueSource => {
|
||||
handleVariablesSourceChange(valueSource, index);
|
||||
})
|
||||
: null;
|
||||
|
||||
//Рендер отображения колонки свойства
|
||||
const handlePropertyCellRender = (item, index) => {
|
||||
//Формирование представления колонки свойства
|
||||
return (
|
||||
<TextField
|
||||
id={`variableSource_${index}`}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={item.variableSource}
|
||||
name={"variableSource"}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleClearVariableClick(index)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
{isValues && (
|
||||
<IconButton onClick={e => handleVariableSourceSelect(e, index)}>
|
||||
<Icon>settings_ethernet</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Рендер отображения колонки значения
|
||||
const handleValueCellRender = (item, index) => {
|
||||
//Формирование представления колонки значения
|
||||
return (
|
||||
<P8PCAFieldWithType
|
||||
valueTypes={valueTypes}
|
||||
name={`resultValue_${index}`}
|
||||
item={item.resultValue}
|
||||
isValueReadOnly={item.resultValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||
onTypeChange={e => handleResultValueTypeChange(e.target.value, index)}
|
||||
onValueChange={e => handleResultValueChange(e.target.value, index)}
|
||||
endAdornments={[
|
||||
{ icon: "clear", onClick: () => handleClearResultValueClick(index), isDisabled: false },
|
||||
{
|
||||
icon: "settings_ethernet",
|
||||
onClick: e => handleValueSourceSelect(e, index),
|
||||
isDisabled: item.resultValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PCATablePropValues
|
||||
name={"Список параметров"}
|
||||
items={variables}
|
||||
propertyCellName={"Переменная"}
|
||||
valueCellName={"Значение"}
|
||||
onPropertyCellRender={(item, index) => handlePropertyCellRender(item, index)}
|
||||
onValueCellRender={(item, index) => handleValueCellRender(item, index)}
|
||||
onAddRow={handleVariableAdd}
|
||||
onRowDelete={index => handleVariableDelete(index)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - редактор действия "Установить переменную"
|
||||
P8PCAVarSetOptions.propTypes = {
|
||||
variables: PropTypes.arrayOf(P8P_CA_VARIABLE_LINK_SHAPE),
|
||||
valueTypes: PropTypes.array,
|
||||
isValues: PropTypes.bool.isRequired,
|
||||
onStateChange: PropTypes.func.isRequired,
|
||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAVarSetOptions };
|
||||
250
app/components/editors/p8p_component_action/editor.js
Normal file
250
app/components/editors/p8p_component_action/editor.js
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор действия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, Select, Menu, MenuItem, FormControl, InputLabel, Stack, Box } from "@mui/material"; //Интерфейсные элементы
|
||||
import { TITLES } from "../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { P8PConfigDialog } from "../p8p_config_dialog"; //Диалог настройки
|
||||
import { deepCopyObject } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { P8P_CA_DEF_VALUE_TYPES, P8P_CA_TYPE, P8P_CA_SHAPE, P8P_CA_INPUT_PARAM_INITIAL, P8P_CA_OPEN_UNIT_INITIAL, P8P_CA_INITIAL } from "./common"; //Общие ресурсы действий
|
||||
import { P8PCAUnitOpenOptions } from "./config_unit_open"; //Компонент редактора действия "Открыть раздел"
|
||||
import { P8PCAPanelOpenOptions } from "./config_panel_open"; //Компонент редактора действия "Открыть панель"
|
||||
import { P8PCAVarSetOptions } from "./config_var_set"; //Компонент редактора действия "Установить переменную"
|
||||
import { isActionOkDisabled, getActionTypeParams } from "./util"; //Вспомогательные ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex", flexDirection: "column", gap: "10px", minWidth: "300px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор действия
|
||||
const P8PCAEditor = ({ areas = [], valueTypes = [], action = null, onOk = null, onCancel = null, valueProviders = {} }) => {
|
||||
//Собственное состояние - признак инициализиации
|
||||
const [init, setInit] = useState(true);
|
||||
|
||||
//Собственное состояние - параметры действия
|
||||
const [state, setState] = useState({});
|
||||
|
||||
//Собственное состояние - доступные типы значений компонента
|
||||
const [availableValueTypes, setAvailableValueTypes] = useState([]);
|
||||
|
||||
//Собственное состояние - доступные области компонента
|
||||
const [availableAreas, setAvailableAreas] = useState([]);
|
||||
|
||||
//Собственное состояние - доступность поля "Элемент"
|
||||
const [hasElement, setHasElement] = useState(false);
|
||||
|
||||
//Собственное состояние - элемент привязки меню выбора источника
|
||||
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState({
|
||||
target: null,
|
||||
onChange: null
|
||||
});
|
||||
|
||||
//Открытие/сокрытие меню выбора источника
|
||||
const toggleValueProvidersMenu = (target, onChange) =>
|
||||
setValueProvidersMenuAnchorEl(target instanceof Element ? { target, onChange } : { target: null, onChange: null });
|
||||
|
||||
//При отображении меню связывания значения с поставщиком данных
|
||||
const handleValueSourceLinkMenuClick = (e, onChange) => setValueProvidersMenuAnchorEl({ target: e.currentTarget, onChange });
|
||||
|
||||
//При закрытии редактора с сохранением
|
||||
const handleOk = () => onOk && onOk({ ...state });
|
||||
|
||||
//При закрытии редактора с отменой
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//При изменении типа действия
|
||||
const handleTypeChange = e => {
|
||||
//Иницилизируем параметры типа
|
||||
let newParams = getActionTypeParams(e.target.value);
|
||||
//Изменяем тип действия с изменение структуры параметров
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
type: e.target.value,
|
||||
params: Array.isArray(newParams) ? [...newParams] : { ...newParams }
|
||||
}));
|
||||
};
|
||||
|
||||
//При ручном изменении общего параметра действия
|
||||
const handleChange = e => setState(pv => ({ ...pv, [e.target.name]: e.target.value }));
|
||||
|
||||
//При изменении области действия
|
||||
const handleAreaChange = e => {
|
||||
//Устанавливаем значение
|
||||
setState(pv => ({ ...pv, [e.target.name]: e.target.value, name: "" }));
|
||||
//Устанавливаем доступность "Элемент"
|
||||
setHasElement(availableAreas.find(item => item.area === e.target.value).hasElement);
|
||||
};
|
||||
|
||||
//При изменении полей параметров действия
|
||||
const handleParamsChange = params => {
|
||||
setState(pv => ({ ...pv, params: { ...pv.params, ...params } }));
|
||||
};
|
||||
|
||||
//При изменении переменных параметров действия "Установить переменную"
|
||||
const handleVariablesParamsChange = variables => {
|
||||
setState(pv => ({ ...pv, params: [...variables] }));
|
||||
};
|
||||
|
||||
//Список значений
|
||||
const values = Object.keys(valueProviders);
|
||||
|
||||
//Наличие значений
|
||||
const isValues = values && values.length > 0 ? true : false;
|
||||
|
||||
//Меню привязки к поставщикам значений
|
||||
const valueProvidersMenu = isValues && (
|
||||
<Menu anchorEl={valueProvidersMenuAnchorEl.target} open={Boolean(valueProvidersMenuAnchorEl.target)} onClose={toggleValueProvidersMenu}>
|
||||
{values.map((value, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
onClick={() => {
|
||||
//Выполняем выбор параметра
|
||||
valueProvidersMenuAnchorEl.onChange(value);
|
||||
//Закрываем меню выбора переменной
|
||||
toggleValueProvidersMenu();
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
//При инициализации действия
|
||||
useEffect(() => {
|
||||
//Если это иницализация
|
||||
if (init) {
|
||||
//Если это открытие действия - берем его параметры, иначе - инициализируем изначальными
|
||||
const initAction = action
|
||||
? action
|
||||
: {
|
||||
...P8P_CA_INITIAL,
|
||||
area: areas[0].area,
|
||||
params: { ...P8P_CA_OPEN_UNIT_INITIAL, inputParams: [{ ...P8P_CA_INPUT_PARAM_INITIAL }] }
|
||||
};
|
||||
//Устанавливаем параметры действия
|
||||
setState(deepCopyObject(initAction));
|
||||
//Заполняем доступные области
|
||||
setAvailableAreas([...areas]);
|
||||
//Определяем доступность "Элемент"
|
||||
setHasElement(areas.find(item => item.area === initAction.area)?.hasElement || false);
|
||||
//Заполняем доступные типы значений
|
||||
setAvailableValueTypes([...P8P_CA_DEF_VALUE_TYPES, ...valueTypes]);
|
||||
//Сбрасываем признак инициализации
|
||||
setInit(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [init]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{!init ? (
|
||||
<P8PConfigDialog
|
||||
title={`${action ? TITLES.UPDATE : TITLES.INSERT} действия`}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={"xl"}
|
||||
okDisabled={isActionOkDisabled(state)}
|
||||
>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{valueProvidersMenu}
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{availableAreas.length !== 0 ? (
|
||||
<FormControl variant={"standard"} fullWidth>
|
||||
<InputLabel id={"areaLabel-label"}>Область</InputLabel>
|
||||
<Select name={"area"} value={state.area} labelId={"area-label"} label={"Область"} onChange={handleAreaChange}>
|
||||
{availableAreas.map((item, index) => (
|
||||
<MenuItem value={item.area} key={index}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
) : null}
|
||||
{hasElement && (
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.element}
|
||||
label={"Элемент"}
|
||||
name={"element"}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
<FormControl variant={"standard"} fullWidth>
|
||||
<InputLabel id={"type-label"}>Тип</InputLabel>
|
||||
<Select name={"type"} value={state.type} labelId={"type-label"} label={"Тип"} onChange={handleTypeChange}>
|
||||
{Object.keys(P8P_CA_TYPE).map((item, index) => (
|
||||
<MenuItem value={P8P_CA_TYPE[item].code} key={index}>
|
||||
{P8P_CA_TYPE[item].name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{state.type === P8P_CA_TYPE.openUnit.code && (
|
||||
<P8PCAUnitOpenOptions
|
||||
unit={state.params}
|
||||
valueTypes={availableValueTypes}
|
||||
isValues={isValues}
|
||||
onStateChange={handleParamsChange}
|
||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||
/>
|
||||
)}
|
||||
{state.type === P8P_CA_TYPE.openPanel.code && (
|
||||
<P8PCAPanelOpenOptions
|
||||
panel={state.params}
|
||||
valueTypes={availableValueTypes}
|
||||
isValues={isValues}
|
||||
onStateChange={handleParamsChange}
|
||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||
/>
|
||||
)}
|
||||
{state.type === P8P_CA_TYPE.setVariable.code && (
|
||||
<P8PCAVarSetOptions
|
||||
variables={state.params}
|
||||
valueTypes={availableValueTypes}
|
||||
isValues={isValues}
|
||||
onStateChange={handleVariablesParamsChange}
|
||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</P8PConfigDialog>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Редактор условия
|
||||
P8PCAEditor.propTypes = {
|
||||
areas: PropTypes.array,
|
||||
valueTypes: PropTypes.array,
|
||||
action: P8P_CA_SHAPE,
|
||||
onOk: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
valueProviders: PropTypes.object
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAEditor };
|
||||
105
app/components/editors/p8p_component_action/field_with_type.js
Normal file
105
app/components/editors/p8p_component_action/field_with_type.js
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Поле с выбором типа значения
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, Select, MenuItem, FormControl, Box, InputAdornment, IconButton, Icon, FormLabel } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8P_CA_OBJECT_VALUE_SHAPE } from "./common"; //Общие ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex", flexDirection: "column", gap: "10px", alignItems: "center" },
|
||||
LEGEND_SETTINGS: { paddingTop: "10px", marginBottom: "-5px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Поле с выбором типа значения
|
||||
const P8PCAFieldWithType = ({
|
||||
valueTypes,
|
||||
item,
|
||||
name,
|
||||
valueLabel = null,
|
||||
groupLabel = null,
|
||||
isValueReadOnly = false,
|
||||
onTypeChange,
|
||||
onValueChange = null,
|
||||
endAdornments = []
|
||||
}) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{groupLabel ? (
|
||||
<FormLabel component="legend" sx={STYLES.LEGEND_SETTINGS}>
|
||||
{groupLabel}
|
||||
</FormLabel>
|
||||
) : null}
|
||||
<FormControl variant={"standard"} fullWidth>
|
||||
<Select name={`${name}_type`} value={item.type} labelId={`${name}-type-label`} onChange={onTypeChange} fullWidth>
|
||||
{valueTypes.map((type, index) => (
|
||||
<MenuItem value={type} key={index}>
|
||||
{type}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
id={`${name}_value`}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={item.value}
|
||||
name={`${name}`}
|
||||
label={valueLabel}
|
||||
onChange={e => {
|
||||
onValueChange && onValueChange(e);
|
||||
}}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: isValueReadOnly,
|
||||
endAdornment:
|
||||
endAdornments.length !== 0 ? (
|
||||
<InputAdornment position="end">
|
||||
{endAdornments.map((item, index) => (
|
||||
<IconButton onClick={e => item.onClick(e)} key={index} disabled={item.isDisabled}>
|
||||
<Icon>{item.icon}</Icon>
|
||||
</IconButton>
|
||||
))}
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - поле с выбором типа значения
|
||||
P8PCAFieldWithType.propTypes = {
|
||||
valueTypes: PropTypes.array.isRequired,
|
||||
item: P8P_CA_OBJECT_VALUE_SHAPE.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
valueLabel: PropTypes.string,
|
||||
groupLabel: PropTypes.string,
|
||||
isValueReadOnly: PropTypes.bool,
|
||||
onTypeChange: PropTypes.func.isRequired,
|
||||
onValueChange: PropTypes.func,
|
||||
endAdornments: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAFieldWithType };
|
||||
@ -0,0 +1,99 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Таблица значений свойств действия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, IconButton, Icon, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER_VARIABLES: { maxHeight: "240px", overflow: "auto", ...APP_STYLES.SCROLL },
|
||||
BOX_VARIABLES_INFO: { display: "flex", justifyContent: "space-between", alignItems: "center", paddingTop: "10px" },
|
||||
TABLE_HEAD_CELL_PROPERTY: { width: "45%", padding: "8px" },
|
||||
TABLE_HEAD_CELL_VALUE: { width: "45%", padding: "8px" },
|
||||
TABLE_HEAD_CELL_DELETE: { width: "10%", padding: "8px" },
|
||||
TABLE_VARIABLES: { maxWidth: "700px", minWidth: "700px" },
|
||||
TABLE_ROW_VARIABLES: { "&:last-child td, &:last-child th": { border: 0 } },
|
||||
TABLE_CELL_VARIABLES: { padding: "8px 16px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Таблица значений свойств действия
|
||||
const P8PCATablePropValues = ({ name, items, propertyCellName, valueCellName, onPropertyCellRender, onValueCellRender, onAddRow, onRowDelete }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
<Box sx={STYLES.BOX_VARIABLES_INFO}>
|
||||
<Typography pl={1}>{name}</Typography>
|
||||
<IconButton onClick={onAddRow}>
|
||||
<Icon>add</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<TableContainer component={Paper} sx={STYLES.CONTAINER_VARIABLES}>
|
||||
<Table sx={STYLES.TABLE_VARIABLES} aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_PROPERTY}>
|
||||
{propertyCellName}
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_VALUE}>
|
||||
{valueCellName}
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_DELETE}></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Array.isArray(items) &&
|
||||
items.map((item, i) => (
|
||||
<TableRow key={i} sx={STYLES.TABLE_ROW_VARIABLES}>
|
||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||
{onPropertyCellRender ? onPropertyCellRender(item, i) : null}
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||
{onValueCellRender ? onValueCellRender(item, i) : null}
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||
<IconButton onClick={() => onRowDelete(i)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - таблица значений свойств действия
|
||||
P8PCATablePropValues.propTypes = {
|
||||
name: PropTypes.string,
|
||||
items: PropTypes.array,
|
||||
propertyCellName: PropTypes.string,
|
||||
valueCellName: PropTypes.string,
|
||||
onPropertyCellRender: PropTypes.func,
|
||||
onValueCellRender: PropTypes.func,
|
||||
onAddRow: PropTypes.func,
|
||||
onRowDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCATablePropValues };
|
||||
212
app/components/editors/p8p_component_action/util.js
Normal file
212
app/components/editors/p8p_component_action/util.js
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Вспомогательные ресурсы компонента "Редактор действия"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { genUID } from "../../../core/utils"; //Вспомогательные функции
|
||||
import {
|
||||
P8P_CA_DEF_TYPE_VALUE,
|
||||
P8P_CA_TYPE,
|
||||
P8P_CA_SOURCE_FIELDS,
|
||||
P8P_CA_OBJECT_SOURCE_FIELDS,
|
||||
P8P_CA_OPEN_UNIT_INITIAL,
|
||||
P8P_CA_INPUT_PARAM_INITIAL,
|
||||
P8P_CA_OPEN_PANEL_INITIAL,
|
||||
P8P_CA_VARIABLE_LINK_INITIAL
|
||||
} from "./common"; //Общие ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Доступность сохранения настроек действия
|
||||
const isActionOkDisabled = action => {
|
||||
//Если область или тип не указаны
|
||||
if (!action.area || !action.type) return true;
|
||||
//Исходим от типа
|
||||
switch (action.type) {
|
||||
//Открыть раздел
|
||||
case P8P_CA_TYPE.openUnit.code:
|
||||
return !action.params.unitCode || !action.params.showMethod;
|
||||
//Открыть панель
|
||||
case P8P_CA_TYPE.openPanel.code:
|
||||
return !action.params.tabValue.type || !action.params.tabValue.value || !action.params.panelValue.type || !action.params.panelValue.value;
|
||||
//Установить переменную
|
||||
case P8P_CA_TYPE.setVariable.code:
|
||||
return action.params.length === 0 || action.params.some(item => !item.variableSource);
|
||||
//Для всех остальных
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//Считывание параметров типа действия
|
||||
const getActionTypeParams = type => {
|
||||
//Определяем от типа
|
||||
switch (type) {
|
||||
//Открыть раздел
|
||||
case P8P_CA_TYPE.openUnit.code:
|
||||
return { ...P8P_CA_OPEN_UNIT_INITIAL, inputParams: [{ ...P8P_CA_INPUT_PARAM_INITIAL }] };
|
||||
//Открыть панель
|
||||
case P8P_CA_TYPE.openPanel.code:
|
||||
return { ...P8P_CA_OPEN_PANEL_INITIAL };
|
||||
//Установить переменную
|
||||
case P8P_CA_TYPE.setVariable.code:
|
||||
return [{ ...P8P_CA_VARIABLE_LINK_INITIAL }];
|
||||
//Для всех остальных
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
//Определение значения действия по типу
|
||||
const getActionValueByType = (type, value, values, prms = {}, getCustomTypeValue = null) => {
|
||||
//Исходим от типа действия
|
||||
switch (type) {
|
||||
//Значение (стандартное)
|
||||
case P8P_CA_DEF_TYPE_VALUE.TEXT_VALUE:
|
||||
return value;
|
||||
//Переменная (стандартное)
|
||||
case P8P_CA_DEF_TYPE_VALUE.VARIABLE:
|
||||
return values[value];
|
||||
//Кастомный тип
|
||||
default:
|
||||
return getCustomTypeValue ? getCustomTypeValue({ type, value, values, prms }) : null;
|
||||
}
|
||||
};
|
||||
|
||||
//Определения функции действия
|
||||
const getActionFunction = (item, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue = null) => {
|
||||
//Выполняем исходя из типа действия
|
||||
switch (item.type) {
|
||||
//Открыть раздел
|
||||
case P8P_CA_TYPE.openUnit.code: {
|
||||
return ({ event, values, prms }) => {
|
||||
onOpenUnit({
|
||||
unitCode: item.params.unitCode,
|
||||
showMethod: item.params.showMethod,
|
||||
inputParameters: item.params.inputParams.reduce(
|
||||
(prev, cur) =>
|
||||
cur.inputParameter
|
||||
? [
|
||||
...prev,
|
||||
{
|
||||
name: cur.inputParameter,
|
||||
value: getActionValueByType(cur.resultValue.type, cur.resultValue.value, values, prms, getCustomTypeValue)
|
||||
}
|
||||
]
|
||||
: [...prev],
|
||||
[]
|
||||
),
|
||||
modal: item.params.modal
|
||||
});
|
||||
event?.stopPropagation && event.stopPropagation();
|
||||
};
|
||||
}
|
||||
//Открыть панель
|
||||
case P8P_CA_TYPE.openPanel.code: {
|
||||
return ({ event, values, prms }) => {
|
||||
onOpenPanel({
|
||||
id: genUID(),
|
||||
url: `${configUrlBase}panels_editor?SCODE=${getActionValueByType(
|
||||
item.params.panelValue.type,
|
||||
item.params.panelValue.value,
|
||||
values,
|
||||
prms,
|
||||
getCustomTypeValue
|
||||
)}`,
|
||||
caption:
|
||||
getActionValueByType(item.params.tabValue.type, item.params.tabValue.value, values, prms, getCustomTypeValue) ||
|
||||
"Редактор панелей"
|
||||
});
|
||||
event?.stopPropagation && event.stopPropagation();
|
||||
};
|
||||
}
|
||||
//Установить переменную
|
||||
case P8P_CA_TYPE.setVariable.code: {
|
||||
//Устанавливаем новые значения проводников
|
||||
return ({ event, values, prms }) => {
|
||||
//Собираем объект новых значений проводников
|
||||
let changedValues = item.params.reduce(
|
||||
(prev, cur) => ({
|
||||
...prev,
|
||||
...{
|
||||
[cur.variableSource]: getActionValueByType(cur.resultValue.type, cur.resultValue.value, values, prms, getCustomTypeValue)
|
||||
}
|
||||
}),
|
||||
{}
|
||||
);
|
||||
onProviderChange({ ...changedValues });
|
||||
event?.stopPropagation && event.stopPropagation();
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//Выполнение действий компонента
|
||||
const getHandlersByActions = (actions, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue = null) => {
|
||||
//Инициализируем обработчики
|
||||
const handlers = actions.reduce((prev, cur) => {
|
||||
//Ключ обработчика
|
||||
let key = `${cur.area}.${cur.element}`;
|
||||
//Если уже был добавлен - не добавляем, иначе добавляем
|
||||
return !prev[key] ? { ...prev, [key]: { area: cur.area, element: cur.element, fn: null } } : { ...prev };
|
||||
}, {});
|
||||
//Обходим уникальные обработчики
|
||||
for (const handler in handlers) {
|
||||
//Считываем действия области
|
||||
let areaActions = actions.filter(item => item.area === handlers[handler].area && item.element === handlers[handler].element);
|
||||
//Собираем массив обработчиков области
|
||||
let areaFunctions = areaActions.reduce(
|
||||
(fns, action) => [...fns, getActionFunction(action, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue)],
|
||||
[]
|
||||
);
|
||||
//Устанавливаем области последовательный вызов обработчиков
|
||||
handlers[handler].fn = ({ event, values, prms }) => {
|
||||
areaFunctions.map(fn => fn({ event, values, prms }));
|
||||
};
|
||||
}
|
||||
//Возвращаем обработчики
|
||||
return handlers;
|
||||
};
|
||||
|
||||
//Считывание всех связанных переменных рекурсивно
|
||||
const getActionsVariablesRecursive = (objValue, propName = null) => {
|
||||
//Если это значение
|
||||
if (!(typeof objValue === "object") && !Array.isArray(objValue)) {
|
||||
//Если поле из списка хранящих ссылку - берем его
|
||||
return P8P_CA_SOURCE_FIELDS.includes(propName) && objValue ? [objValue] : [];
|
||||
}
|
||||
//Если это объект хранящий тип и значение - может хранить ссылку на переменную
|
||||
if (P8P_CA_OBJECT_SOURCE_FIELDS.includes(propName)) {
|
||||
//Если тип "Переменная" - указываем значение
|
||||
return objValue?.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE && objValue?.value ? [objValue.value] : [];
|
||||
}
|
||||
//Если это массив
|
||||
if (Array.isArray(objValue)) {
|
||||
return objValue.reduce((prev, cur) => [...prev, ...getActionsVariablesRecursive(cur)], []);
|
||||
} else {
|
||||
//Если это объект
|
||||
return Object.keys(objValue).reduce((prev, cur) => [...prev, ...getActionsVariablesRecursive(objValue[cur], cur)], []);
|
||||
}
|
||||
};
|
||||
|
||||
//Считывание всех связанных переменных действий
|
||||
const getActionsVariables = actions => {
|
||||
//Считываем связанные переменные действий рекурсивно
|
||||
return getActionsVariablesRecursive(actions);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { isActionOkDisabled, getActionTypeParams, getActionsVariables, getHandlersByActions };
|
||||
82
app/components/editors/p8p_component_condition/common.js
Normal file
82
app/components/editors/p8p_component_condition/common.js
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Общие ресурсы компонента "Редактор условия"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Доступные операторы условия
|
||||
const P8P_CC_OPERATORS = [
|
||||
{ name: "==", value: "equal" },
|
||||
{ name: "!=", value: "notEqual" },
|
||||
{ name: "<=", value: "lessEqual" },
|
||||
{ name: "<", value: "less" },
|
||||
{ name: ">=", value: "greaterEqual" },
|
||||
{ name: ">", value: "greater" },
|
||||
{ name: "in", value: "in" }
|
||||
];
|
||||
|
||||
//Структура параметра поля условия
|
||||
const P8P_CC_FIELD_PRM_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
hasElement: PropTypes.bool,
|
||||
icon: PropTypes.string
|
||||
});
|
||||
|
||||
//Структура поля условия
|
||||
const P8P_CC_FIELD_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired
|
||||
});
|
||||
|
||||
//Структура оператора условия
|
||||
const P8P_CC_OPERATOR_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired
|
||||
});
|
||||
|
||||
//Структура условия
|
||||
const P8P_CC_SHAPE = PropTypes.shape({
|
||||
condField: P8P_CC_FIELD_SHAPE.isRequired,
|
||||
condOperator: P8P_CC_OPERATOR_SHAPE.isRequired,
|
||||
condElement: PropTypes.string, //Пока
|
||||
condValue: PropTypes.string.isRequired,
|
||||
resField: P8P_CC_FIELD_SHAPE.isRequired,
|
||||
resElement: PropTypes.string, //Пока
|
||||
resValue: PropTypes.string.isRequired
|
||||
});
|
||||
|
||||
//Начальное состояние поля условия
|
||||
const P8P_CC_FIELD_INITIAL = {
|
||||
name: "",
|
||||
value: ""
|
||||
};
|
||||
|
||||
//Начальное состояние условия
|
||||
const P8P_CC_INITIAL = {
|
||||
condField: { ...P8P_CC_FIELD_INITIAL },
|
||||
condOperator: { ...P8P_CC_OPERATORS[0] },
|
||||
condElement: "",
|
||||
condValue: "",
|
||||
resField: { ...P8P_CC_FIELD_INITIAL },
|
||||
resElement: "",
|
||||
resValue: ""
|
||||
};
|
||||
|
||||
//Начальное состояние условий
|
||||
const P8P_CCS_INITIAL = [];
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CC_OPERATORS, P8P_CC_FIELD_PRM_SHAPE, P8P_CC_FIELD_SHAPE, P8P_CC_SHAPE, P8P_CC_INITIAL, P8P_CCS_INITIAL };
|
||||
239
app/components/editors/p8p_component_condition/editor.js
Normal file
239
app/components/editors/p8p_component_condition/editor.js
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор условия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, Select, MenuItem, FormControl, InputLabel, Stack, Box } from "@mui/material"; //Интерфейсные элементы
|
||||
import { TITLES } from "../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { P8PConfigDialog } from "../p8p_config_dialog"; //Диалог настройки
|
||||
import { P8P_CC_INITIAL, P8P_CC_SHAPE, P8P_CC_OPERATORS, P8P_CC_FIELD_PRM_SHAPE } from "./common"; //Общие ресурсы условий
|
||||
import { deepCopyObject } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { isConditionOkDisabled } from "./util"; //Вспомогательные ресурсы условий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
BOX_COND_GROUP: { display: "flex", flexDirection: "row", gap: "10px", paddingBottom: "10px" },
|
||||
BOX_COND_ELEMENT: width => ({ minWidth: width })
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор условия
|
||||
const P8PCCEditor = ({ condition = null, onOk = null, onCancel = null, condFields = [], resFields = [] }) => {
|
||||
//Собственное состояние - признак инициализиации
|
||||
const [init, setInit] = useState(true);
|
||||
|
||||
//Собственное состояние - параметры элемента формы
|
||||
const [state, setState] = useState({});
|
||||
|
||||
//При закрытии редактора с сохранением
|
||||
const handleOk = () => onOk && onOk({ ...state });
|
||||
|
||||
//Собственное состояние - доступность поля "Элемент условия"
|
||||
const [hasCondElement, setHasCondElement] = useState(false);
|
||||
|
||||
//Собственное состояние - доступность поля "Элемент результата"
|
||||
const [hasResElement, setHasResElement] = useState(false);
|
||||
|
||||
//При закрытии редактора с отменой
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//При изменении параметра элемента
|
||||
const handleChange = e => setState(pv => ({ ...pv, [e.target.name]: e.target.value }));
|
||||
|
||||
//При изменении поля условия
|
||||
const handleCondFieldChange = e => {
|
||||
//Считываем нужный элемент
|
||||
const newCondField = condFields.find(item => item.name === e.target.value);
|
||||
//Обновляем поле (объект)
|
||||
setState(pv => ({ ...pv, [e.target.name]: { name: newCondField.name, value: newCondField.value } }));
|
||||
//Определяем доступность поля "Элемент условия"
|
||||
setHasCondElement(newCondField?.hasElement || false);
|
||||
};
|
||||
|
||||
//При изменении поля результата
|
||||
const handleResFieldChange = e => {
|
||||
//Считываем нужный элемент
|
||||
const newResField = resFields.find(item => item.name === e.target.value);
|
||||
//Обновляем поле результата
|
||||
setState(pv => ({ ...pv, [e.target.name]: { name: newResField.name, value: newResField.value } }));
|
||||
//Определяем доступность поля "Элемент результата"
|
||||
setHasResElement(newResField?.hasElement || false);
|
||||
};
|
||||
|
||||
//При изменении оператора условия
|
||||
const handleOperatorChange = e => {
|
||||
//Считываем нужный элемент
|
||||
const newOperator = P8P_CC_OPERATORS.find(item => item.name === e.target.value);
|
||||
//Обновляем оператор
|
||||
setState(pv => ({ ...pv, [e.target.name]: { ...newOperator } }));
|
||||
};
|
||||
|
||||
//При инициализации условия
|
||||
useEffect(() => {
|
||||
//Если это иницализация
|
||||
if (init) {
|
||||
//Если это открытие условия - берем его параметры, иначе - инициализируем изначальными
|
||||
const initCondition = condition
|
||||
? condition
|
||||
: {
|
||||
...P8P_CC_INITIAL,
|
||||
condField: { name: condFields[0].name, value: condFields[0].value },
|
||||
resField: { name: resFields[0].name, value: resFields[0].value }
|
||||
};
|
||||
//Устанавливаем параметры действия
|
||||
setState(deepCopyObject(initCondition));
|
||||
//Определяем доступность "Элемент условия"
|
||||
setHasCondElement(condFields.find(item => item.name === initCondition.condField.name)?.hasElement || false);
|
||||
//Определяем доступность "Элемент результата"
|
||||
setHasResElement(resFields.find(item => item.name === initCondition.resField.name)?.hasElement || false);
|
||||
//Сбрасываем признак инициализации
|
||||
setInit(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [init]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{!init ? (
|
||||
<P8PConfigDialog
|
||||
title={`${condition ? TITLES.UPDATE : TITLES.INSERT} условия`}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okDisabled={isConditionOkDisabled(state)}
|
||||
>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
<Box>
|
||||
<Box sx={STYLES.BOX_COND_GROUP}>
|
||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("160px")}>
|
||||
<InputLabel id={"condField-label"}>Поле</InputLabel>
|
||||
<Select
|
||||
name={"condField"}
|
||||
value={state.condField.name}
|
||||
labelId={"condField-label"}
|
||||
label={"Поле"}
|
||||
onChange={handleCondFieldChange}
|
||||
>
|
||||
{condFields.map((item, index) => (
|
||||
<MenuItem value={item.name} key={index}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("70px")}>
|
||||
<InputLabel id={"condOperator-label"}>Оператор</InputLabel>
|
||||
<Select
|
||||
name={"condOperator"}
|
||||
value={state.condOperator.name}
|
||||
labelId={"condOperator-label"}
|
||||
label={"Оператор"}
|
||||
onChange={handleOperatorChange}
|
||||
>
|
||||
{P8P_CC_OPERATORS.map((item, index) => (
|
||||
<MenuItem value={item.name} key={index}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.condValue}
|
||||
label={"Значение"}
|
||||
name={"condValue"}
|
||||
onChange={handleChange}
|
||||
sx={STYLES.BOX_COND_ELEMENT("160px")}
|
||||
/>
|
||||
</Box>
|
||||
{hasCondElement ? (
|
||||
<TextField
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{ pb: 1.25 }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.condElement}
|
||||
label={"Элемент условия"}
|
||||
name={"condElement"}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
) : null}
|
||||
<Box sx={STYLES.BOX_COND_GROUP}>
|
||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("160px")}>
|
||||
<InputLabel id={"resField-label"}>Поле результата</InputLabel>
|
||||
<Select
|
||||
name={"resField"}
|
||||
value={state.resField.name}
|
||||
labelId={"resField-label"}
|
||||
label={"Поле результата"}
|
||||
onChange={handleResFieldChange}
|
||||
>
|
||||
{resFields.map((item, index) => (
|
||||
<MenuItem value={item.name} key={index}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.resValue}
|
||||
label={"Результат"}
|
||||
name={"resValue"}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</Box>
|
||||
{hasResElement ? (
|
||||
<TextField
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{ pb: 1.25 }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.resElement}
|
||||
label={"Элемент результата"}
|
||||
name={"resElement"}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
</Stack>
|
||||
</P8PConfigDialog>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Редактор условия
|
||||
P8PCCEditor.propTypes = {
|
||||
condition: P8P_CC_SHAPE,
|
||||
onOk: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
condFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||
resFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCCEditor };
|
||||
89
app/components/editors/p8p_component_condition/util.js
Normal file
89
app/components/editors/p8p_component_condition/util.js
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Вспомогательные ресурсы компонента "Редактор условия"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Функции операторов условий
|
||||
const P8P_CC_OPERATOR_FUNC = {
|
||||
equal: (objValue, value) => objValue == value,
|
||||
notEqual: (objValue, value) => objValue != value,
|
||||
lessEqual: (objValue, value) => objValue <= value,
|
||||
less: (objValue, value) => objValue < value,
|
||||
greaterEqual: (objValue, value) => objValue >= value,
|
||||
greater: (objValue, value) => objValue > value,
|
||||
in: (objValue, value) =>
|
||||
value
|
||||
.split(",")
|
||||
.map(item => item.trim())
|
||||
.includes(objValue + "")
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Загрузка условий в объект
|
||||
const loadConditionsToObject = (obj, conditions) => {
|
||||
//Инициализируем новый объект
|
||||
let newObj = { ...obj };
|
||||
//Инициализируем функцию оператора
|
||||
let operatorFunc;
|
||||
//Если изначальный индикатор загружен
|
||||
if (Object.keys(newObj).length !== 0) {
|
||||
//Обходим условия
|
||||
conditions.map(item => {
|
||||
//Функция оператора
|
||||
operatorFunc = P8P_CC_OPERATOR_FUNC[item.condOperator.value];
|
||||
//Проверяем условие
|
||||
if (operatorFunc && operatorFunc(newObj[item.condField.value], item.condValue)) {
|
||||
//Устанавливаем поле в новое значение
|
||||
newObj[item.resField.value] = item.resValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
//Возвращаем новый объект
|
||||
return newObj;
|
||||
};
|
||||
|
||||
//Считывание результатов условия
|
||||
const getConditionsValues = (obj, conditions, element = "") => {
|
||||
//Инициализируем функцию оператора
|
||||
let operatorFunc;
|
||||
//Инициализируем значение поля
|
||||
let condFieldValue = "";
|
||||
//Инициализируем результат
|
||||
let resObject = {};
|
||||
//Обходим условия
|
||||
conditions.map(item => {
|
||||
//Определяем значение поля условия
|
||||
condFieldValue = item.condElement ? obj[item.condElement] : obj[item.condField.value];
|
||||
//Функция оператора
|
||||
operatorFunc = P8P_CC_OPERATOR_FUNC[item.condOperator.value];
|
||||
//Проверяем условие
|
||||
if (operatorFunc && operatorFunc(condFieldValue, item.condValue)) {
|
||||
//Если в условии нет элемента результата или он равен текущему элементу
|
||||
if (!item.resElement || (element && item.resElement === element)) {
|
||||
resObject[item.resField.value] = item.resValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
//Возвращаем новый объект
|
||||
return resObject;
|
||||
};
|
||||
|
||||
//Доступность сохранения настроек условия
|
||||
const isConditionOkDisabled = condition => (!condition.condField.value || !condition.condOperator.value || !condition.resField.value ? true : false);
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { loadConditionsToObject, getConditionsValues, isConditionOkDisabled };
|
||||
41
app/components/editors/p8p_component_settings.js
Normal file
41
app/components/editors/p8p_component_settings.js
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Дополнительные настройки источников
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Наименование параметра данных компонента, полученных с сервера
|
||||
const P8P_COMPONENT_SETTINGS_RESP_ARGS = {
|
||||
INDICATOR: "XINDICATOR",
|
||||
CHART: "XCHART",
|
||||
TABLE: "XDATA_GRID"
|
||||
};
|
||||
|
||||
//Наименование путей компонента
|
||||
const P8P_COMPONENT_SETTINGS_PATHS = {
|
||||
INDICATOR: "indicator",
|
||||
CHART: "chart",
|
||||
TABLE: "table",
|
||||
FORM: "form"
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_COMPONENT_SETTINGS_RESP_ARGS, P8P_COMPONENT_SETTINGS_PATHS };
|
||||
99
app/components/editors/p8p_conditions.js
Normal file
99
app/components/editors/p8p_conditions.js
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Условия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PCCEditor } from "./p8p_component_condition/editor"; //Редактор условия
|
||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||
import { P8P_CCS_INITIAL, P8P_CC_SHAPE, P8P_CC_FIELD_PRM_SHAPE } from "./p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Условия
|
||||
const P8PConditions = ({ conditions = P8P_CCS_INITIAL, condFields, resFields, onChange = null } = {}) => {
|
||||
//Собственное состояние - редактор условий
|
||||
const [conditionEditor, setConditionEditor] = useState({ display: false, index: null });
|
||||
|
||||
//При изменении условий
|
||||
const handleConditionsChange = conditions => onChange && onChange(conditions);
|
||||
|
||||
//При добавлении нового условия
|
||||
const handleConditionAdd = () => setConditionEditor({ display: true, index: null });
|
||||
|
||||
//При нажатии на условие
|
||||
const handleConditionClick = i => setConditionEditor({ display: true, index: i });
|
||||
|
||||
//При удалении условия
|
||||
const handleConditionDelete = i => {
|
||||
const newConditions = [...conditions];
|
||||
newConditions.splice(i, 1);
|
||||
handleConditionsChange(newConditions);
|
||||
};
|
||||
|
||||
//При отмене сохранения изменений условия
|
||||
const handleConditionCancel = () => setConditionEditor({ display: false, index: null });
|
||||
|
||||
//При сохранении изменений условия
|
||||
const handleConditionSave = condition => {
|
||||
const newConditions = [...conditions];
|
||||
conditionEditor.index == null ? newConditions.push({ ...condition }) : (newConditions[conditionEditor.index] = { ...condition });
|
||||
handleConditionsChange(newConditions);
|
||||
setConditionEditor({ display: false, index: null });
|
||||
};
|
||||
|
||||
//Определяем структуру условий для отображения
|
||||
const conditionChips = conditions.map(item => {
|
||||
//Собираем текст условия
|
||||
let text = `${item.condField.name} ${item.condOperator.name} ${item.condValue}`;
|
||||
//Считываем поле результата
|
||||
let resField = resFields.find(field => field.name === item.resField.name);
|
||||
//Формируем структуру для отображения карточки действия
|
||||
return { text: text, title: text, icon: resField?.icon, iconTitle: resField?.name };
|
||||
});
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{conditionEditor.display && (
|
||||
<P8PCCEditor
|
||||
condition={conditionEditor.index !== null ? { ...conditions[conditionEditor.index] } : null}
|
||||
onCancel={handleConditionCancel}
|
||||
onOk={handleConditionSave}
|
||||
condFields={condFields}
|
||||
resFields={resFields}
|
||||
/>
|
||||
)}
|
||||
<P8PChipList items={conditionChips} onClick={handleConditionClick} onDelete={handleConditionDelete} />
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handleConditionAdd}>
|
||||
Добавить условие
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - условия
|
||||
P8PConditions.propTypes = {
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
condFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||
resFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PConditions };
|
||||
@ -9,17 +9,17 @@
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PDialog } from "../p8p_dialog"; //Типовой диалог
|
||||
import { P8PDialog, P8P_DIALOG_WIDTH } from "../p8p_dialog"; //Типовой диалог
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог настройки
|
||||
const P8PConfigDialog = ({ title, children, onOk, onCancel }) => {
|
||||
const P8PConfigDialog = ({ title, children, width, onOk, onCancel, okDisabled = false }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PDialog title={title} onOk={onOk} onCancel={onCancel}>
|
||||
<P8PDialog title={title} onOk={onOk} onCancel={onCancel} width={width} okDisabled={okDisabled}>
|
||||
{children}
|
||||
</P8PDialog>
|
||||
);
|
||||
@ -29,8 +29,10 @@ const P8PConfigDialog = ({ title, children, onOk, onCancel }) => {
|
||||
P8PConfigDialog.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||
width: PropTypes.oneOf(Object.values(P8P_DIALOG_WIDTH)),
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
onCancel: PropTypes.func,
|
||||
okDisabled: PropTypes.bool
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
||||
@ -9,11 +9,11 @@
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, IconButton, Icon, Typography, Chip, Button, Card, CardContent, CardActions, CardActionArea } from "@mui/material"; //Интерфейсные элементы
|
||||
import { Stack, IconButton, Icon, Typography, Button, Card, CardContent, CardActions, CardActionArea } from "@mui/material"; //Интерфейсные элементы
|
||||
import { BUTTONS, TEXTS } from "../../../app.text"; //Общие текстовые ресурсы
|
||||
import { STYLES as COMMON_STYLES } from "./p8p_editors_common"; //Общие ресурсы редаторов
|
||||
import { P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_TYPE_NAME, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
||||
import { P8PDataSourceConfigDialog } from "./p8p_data_source_config_dialog"; //Диалог настройки источника данных
|
||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
@ -45,17 +45,9 @@ const P8PDataSource = ({ dataSource = null, valueProviders = {}, onChange = null
|
||||
//Расчет флага "настроенности"
|
||||
const configured = dataSource?.type ? true : false;
|
||||
|
||||
//Список аргументов
|
||||
const args =
|
||||
configured &&
|
||||
dataSource.arguments.map((argument, i) => (
|
||||
<Chip
|
||||
key={i}
|
||||
label={`:${argument.name} = ${argument.valueSource || argument.value || "NULL"}`}
|
||||
variant={"outlined"}
|
||||
sx={COMMON_STYLES.CHIP(true)}
|
||||
/>
|
||||
));
|
||||
//Структура параметров для отображения
|
||||
const argChips =
|
||||
configured && dataSource.arguments.map(argument => ({ text: `:${argument.name} = ${argument.valueSource || argument.value || "NULL"}` }));
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
@ -79,7 +71,7 @@ const P8PDataSource = ({ dataSource = null, valueProviders = {}, onChange = null
|
||||
{P8P_DATA_SOURCE_TYPE_NAME[dataSource.type] || TEXTS.UNKNOWN_SOURCE_TYPE}
|
||||
</Typography>
|
||||
<Stack direction={"column"} spacing={1} pt={2}>
|
||||
{args}
|
||||
<P8PChipList items={argChips} />
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
|
||||
@ -67,6 +67,7 @@ const P8P_DATA_SOURCE_SHAPE = PropTypes.shape({
|
||||
const P8P_DATA_SOURCE_INITIAL = {
|
||||
type: "",
|
||||
userProc: "",
|
||||
query: "",
|
||||
stored: "",
|
||||
respArg: "",
|
||||
arguments: []
|
||||
|
||||
@ -15,6 +15,8 @@ import { TITLES, CAPTIONS } from "../../../app.text"; //Общие тексто
|
||||
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_ARGUMENT_INITIAL, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
||||
import { useUserProcDesc } from "./p8p_data_source_hooks"; //Хуки источников данных
|
||||
import { P8PDataSourceQuerySelector } from "./p8p_data_source_query_selector"; //Диалог выбора записи редактора запросов
|
||||
import { hasValue } from "../../core/utils"; //Вспомогательные функции
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
@ -34,6 +36,12 @@ const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onO
|
||||
//Описание выбранной пользовательской процедуры
|
||||
const [userProcDesc] = useUserProcDesc({ code: state.userProc, refresh: refresh.userProcDesc });
|
||||
|
||||
//Собственное состояние - отображение диалога выбора запроса
|
||||
const [openQuerySelector, setOpenQuerySelector] = useState(false);
|
||||
|
||||
//Собственное состояние - доступность полей выбора источника
|
||||
const [disabledFields, setDisabledFields] = useState({ query: false, userProc: false });
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
@ -86,6 +94,21 @@ const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onO
|
||||
//При вводе значения аргумента
|
||||
const handleArgumentChange = (index, value) => setArgumentValueSource(index, value, "");
|
||||
|
||||
//Открытие диалога выбора запроса
|
||||
const handleOpenQuerySelector = () => setOpenQuerySelector(true);
|
||||
|
||||
//Закрытие диалога выбора запроса
|
||||
const handleCancelQuerySelector = () => setOpenQuerySelector(false);
|
||||
|
||||
//При нажатии на очистку мнемокода запроса
|
||||
const handleQueryClearClick = () => setState({ ...P8P_DATA_SOURCE_INITIAL });
|
||||
|
||||
//При нажатии на выбор запроса в качестве источника данных
|
||||
const handleQuerySelectClick = query => {
|
||||
setState(pv => ({ ...pv, type: P8P_DATA_SOURCE_TYPE.QUERY, query: query.code }));
|
||||
handleCancelQuerySelector();
|
||||
};
|
||||
|
||||
//При изменении описания пользовательской процедуры
|
||||
useEffect(() => {
|
||||
if (userProcDesc)
|
||||
@ -97,8 +120,22 @@ const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onO
|
||||
}));
|
||||
}, [userProcDesc]);
|
||||
|
||||
//При изменении источника
|
||||
useEffect(() => {
|
||||
/*
|
||||
Если выбран запрос - блокируем выбор процедуры
|
||||
Если выбрана процедура - блокируем выбор запроса
|
||||
Ничего не выбрано - доступны все варианты
|
||||
*/
|
||||
hasValue(state.query)
|
||||
? setDisabledFields({ query: false, userProc: true })
|
||||
: hasValue(state.userProc)
|
||||
? setDisabledFields({ query: true, userProc: false })
|
||||
: setDisabledFields({ query: false, userProc: false });
|
||||
}, [state.query, state.userProc]);
|
||||
|
||||
//Список значений
|
||||
const values = Object.keys(valueProviders).reduce((res, key) => [...res, ...Object.keys(valueProviders[key])], []);
|
||||
const values = Object.keys(valueProviders);
|
||||
|
||||
//Наличие значений
|
||||
const isValues = values && values.length > 0 ? true : false;
|
||||
@ -114,59 +151,90 @@ const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onO
|
||||
</Menu>
|
||||
);
|
||||
|
||||
//Доступность сохранения настройки источника данных
|
||||
const okDisabled = !state.query && !state.userProc ? true : false;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PConfigDialog title={TITLES.DATA_SOURCE_CONFIG} onOk={handleOk} onCancel={handleCancel}>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{valueProvidersMenu}
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.userProc}
|
||||
label={CAPTIONS.USER_PROC}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleUserProcClearClick}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleUserProcSelectClick}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{Array.isArray(state?.arguments) &&
|
||||
state.arguments.map((argument, i) => (
|
||||
<TextField
|
||||
key={i}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={argument.value || argument.valueSource}
|
||||
label={argument.caption}
|
||||
onChange={e => handleArgumentChange(i, e.target.value)}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleArgumentClearClick(i)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
{isValues && (
|
||||
<IconButton id={i} onClick={handleArgumentLinkMenuClick}>
|
||||
<Icon>settings_ethernet</Icon>
|
||||
<>
|
||||
{openQuerySelector ? (
|
||||
<P8PDataSourceQuerySelector current={state.query} onSelect={handleQuerySelectClick} onCancel={handleCancelQuerySelector} />
|
||||
) : null}
|
||||
<P8PConfigDialog title={TITLES.DATA_SOURCE_CONFIG} onOk={handleOk} onCancel={handleCancel} okDisabled={okDisabled}>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{valueProvidersMenu}
|
||||
{/* ДОРАБАТЫВАТЬ ПОСЛЕ РЕАЛИЗАЦИИ ЗАПРОСОВ */}
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.query}
|
||||
label={CAPTIONS.QUERY}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleQueryClearClick} disabled={disabledFields.query}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleOpenQuerySelector} disabled={disabledFields.query}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
disabled={disabledFields.query}
|
||||
/>
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.userProc}
|
||||
label={CAPTIONS.USER_PROC}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleUserProcClearClick} disabled={disabledFields.userProc}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleUserProcSelectClick} disabled={disabledFields.userProc}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
disabled={disabledFields.userProc}
|
||||
/>
|
||||
{Array.isArray(state?.arguments) &&
|
||||
state.arguments.map((argument, i) => (
|
||||
<TextField
|
||||
key={i}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={argument.value || argument.valueSource}
|
||||
label={argument.caption}
|
||||
onChange={e => handleArgumentChange(i, e.target.value)}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleArgumentClearClick(i)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</P8PConfigDialog>
|
||||
{isValues && (
|
||||
<IconButton id={i} onClick={handleArgumentLinkMenuClick}>
|
||||
<Icon>settings_ethernet</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</P8PConfigDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -13,6 +13,9 @@ import { ERRORS } from "../../../app.text"; //Общие текстовые ре
|
||||
import { formatErrorMessage } from "../../core/utils"; //Общие вспомогательные функции
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE } from "./p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import { getConditionsValues } from "./p8p_component_condition/util"; //Вспомогательные ресурсы условий
|
||||
import { getHandlersByActions } from "./p8p_component_action/util"; //Вспомогательные ресурсы действий
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
@ -58,7 +61,7 @@ const useUserProcDesc = ({ code, refresh }) => {
|
||||
};
|
||||
|
||||
//Получение данных из источника
|
||||
const useDataSource = ({ dataSource, values }) => {
|
||||
const useDataSource = ({ dataSource, values, componentRespArg = "" }) => {
|
||||
//Контроллер для прерывания запросов
|
||||
const abortController = useRef(null);
|
||||
|
||||
@ -69,14 +72,25 @@ const useDataSource = ({ dataSource, values }) => {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState({ init: false });
|
||||
const [data, setData] = useState({ componentData: {}, init: false });
|
||||
|
||||
//Собственное состояние - ошибка получения данных
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
//Собственное состояние - наличие настроек
|
||||
const [haveConfing, setHaveConfig] = useState(false);
|
||||
|
||||
//Собственное состояние - наличие данных
|
||||
const [haveData, setHaveData] = useState(false);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости обновление информации о наличии данных
|
||||
useEffect(() => {
|
||||
setHaveData(data.init === true && !error ? true : false);
|
||||
}, [data.init, error]);
|
||||
|
||||
//При необходимости обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
@ -94,11 +108,11 @@ const useDataSource = ({ dataSource, values }) => {
|
||||
showErrorMessage: false
|
||||
});
|
||||
setError(null);
|
||||
setData({ ...data, init: true });
|
||||
setData({ componentData: { ...data[componentRespArg] }, init: true });
|
||||
} catch (e) {
|
||||
if (e.message !== client.ERR_ABORTED) {
|
||||
setError(formatErrorMessage(e.message).text);
|
||||
setData({ init: false });
|
||||
setData({ componentData: {}, init: false });
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@ -106,12 +120,15 @@ const useDataSource = ({ dataSource, values }) => {
|
||||
};
|
||||
if (state.reqSet) {
|
||||
if (state.stored) loadData();
|
||||
} else setData({ init: false });
|
||||
} else setData({ componentData: {}, init: false });
|
||||
return () => abortController.current?.abort?.();
|
||||
}, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored]);
|
||||
}, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored, componentRespArg]);
|
||||
|
||||
//При изменении свойств
|
||||
useEffect(() => {
|
||||
//Устанавливаем признак наличия настроек
|
||||
setHaveConfig(dataSource?.stored ? true : false);
|
||||
//Устанавливаем параметры исполнения
|
||||
setState(pv => {
|
||||
if (dataSource?.type == P8P_DATA_SOURCE_TYPE.USER_PROC) {
|
||||
const { stored, respArg } = dataSource;
|
||||
@ -132,7 +149,7 @@ const useDataSource = ({ dataSource, values }) => {
|
||||
if (pv.stored != stored || pv.respArg != respArg || JSON.stringify(pv.storedArgs) != JSON.stringify(storedArgs)) {
|
||||
if (!reqSet) {
|
||||
setError(ERRORS.DATA_SOURCE_NO_REQ_ARGS);
|
||||
setData({ init: false });
|
||||
setData({ componentData: {}, init: false });
|
||||
}
|
||||
return { stored, respArg, storedArgs, reqSet };
|
||||
} else return pv;
|
||||
@ -141,11 +158,72 @@ const useDataSource = ({ dataSource, values }) => {
|
||||
}, [dataSource, values]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, error, isLoading];
|
||||
return [data.componentData, error, haveConfing, haveData, isLoading];
|
||||
};
|
||||
|
||||
//Изменение данных компонента с учетом условий
|
||||
const useConditions = ({ componentData, conditions }) => {
|
||||
//Собственное состояние - текущие условия компонента
|
||||
const [currentConditions, setCurrentConditions] = useState([]);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState();
|
||||
|
||||
//При обновлении условий компонента
|
||||
useEffect(() => {
|
||||
//Если условия изменились
|
||||
if (JSON.stringify(currentConditions) != JSON.stringify(conditions)) {
|
||||
//Устанавливаем новые условия
|
||||
setCurrentConditions(conditions);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [conditions]);
|
||||
|
||||
//При обновлении данных или условий компонента
|
||||
useEffect(() => {
|
||||
//Если есть текущие условия
|
||||
if (currentConditions.length !== 0) {
|
||||
//Устанавливаем данные с учетом условий
|
||||
setData({ ...componentData, ...getConditionsValues(componentData, currentConditions) });
|
||||
} else {
|
||||
//Оставляем данные компонента
|
||||
setData({ ...componentData });
|
||||
}
|
||||
}, [currentConditions, componentData]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data];
|
||||
};
|
||||
|
||||
//Получение обработчиков компонента
|
||||
const useComponentHandlers = ({ actions = [], onValuesChange = null, getCustomTypeValue = null }) => {
|
||||
//Контроллер для текущего состояния действий
|
||||
const currentActions = useRef([]);
|
||||
|
||||
//Собственное состояние - обработчики компонента
|
||||
const [handlers, setHandlers] = useState({});
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { configUrlBase, pOnlineShowTab, pOnlineShowUnit } = useContext(ApplicationСtx);
|
||||
|
||||
//При необходимости обновления информации об обработчиках
|
||||
useEffect(() => {
|
||||
//Если изменились действия или параметры
|
||||
if (JSON.stringify(currentActions.current) != JSON.stringify(actions)) {
|
||||
//Считываем обработчики компонента
|
||||
setHandlers(getHandlersByActions(actions, pOnlineShowUnit, configUrlBase, pOnlineShowTab, onValuesChange, getCustomTypeValue));
|
||||
//Устанавливаем контроллер текущих действий
|
||||
currentActions.current = actions;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [actions]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [handlers];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useUserProcDesc, useDataSource };
|
||||
export { useUserProcDesc, useDataSource, useConditions, useComponentHandlers };
|
||||
|
||||
75
app/components/editors/p8p_data_source_query_selector.js
Normal file
75
app/components/editors/p8p_data_source_query_selector.js
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Диалог выбора записи редактора запросов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, List, ListItem, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
LIST: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог выбора записи редактора запросов
|
||||
const P8PDataSourceQuerySelector = ({ current = null, onSelect, onCancel }) => {
|
||||
//!!! НА РЕАЛИЗАЦИИ
|
||||
const test = [{ code: "TestCode", name: "TestName" }];
|
||||
//При выборе элемента списка
|
||||
const handleSelectClick = query => {
|
||||
onSelect && onSelect(query);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PConfigDialog title={"Запросы"} onCancel={onCancel}>
|
||||
<List sx={STYLES.LIST}>
|
||||
{test.map((query, i) => {
|
||||
const selected = query.code === current;
|
||||
return (
|
||||
<ListItem key={i}>
|
||||
<ListItemButton onClick={() => handleSelectClick(query)} selected={selected}>
|
||||
<ListItemText
|
||||
primary={query.code}
|
||||
secondaryTypographyProps={{ component: "div" }}
|
||||
secondary={
|
||||
<Stack direction={"column"}>
|
||||
<Typography variant={"caption"}>{`${query.code}, ${query.name}`}</Typography>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</P8PConfigDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалог выбора записи редактора запросов
|
||||
P8PDataSourceQuerySelector.propTypes = {
|
||||
current: PropTypes.string,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PDataSourceQuerySelector };
|
||||
@ -17,7 +17,7 @@ import { BUTTONS } from "../../../app.text"; //Общие текстовые р
|
||||
//-----------
|
||||
|
||||
//Контейнер редактора
|
||||
const P8PEditorBox = ({ title, children, onSave }) => {
|
||||
const P8PEditorBox = ({ title, children, onSave, allowClose = true }) => {
|
||||
//При нажатии на "Сохранить"
|
||||
const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor);
|
||||
|
||||
@ -36,9 +36,11 @@ const P8PEditorBox = ({ title, children, onSave }) => {
|
||||
<IconButton onClick={() => handleSaveClick(false)} title={BUTTONS.APPLY}>
|
||||
<Icon>done</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={() => handleSaveClick(true)} title={BUTTONS.SAVE}>
|
||||
<Icon>done_all</Icon>
|
||||
</IconButton>
|
||||
{allowClose ? (
|
||||
<IconButton onClick={() => handleSaveClick(true)} title={BUTTONS.SAVE}>
|
||||
<Icon>done_all</Icon>
|
||||
</IconButton>
|
||||
) : null}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
@ -49,7 +51,8 @@ const P8PEditorBox = ({ title, children, onSave }) => {
|
||||
P8PEditorBox.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||
onSave: PropTypes.func
|
||||
onSave: PropTypes.func,
|
||||
allowClose: PropTypes.bool
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { IconButton, Icon, Stack, Grid } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -20,7 +20,8 @@ const P8P_EDITOR_TOOL_BAR_ITEM_SHAPE = PropTypes.shape({
|
||||
icon: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired
|
||||
onClick: PropTypes.func.isRequired,
|
||||
customRenderer: PropTypes.func
|
||||
});
|
||||
|
||||
//-----------
|
||||
@ -32,11 +33,19 @@ const P8PEditorToolBar = ({ items = [] }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<Stack direction={"row"} p={1}>
|
||||
{items.map((item, i) => (
|
||||
<IconButton key={i} onClick={item.onClick} title={item.title} disabled={item?.disabled === true}>
|
||||
<Icon>{item.icon}</Icon>
|
||||
</IconButton>
|
||||
))}
|
||||
<Grid container columns={items.length}>
|
||||
{items.map((item, i) => (
|
||||
<Grid item size={{ xs: 2, sm: 4, md: 4 }} key={i}>
|
||||
{item.customRenderer ? (
|
||||
item.customRenderer({ icon: item.icon, title: item.title, disabled: item?.disabled === true, onClick: item.onClick })
|
||||
) : (
|
||||
<IconButton onClick={item.onClick} title={item.title} disabled={item?.disabled === true}>
|
||||
<Icon>{item.icon}</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@ -23,8 +23,17 @@ const STYLES = {
|
||||
})
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Проверка корректности наименования элемента формы
|
||||
const isElementNameCorrect = elementName => {
|
||||
return new RegExp(/^[\w\d_]*$/).test(elementName);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { STYLES };
|
||||
export { STYLES, isElementNameCorrect };
|
||||
|
||||
@ -253,13 +253,13 @@ const P8PAppInlineInfo = props => buildVariantInlineMessage(props, P8P_APP_MESSA
|
||||
//Диалог подсказки
|
||||
const P8PHintDialog = ({ title, hint, onOk }) => {
|
||||
return (
|
||||
<Dialog open={true} onClose={() => (onOk ? onOk() : null)}>
|
||||
<Dialog open={true} onClose={e => (onOk ? onOk(e) : null)}>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<div dangerouslySetInnerHTML={{ __html: hint }}></div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => (onOk ? onOk() : null)}>{BUTTONS.OK}</Button>
|
||||
<Button onClick={e => (onOk ? onOk(e) : null)}>{BUTTONS.OK}</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@ -61,7 +61,7 @@ const STYLES = {
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer",
|
||||
"&:hover": { backgroundColor: APP_COLORS.HOVER.color },
|
||||
"&:hover": { filter: "brightness(0.92) !important" },
|
||||
"&:active": { backgroundColor: APP_COLORS.ACTIVE.color }
|
||||
}
|
||||
: {})
|
||||
@ -69,7 +69,21 @@ const STYLES = {
|
||||
ICON: (state, userColor) => ({ fontSize: "50px", ...getColor(state, userColor) }),
|
||||
HINT_ICON: (state, userColor) => ({ fontSize: "1rem", ...getColor(state, userColor) }),
|
||||
VALUE_CAPTION_STACK: { containerType: "inline-size", width: "100%", overflow: "hidden" },
|
||||
CAPTION_TYPOGRAPHY: { width: "99cqw" }
|
||||
CAPTION_TYPOGRAPHY: clickable => ({
|
||||
width: "99cqw",
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
}),
|
||||
VALUE_TYPOGRAPHY: clickable => ({
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
@ -98,6 +112,8 @@ const P8PIndicator = ({
|
||||
variant = P8P_INDICATOR_VARIANT.ELEVATION,
|
||||
hint = null,
|
||||
onClick = null,
|
||||
onValueClick = null,
|
||||
onCaptionClick = null,
|
||||
backgroundColor = null,
|
||||
color = null
|
||||
} = {}) => {
|
||||
@ -114,14 +130,39 @@ const P8PIndicator = ({
|
||||
};
|
||||
|
||||
//При нажатии на кнопку закрытия подсказки
|
||||
const handleHintClose = () => setShowHint(false);
|
||||
const handleHintClose = e => {
|
||||
setShowHint(false);
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
//При нажатии на заголовок
|
||||
const handleCaptionClick = event => {
|
||||
//Если есть функция нажатия на заголовок
|
||||
onCaptionClick && onCaptionClick(event);
|
||||
};
|
||||
|
||||
//При нажатии на значение
|
||||
const handleValueClick = event => {
|
||||
//Если есть функция нажатия на заголовок
|
||||
onValueClick && onValueClick(event);
|
||||
};
|
||||
|
||||
//Представление текста значения индикатора
|
||||
const valueTextView = <Typography variant={"h4"}>{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}</Typography>;
|
||||
const valueTextView = (
|
||||
<Typography variant={"h4"} sx={STYLES.VALUE_TYPOGRAPHY(onValueClick ? true : false)} onClick={handleValueClick}>
|
||||
{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
//Представление текста подписи индикатора
|
||||
const captionView = (
|
||||
<Typography align={"left"} noWrap={true} sx={STYLES.CAPTION_TYPOGRAPHY} title={caption}>
|
||||
<Typography
|
||||
align={"left"}
|
||||
noWrap={true}
|
||||
sx={STYLES.CAPTION_TYPOGRAPHY(onCaptionClick ? true : false)}
|
||||
title={caption}
|
||||
onClick={handleCaptionClick}
|
||||
>
|
||||
{caption}
|
||||
</Typography>
|
||||
);
|
||||
@ -175,6 +216,8 @@ P8PIndicator.propTypes = {
|
||||
variant: PropTypes.oneOf(Object.values(P8P_INDICATOR_VARIANT)),
|
||||
hint: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
onValueClick: PropTypes.func,
|
||||
onCaptionClick: PropTypes.func,
|
||||
backgroundColor: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
};
|
||||
|
||||
@ -195,6 +195,9 @@ const genGUID = () =>
|
||||
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
||||
);
|
||||
|
||||
//Формироваеие уникального идентификатора по текущему времени
|
||||
const genUID = prefix => (prefix ? `${prefix}_${Date.now()}` : `${Date.now()}`);
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
@ -211,5 +214,6 @@ export {
|
||||
formatDateJSONDateOnly,
|
||||
formatNumberRFCurrency,
|
||||
formatErrorMessage,
|
||||
genGUID
|
||||
genGUID,
|
||||
genUID
|
||||
};
|
||||
|
||||
@ -12,24 +12,35 @@ import PropTypes from "prop-types"; //Контроль свойств компо
|
||||
import { Box, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||
import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
COMPONENT_BOX: isDragging => {
|
||||
return isDragging ? { pointerEvents: "none" } : {};
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Представление компонента панели
|
||||
const ComponentView = ({ id, path, settings = {}, values = {}, onValuesChange = null } = {}) => {
|
||||
const ComponentView = ({ id, isDragging, path, settings = {}, values = {}, onValuesChange = null } = {}) => {
|
||||
//Подгрузка модуля представления компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
|
||||
const [ComponentView, init] = useComponentModule({ path, module: "view" });
|
||||
|
||||
//При смене значений
|
||||
const handleValuesChange = values => onValuesChange && onValuesChange(id, values);
|
||||
const handleValuesChange = values => onValuesChange && onValuesChange(values);
|
||||
|
||||
//Расчёт флага наличия компонента
|
||||
const haveComponent = path ? true : false;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box className={"component-view__wrap"}>
|
||||
<Box className={"component-view__wrap"} sx={STYLES.COMPONENT_BOX(isDragging)}>
|
||||
{haveComponent && init && <ComponentView.default id={id} {...settings} values={values} onValuesChange={handleValuesChange} />}
|
||||
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
|
||||
</Box>
|
||||
@ -39,6 +50,7 @@ const ComponentView = ({ id, path, settings = {}, values = {}, onValuesChange =
|
||||
//Контроль свойств компонента - компонент панели
|
||||
ComponentView.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
settings: PropTypes.object,
|
||||
values: PropTypes.object,
|
||||
|
||||
51
app/panels/panels_editor/components/chart/action.js
Normal file
51
app/panels/panels_editor/components/chart/action.js
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: График (общие ресурсы действия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Собственные значения типа действия
|
||||
const P8P_CA_CHART_TYPE_VALUE = {
|
||||
GRPH_ELEMENT: "Элемент графика"
|
||||
};
|
||||
|
||||
//Типы значений действий графика
|
||||
const P8P_CA_CHART_VALUE_TYPES = [...Object.values(P8P_CA_CHART_TYPE_VALUE)];
|
||||
|
||||
//Доступные области действий графика
|
||||
const P8P_CA_CHART_ACTION_AREAS = [
|
||||
{ name: "Компонент", area: "component", hasElement: false },
|
||||
{ name: "Элемент графика", area: "chart_item", hasElement: false }
|
||||
];
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Получение значения собственного типа действия
|
||||
const getChartCustomTypeValue = ({ type, value, prms }) => {
|
||||
//Если это элемента графика - возвращаем нужное значение, иначе - null
|
||||
return type === P8P_CA_CHART_TYPE_VALUE.GRPH_ELEMENT ? prms.item[value] : null;
|
||||
};
|
||||
|
||||
//Считывание обработчиков графика
|
||||
const getChartHandlers = handlers => {
|
||||
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||
return {
|
||||
onComponentClick: handlers["component."]?.fn,
|
||||
onChartItemClick: handlers["chart_item."]?.fn
|
||||
};
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CA_CHART_VALUE_TYPES, P8P_CA_CHART_ACTION_AREAS, getChartCustomTypeValue, getChartHandlers };
|
||||
@ -13,32 +13,46 @@ import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { P8P_CA_CHART_VALUE_TYPES, P8P_CA_CHART_ACTION_AREAS } from "./action"; //Общие ресурсы действий графика
|
||||
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//График (редактор настроек)
|
||||
const ChartEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
|
||||
const ChartEditor = ({ id, dataSource = null, valueProviders = {}, actions = P8P_CAS_INITIAL, onSettingsChange = null } = {}) => {
|
||||
//Собственное состояние - текущие настройки
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource });
|
||||
}, [settings, id, dataSource]);
|
||||
|
||||
//При сохранении изменений элемента
|
||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||
|
||||
//При изменении действий
|
||||
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||
|
||||
//При сохранении настроек
|
||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource, actions });
|
||||
}, [settings, id, dataSource, actions]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PEditorBox title={"Параметры графика"} onSave={handleSave}>
|
||||
<P8PEditorSubHeader title={"Источник данных"} />
|
||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||
<P8PEditorSubHeader title={"Действия"} />
|
||||
<P8PActions
|
||||
actions={settings?.actions}
|
||||
valueProviders={valueProviders}
|
||||
areas={P8P_CA_CHART_ACTION_AREAS}
|
||||
valueTypes={P8P_CA_CHART_VALUE_TYPES}
|
||||
onChange={handleActionsChange}
|
||||
/>
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
@ -48,6 +62,7 @@ ChartEditor.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onSettingsChange: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
@ -11,13 +11,16 @@ import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PChart } from "../../../../components/p8p_chart"; //График
|
||||
import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { useDataSource, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import {
|
||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||
P8P_COMPONENT_INLINE_MESSAGE,
|
||||
P8PComponentInlineMessage
|
||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||
import { P8P_CA_SHAPE } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { getChartCustomTypeValue, getChartHandlers } from "./action"; //Общие ресурсы действий графика
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -31,7 +34,14 @@ const COMPONENT_NAME = "График";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CHART: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", display: "flex" }
|
||||
CHART: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", display: "flex" },
|
||||
CONTAINER: clickable => ({
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//-----------
|
||||
@ -39,24 +49,31 @@ const STYLES = {
|
||||
//-----------
|
||||
|
||||
//График (представление)
|
||||
const Chart = ({ dataSource = null, values = {} } = {}) => {
|
||||
const Chart = ({ dataSource = null, values = {}, actions = [], onValuesChange = null } = {}) => {
|
||||
//Собственное состояние - данные
|
||||
const [data, error] = useDataSource({ dataSource, values });
|
||||
const [chart, error, haveConfing, haveData] = useDataSource({ dataSource, values, componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.CHART });
|
||||
|
||||
//Флаг настроенности графика
|
||||
const haveConfing = dataSource?.stored ? true : false;
|
||||
//Собственное состояние - обработчики компонента
|
||||
const [handlers] = useComponentHandlers({ actions, onValuesChange, getCustomTypeValue: getChartCustomTypeValue });
|
||||
|
||||
//Флаг наличия данных
|
||||
const haveData = data?.init === true && !error ? true : false;
|
||||
|
||||
//Данные графика
|
||||
const chart = data?.XCHART || {};
|
||||
//Обработчики областей
|
||||
const { onComponentClick, onChartItemClick } = getChartHandlers(handlers);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Paper className={"component-view__container component-view__container__empty"} elevation={6}>
|
||||
<Paper
|
||||
sx={STYLES.CONTAINER(onComponentClick)}
|
||||
className={"component-view__container component-view__container__empty"}
|
||||
elevation={6}
|
||||
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||
>
|
||||
{haveConfing && haveData ? (
|
||||
<P8PChart style={STYLES.CHART} {...chart} />
|
||||
<P8PChart
|
||||
style={STYLES.CHART}
|
||||
{...chart}
|
||||
options={{ responsive: true, maintainAspectRatio: false }}
|
||||
onClick={prms => onChartItemClick && onChartItemClick({ values, prms })}
|
||||
/>
|
||||
) : (
|
||||
<P8PComponentInlineMessage
|
||||
icon={COMPONENT_ICON}
|
||||
@ -72,7 +89,9 @@ const Chart = ({ dataSource = null, values = {} } = {}) => {
|
||||
//Контроль свойств компонента - График (представление)
|
||||
Chart.propTypes = {
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
values: PropTypes.object
|
||||
values: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onValuesChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
||||
@ -7,123 +7,51 @@
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
const COMPONETNS = [
|
||||
const COMPONENTS = [
|
||||
{
|
||||
name: "Форма",
|
||||
path: "form",
|
||||
settings2: {
|
||||
title: "Параметры формирования",
|
||||
autoApply: true,
|
||||
items: [
|
||||
{
|
||||
name: "AGENT",
|
||||
caption: "Контрагент",
|
||||
unitCode2: "AGNLIST",
|
||||
unitName: "Контрагенты",
|
||||
showMethod: "main",
|
||||
showMethodName: "main",
|
||||
parameter: "Мнемокод",
|
||||
inputParameter: "in_AGNABBR",
|
||||
outputParameter: "out_AGNABBR"
|
||||
},
|
||||
{
|
||||
name: "DOC_TYPE",
|
||||
caption: "Тип документа",
|
||||
unitCode2: "DOCTYPES",
|
||||
unitName: "Типы документов",
|
||||
showMethod: "main",
|
||||
showMethodName: "main",
|
||||
parameter: "Мнемокод",
|
||||
inputParameter: "in_DOCCODE",
|
||||
outputParameter: "out_DOCCODE"
|
||||
}
|
||||
]
|
||||
settings: {
|
||||
id: "",
|
||||
title: "",
|
||||
autoApply: false,
|
||||
orientation: "v",
|
||||
items: []
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "График",
|
||||
path: "chart",
|
||||
settings2: {
|
||||
dataSource: {
|
||||
type: "USER_PROC",
|
||||
userProc: "ГрафТоп5ДогКонтрТип",
|
||||
stored: "UDO_P_P8P_AGNCONTR_CHART",
|
||||
respArg: "COUT",
|
||||
arguments: [
|
||||
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
|
||||
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" }
|
||||
]
|
||||
}
|
||||
settings: {
|
||||
id: "",
|
||||
dataSource: {},
|
||||
actions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Таблица",
|
||||
path: "table",
|
||||
settings2: {
|
||||
dataSource: {
|
||||
type: "USER_PROC",
|
||||
userProc: "ТаблицаДогКонтрТип",
|
||||
stored: "UDO_P_P8P_AGNCONTR_TABLE",
|
||||
respArg: "COUT",
|
||||
arguments: [
|
||||
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
|
||||
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" }
|
||||
]
|
||||
}
|
||||
settings: {
|
||||
id: "",
|
||||
dataSource: {},
|
||||
actions: [],
|
||||
conditions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Индикатор",
|
||||
path: "indicator",
|
||||
settings2: {
|
||||
dataSource: {
|
||||
type: "USER_PROC",
|
||||
userProc: "ИндКолДогКонтрТип",
|
||||
stored: "UDO_P_P8P_AGNCONTR_IND",
|
||||
respArg: "COUT",
|
||||
arguments: [
|
||||
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
|
||||
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" },
|
||||
{
|
||||
name: "NIND_TYPE",
|
||||
caption: "Тип индикатора (0 - все, 1 - неутвержденные)",
|
||||
dataType: "NUMB",
|
||||
req: true,
|
||||
value: "0",
|
||||
valueSource: ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
} /*,
|
||||
{
|
||||
name: "Индикатор2",
|
||||
path: "indicator",
|
||||
settings: {
|
||||
dataSource: {
|
||||
type: "USER_PROC",
|
||||
userProc: "ИндКолДогКонтрТип",
|
||||
stored: "UDO_P_P8P_AGNCONTR_IND",
|
||||
respArg: "COUT",
|
||||
arguments: [
|
||||
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
|
||||
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" },
|
||||
{
|
||||
name: "NIND_TYPE",
|
||||
caption: "Тип индикатора (0 - все, 1 - неутвержденные)",
|
||||
dataType: "NUMB",
|
||||
req: true,
|
||||
value: "1",
|
||||
valueSource: ""
|
||||
}
|
||||
]
|
||||
}
|
||||
id: "",
|
||||
dataSource: {},
|
||||
actions: [],
|
||||
conditions: []
|
||||
}
|
||||
}*/
|
||||
}
|
||||
];
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { COMPONETNS };
|
||||
export { COMPONENTS };
|
||||
|
||||
@ -7,7 +7,140 @@
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useEffect } from "react"; //Классы React
|
||||
import { useState, useEffect, useContext, useCallback, useLayoutEffect } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений
|
||||
import { object2Base64XML, genUID, xml2JSON } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { exportXMLFile } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
import { P8P_COMPONENT_SETTINGS_PATHS } from "../../../components/editors/p8p_component_settings"; //Дополнительные настройки источников
|
||||
import { getActionsVariables } from "../../../components/editors/p8p_component_action/util"; //Вспомогательный функционал действий компонентов
|
||||
import { P8P_CA_TYPE } from "../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Начальное состояние размера макета
|
||||
const INITIAL_BREAKPOINT = "lg";
|
||||
|
||||
//Начальное состояние макета
|
||||
const INITIAL_LAYOUTS = {
|
||||
[INITIAL_BREAKPOINT]: []
|
||||
};
|
||||
|
||||
//---------
|
||||
//Константы расчета высоты строки ResponsiveGridLayout
|
||||
//---------
|
||||
|
||||
//Структура параметров для расчета высоты строки ResponsiveGridLayout
|
||||
const GRID_LAYOUT_PARAMS = {
|
||||
//Высота главного меню (px)
|
||||
APP_BAR_HEIGHT: 64,
|
||||
//Дополнительный отступ (px)
|
||||
SAFE_OFFSET: 20,
|
||||
//Максимальное количество строк (число)
|
||||
MAX_ROWS: 68,
|
||||
//Высота отступа (px)
|
||||
MARGIN: 10
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Проверка на массив загружаемых данных панели
|
||||
const isArrayPanelDesc = (name, jPath) =>
|
||||
["items", "arguments", "conditions", "actions", "dependencies", "inputParams"].includes(name) || /(.*)XLAYOUTS\.[^.]*$/.test(jPath);
|
||||
|
||||
//Обработка значений тэгов загружаемых данных панели
|
||||
const tagValueProcessorPanelDesc = (name, val, jPath) =>
|
||||
["condValue", "resValue", "description"].includes(name)
|
||||
? undefined
|
||||
: /(.*)dataSource.arguments.value$/.test(jPath) || /(.*)XVALUE_PROVIDERS(.*)$/.test(jPath)
|
||||
? undefined
|
||||
: val;
|
||||
|
||||
//Конвертация серверного описания компонентов в данные для редактора панелей
|
||||
const convertServerData2Components = components => {
|
||||
//Корректировка информации о действия (Для типа "setVariable" значение ключа "params" - обязательно массив, в иных случаях - объект)
|
||||
const correctionActions = actions => {
|
||||
return actions.reduce(
|
||||
(prevActions, action) => [
|
||||
...prevActions,
|
||||
{
|
||||
...(action.type === P8P_CA_TYPE.setVariable.code && !Array.isArray(action.params)
|
||||
? { ...action, params: [{ ...action.params }] }
|
||||
: { ...action })
|
||||
}
|
||||
],
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
//Форматируем устанавливая пустой объект для dataSource, если он пуст
|
||||
return Object.keys(components).reduce(
|
||||
(prev, cur) => ({
|
||||
...prev,
|
||||
[cur]: {
|
||||
...components[cur],
|
||||
settings: {
|
||||
...components[cur].settings,
|
||||
//dataSource - обязательно объект
|
||||
...(components[cur].settings?.dataSource ? { dataSource: components[cur].settings.dataSource || {} } : {}),
|
||||
//actions - требуют корректировки
|
||||
...(components[cur].settings?.actions
|
||||
? {
|
||||
actions: correctionActions(components[cur].settings.actions)
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}
|
||||
}),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
//Конвертация серверного описания проводников значений в данные для редактора панелей
|
||||
const convertServerData2ValueProviders = valueProviders => {
|
||||
//Форматируем инициализируя dependencies, если требуется
|
||||
return Object.keys(valueProviders).reduce(
|
||||
(prev, cur) => ({ ...prev, [cur]: { ...valueProviders[cur], dependencies: valueProviders[cur].dependencies || [] } }),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
//Конвертация серверного описания панели в данные для редактора панелей
|
||||
const serverPanelData2PanelDesc = (components, valueProviders, layouts, breakpoint) => {
|
||||
//Возвращаем информацию о панеле с учетом конвертаций
|
||||
return {
|
||||
components: convertServerData2Components(components),
|
||||
valueProviders: convertServerData2ValueProviders(valueProviders),
|
||||
layouts: layouts[breakpoint] ? { [breakpoint]: [...layouts[breakpoint]] } : INITIAL_LAYOUTS,
|
||||
breakpoint: breakpoint
|
||||
};
|
||||
};
|
||||
|
||||
//Считывание общего списка зависимостей от проводника значений
|
||||
const getValueProvidersLinks = (componentPath, settings) => {
|
||||
//Если это индикатор/график/таблица
|
||||
if ([P8P_COMPONENT_SETTINGS_PATHS.INDICATOR, P8P_COMPONENT_SETTINGS_PATHS.CHART, P8P_COMPONENT_SETTINGS_PATHS.TABLE].includes(componentPath)) {
|
||||
//Собираем зависимости из настройки источника
|
||||
let argumentsSources = Array.isArray(settings?.dataSource?.arguments)
|
||||
? settings.dataSource.arguments.reduce((prev, cur) => (cur.valueSource ? [...prev, cur.valueSource] : [...prev]), [])
|
||||
: [];
|
||||
//Собираем зависимости из параметров действий
|
||||
let actionsSources = Array.isArray(settings?.actions) ? getActionsVariables(settings.actions) : [];
|
||||
//Возвращаем зависимости компонента
|
||||
return [...new Set([...argumentsSources, ...actionsSources])];
|
||||
}
|
||||
//Если это форма
|
||||
if (P8P_COMPONENT_SETTINGS_PATHS.FORM === componentPath) {
|
||||
//Собираем зависимости из элементов формы
|
||||
let items = Array.isArray(settings?.items) ? settings.items.map(item => item.name) : [];
|
||||
//Возвращаем зависимости компонента
|
||||
return [...new Set(items)];
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
@ -37,8 +170,470 @@ const useComponentModule = ({ path = null, module = "view" } = {}) => {
|
||||
return [componentModule, init];
|
||||
};
|
||||
|
||||
//Работа с панелью
|
||||
const usePanel = () => {
|
||||
//Собственное состояние - рабочая панель
|
||||
const [panel, setPanel] = useState();
|
||||
//Собственное состояние - имя рабочей панели
|
||||
const [panelName, setPanelName] = useState(null);
|
||||
//Собственное состояние - наличие изменений рабочей панели
|
||||
const [isPanelChanged, setIsPanelChanged] = useState(false);
|
||||
//Собственное состояние - возможность редактирования
|
||||
const [isEditAvaliable, setIsEditAvaliable] = useState(true);
|
||||
//Собственное состояние - режим редактирования
|
||||
const [editMode, setEditMode] = useState(true);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости загрузки информации о панели
|
||||
const loadPanel = async panel => {
|
||||
//Считываем информацию с сервера
|
||||
const res = await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_ATTRS_GET_BY_CODE",
|
||||
args: {
|
||||
SCODE: panel
|
||||
},
|
||||
respArg: "COUT",
|
||||
loader: true
|
||||
});
|
||||
//Устанавливаем рабочую панель
|
||||
setPanel(res.XPANEL_ATTRS.rn);
|
||||
//Устанавливаем наименование рабочей панели
|
||||
setPanelName(res.XPANEL_ATTRS.name);
|
||||
//Сбрасываем наличие изменений рабочей панели
|
||||
setIsPanelChanged(false);
|
||||
//Загружаемую панель запрещено редактировать
|
||||
setIsEditAvaliable(false);
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
//При выборе панели
|
||||
const selectPanel = (panel, panelName, isPanelEditAvaliable) => {
|
||||
//Обновляем информацию о панели
|
||||
setPanel(panel);
|
||||
setPanelName(panelName);
|
||||
setIsEditAvaliable(isPanelEditAvaliable);
|
||||
setEditMode(isPanelEditAvaliable);
|
||||
setIsPanelChanged(false);
|
||||
};
|
||||
|
||||
//При закрытии панели
|
||||
const closePanel = () => {
|
||||
setPanel(null);
|
||||
setPanelName(null);
|
||||
setIsPanelChanged(false);
|
||||
};
|
||||
|
||||
//При установке признака изменений панели
|
||||
const setPanelChanged = isChanged => setIsPanelChanged(isChanged);
|
||||
|
||||
//При установке признака режима редактирования
|
||||
const changeEditMode = isEditMode => setEditMode(isEditMode);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [panel, panelName, editMode, isEditAvaliable, isPanelChanged, loadPanel, selectPanel, closePanel, changeEditMode, setPanelChanged];
|
||||
};
|
||||
|
||||
//Работа с менеджером панелей
|
||||
const usePanelManager = () => {
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//Добавление панели
|
||||
const insertPanel = useCallback(
|
||||
async (code, name) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_INSERT", args: { SCODE: code, SNAME: name }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Изменение панели
|
||||
const updatePanel = useCallback(
|
||||
async (query, code, name) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_UPDATE", args: { NRN: query, SCODE: code, SNAME: name }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Удаление панели
|
||||
const deletePanel = useCallback(
|
||||
async query => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_DELETE", args: { NRN: query }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Установка флага готовности панели
|
||||
const setPanelReady = useCallback(
|
||||
async (query, ready) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_READY_SET", args: { NRN: query, NREADY: ready }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Установка флага публичности панели
|
||||
const setPanelPbl = useCallback(
|
||||
async (query, pbl) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_PBL_SET", args: { NRN: query, NPBL: pbl }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Импорт новой панели
|
||||
const importPanel = useCallback(
|
||||
async fileData => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_IMPORT",
|
||||
args: {
|
||||
CPANEL: {
|
||||
//Форматируем данные документа в base64
|
||||
VALUE: btoa(unescape(encodeURIComponent(fileData))),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
},
|
||||
loader: true
|
||||
});
|
||||
setRefresh(true);
|
||||
},
|
||||
[SERV_DATA_TYPE_CLOB, executeStored]
|
||||
);
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_LIST",
|
||||
respArg: "COUT",
|
||||
isArray: name => ["XPANEL"].includes(name),
|
||||
attributeValueProcessor: (name, val) => (["code", "name"].includes(name) ? undefined : val),
|
||||
loader: true
|
||||
});
|
||||
setData(data?.XPANELS?.XPANEL || []);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
}
|
||||
};
|
||||
//Если надо обновить
|
||||
if (refresh)
|
||||
//Получим данные
|
||||
loadData();
|
||||
}, [refresh, executeStored]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, insertPanel, updatePanel, deletePanel, setPanelReady, setPanelPbl, importPanel];
|
||||
};
|
||||
|
||||
//Работа с содержимым панели
|
||||
const usePanelDesc = panel => {
|
||||
//Собственное состояние - флаг инициализированности
|
||||
const [isInit, setInit] = useState(false);
|
||||
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState({
|
||||
components: {},
|
||||
valueProviders: {},
|
||||
layouts: INITIAL_LAYOUTS,
|
||||
breakpoint: INITIAL_BREAKPOINT
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgErr } = useContext(MessagingСtx);
|
||||
|
||||
//Считывание базовой информации о панели
|
||||
const getPanelInfo = useCallback(async () => {
|
||||
//Считываем информацию с сервера
|
||||
const res = await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_ATTRS_GET",
|
||||
args: {
|
||||
NRN: panel
|
||||
},
|
||||
respArg: "COUT",
|
||||
loader: true
|
||||
});
|
||||
//Забираем все без RN
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { rn, ...panelAttrs } = res.XPANEL_ATTRS;
|
||||
//Возвращаем результат
|
||||
return panelAttrs;
|
||||
}, [executeStored, panel]);
|
||||
|
||||
//Формирования данных панели для выгрузки
|
||||
const makeXMLPanelDesc = useCallback(
|
||||
async (includePanelInfo = false, isBase64 = true) => {
|
||||
//Считываем информацию о выгружаемой панели
|
||||
const panelInfo = includePanelInfo ? await getPanelInfo() : {};
|
||||
//Сформируем данные в формат XML
|
||||
const xmlData = object2Base64XML(
|
||||
{
|
||||
XPANEL: {
|
||||
...(includePanelInfo ? { XPANEL_INFO: panelInfo } : {}),
|
||||
XCOMPONENTS: data.components,
|
||||
XVALUE_PROVIDERS: data.valueProviders,
|
||||
XLAYOUTS: data.layouts,
|
||||
XOPTIONS: {
|
||||
breakpoint: data.breakpoint
|
||||
}
|
||||
}
|
||||
},
|
||||
{ suppressEmptyNode: false }
|
||||
);
|
||||
//Возвращаем данные в формате XML (формат исходит от признака)
|
||||
return isBase64 ? xmlData : decodeURIComponent(escape(atob(xmlData)));
|
||||
},
|
||||
[data.breakpoint, data.components, data.layouts, data.valueProviders, getPanelInfo]
|
||||
);
|
||||
|
||||
//Добавление компонента в макет
|
||||
const addComponent = component => {
|
||||
//Генерируем ID
|
||||
const id = genUID(component.path);
|
||||
//Добавляем компонент и его макет
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
components: { ...pv.components, [id]: { ...component, settings: { ...component.settings, id: id } } },
|
||||
layouts: { ...pv.layouts, [data.breakpoint]: [...pv.layouts[data.breakpoint], { i: id, x: 0, y: 0, w: 4, h: 10 }] }
|
||||
}));
|
||||
};
|
||||
|
||||
//Удаление компонента из макета
|
||||
const deleteComponent = id => {
|
||||
//Удаляем все старые зависимости от компонента
|
||||
const newValueProviders = Object.keys(data.valueProviders).reduce(
|
||||
(prev, cur) => ({
|
||||
...prev,
|
||||
[cur]: { ...data.valueProviders[cur], dependencies: data.valueProviders[cur].dependencies.filter(el => el !== id) }
|
||||
}),
|
||||
{}
|
||||
);
|
||||
//Обновляем данные
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
layouts: { ...pv.layouts, [data.breakpoint]: data.layouts[data.breakpoint].filter(item => item.i !== id) },
|
||||
components: { ...pv.components, [id]: { ...pv.components[id], deleted: true } },
|
||||
valueProviders: { ...newValueProviders }
|
||||
}));
|
||||
};
|
||||
|
||||
//Изменение размера холста
|
||||
const breakpointChange = breakpoint => setData(pv => ({ ...pv, breakpoint: breakpoint }));
|
||||
|
||||
//Изменение состояния макета
|
||||
const layoutsChange = layouts => setData(pv => ({ ...pv, layouts: layouts }));
|
||||
|
||||
//Изменение значений в компоненте
|
||||
const changeValueProviders = values => {
|
||||
//Считываем проводники
|
||||
const newValueProviders = { ...data.valueProviders };
|
||||
//Переносим новые значения в проводники
|
||||
Object.keys(values).map(el => (newValueProviders[el].value = values[el]));
|
||||
//Обновляем проводники
|
||||
setData(pv => ({ ...pv, valueProviders: { ...newValueProviders } }));
|
||||
};
|
||||
|
||||
//Изменение настроек компонента
|
||||
const changeComponentSettings = (id = null, settings = {}, onChanged = null) => {
|
||||
//Считываем новые зависимости компонента
|
||||
const providedValues = getValueProvidersLinks(data.components[id].path, settings);
|
||||
//Удаляем все старые зависимости от компонента
|
||||
const newValueProviders = Object.keys(data.valueProviders).reduce(
|
||||
(prev, cur) => ({
|
||||
...prev,
|
||||
[cur]: { ...data.valueProviders[cur], dependencies: data.valueProviders[cur].dependencies.filter(el => el !== id) }
|
||||
}),
|
||||
{}
|
||||
);
|
||||
//Добавляем новые зависимости
|
||||
providedValues.map(providerName => newValueProviders[providerName].dependencies.push(id));
|
||||
//Обновляем данные
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
components: { ...pv.components, [id]: { ...pv.components[id], settings: { ...settings } } },
|
||||
valueProviders: { ...newValueProviders }
|
||||
}));
|
||||
//Выполняем действия после изменения
|
||||
onChanged && onChanged();
|
||||
};
|
||||
|
||||
//Изменение настроек панели
|
||||
const changePanelSettings = valueProviders => {
|
||||
//Обновляем данные
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
valueProviders: { ...valueProviders }
|
||||
}));
|
||||
};
|
||||
|
||||
//Сохранение описания панели
|
||||
const savePanelDesc = useCallback(
|
||||
async (callBack = null) => {
|
||||
try {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_DESC_SET",
|
||||
args: {
|
||||
NRN: panel,
|
||||
CPANEL: {
|
||||
VALUE: await makeXMLPanelDesc(),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
},
|
||||
loader: true
|
||||
});
|
||||
callBack && callBack(false);
|
||||
} catch (e) {
|
||||
callBack && callBack(true);
|
||||
}
|
||||
},
|
||||
[SERV_DATA_TYPE_CLOB, executeStored, makeXMLPanelDesc, panel]
|
||||
);
|
||||
|
||||
//Загрузка описания панели из файла
|
||||
const importPanelDesc = async (fileData, callBack = null) => {
|
||||
//Формируем данные панели
|
||||
const panelData = await xml2JSON({
|
||||
xmlDoc: fileData,
|
||||
isArray: isArrayPanelDesc,
|
||||
tagValueProcessor: tagValueProcessorPanelDesc
|
||||
});
|
||||
//Если файл содержит тэг XPANEL
|
||||
if (panelData.XPANEL) {
|
||||
setData(
|
||||
serverPanelData2PanelDesc(
|
||||
panelData.XPANEL?.XCOMPONENTS || {},
|
||||
panelData.XPANEL?.XVALUE_PROVIDERS || {},
|
||||
panelData.XPANEL?.XLAYOUTS || {},
|
||||
panelData.XPANEL?.XOPTIONS?.breakpoint || INITIAL_BREAKPOINT
|
||||
)
|
||||
);
|
||||
callBack && callBack(true);
|
||||
} else {
|
||||
showMsgErr("Загружаемые данные не соответствуют формату настройки панели.");
|
||||
callBack && callBack(false);
|
||||
}
|
||||
};
|
||||
|
||||
//Выгрузка панели в файл
|
||||
const exportPanelDesc = async panelName => {
|
||||
//Формируем XML-представление панели
|
||||
const xmlPanelDesc = await makeXMLPanelDesc(true, false);
|
||||
//Выгружаем в файл
|
||||
exportXMLFile(xmlPanelDesc, panelName);
|
||||
};
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_DESC_GET",
|
||||
args: { NRN: panel },
|
||||
respArg: "COUT",
|
||||
isArray: isArrayPanelDesc,
|
||||
tagValueProcessor: tagValueProcessorPanelDesc,
|
||||
loader: true
|
||||
});
|
||||
setData(
|
||||
serverPanelData2PanelDesc(
|
||||
data?.XCOMPONENTS || {},
|
||||
data?.XVALUE_PROVIDERS || {},
|
||||
data?.XLAYOUTS || {},
|
||||
data?.XOPTIONS?.breakpoint || INITIAL_BREAKPOINT
|
||||
)
|
||||
);
|
||||
setInit(true);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
}
|
||||
};
|
||||
//Если надо обновить
|
||||
if (refresh)
|
||||
if (panel)
|
||||
//Если есть для чего получать данные
|
||||
loadData();
|
||||
//Нет идентификатора запроса - нет данных
|
||||
else
|
||||
setData({
|
||||
components: {},
|
||||
valueProviders: {},
|
||||
layouts: INITIAL_LAYOUTS,
|
||||
breakpoint: INITIAL_BREAKPOINT
|
||||
});
|
||||
}, [refresh, panel, executeStored]);
|
||||
|
||||
//При изменении входных свойств - поднимаем флаг обновления
|
||||
useEffect(() => {
|
||||
setInit(false);
|
||||
setRefresh(true);
|
||||
}, [panel]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [
|
||||
data,
|
||||
isInit,
|
||||
addComponent,
|
||||
deleteComponent,
|
||||
breakpointChange,
|
||||
layoutsChange,
|
||||
changeValueProviders,
|
||||
changeComponentSettings,
|
||||
changePanelSettings,
|
||||
savePanelDesc,
|
||||
importPanelDesc,
|
||||
exportPanelDesc
|
||||
];
|
||||
};
|
||||
|
||||
//Работа с соотношением размеров
|
||||
const useWindowResize = () => {
|
||||
//Состояние высоты строки ResponsiveGridLayout
|
||||
const [rowHeight, setRowHeight] = useState(1);
|
||||
//При изменении размера
|
||||
useLayoutEffect(() => {
|
||||
//Расчет высоты строки
|
||||
const updateRowHeight = () => {
|
||||
//Определяем доступную область
|
||||
const availableHeight = window.innerHeight - GRID_LAYOUT_PARAMS.APP_BAR_HEIGHT - GRID_LAYOUT_PARAMS.SAFE_OFFSET;
|
||||
//Промежутки между рядами
|
||||
const totalMargins = (GRID_LAYOUT_PARAMS.MAX_ROWS - 1) * GRID_LAYOUT_PARAMS.MARGIN;
|
||||
//Рассчитываем высоту строки
|
||||
const result = (availableHeight - totalMargins) / GRID_LAYOUT_PARAMS.MAX_ROWS;
|
||||
//Устанавливаем
|
||||
setRowHeight(result);
|
||||
};
|
||||
//Запускаем при открытии панели
|
||||
updateRowHeight();
|
||||
//Устанавливаем обработчик на событие
|
||||
window.addEventListener("resize", updateRowHeight);
|
||||
//Удаляем обработчик на событие
|
||||
return () => window.removeEventListener("resize", updateRowHeight);
|
||||
}, []);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [rowHeight];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useComponentModule };
|
||||
export { useComponentModule, usePanel, usePanelManager, usePanelDesc, useWindowResize };
|
||||
|
||||
@ -47,3 +47,6 @@ export const ORIENTATION = {
|
||||
H: "H",
|
||||
V: "v"
|
||||
};
|
||||
|
||||
//Доступность сохранения настроек элемента
|
||||
export const isItemOkDisabled = item => (!item.name ? true : false);
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
Button,
|
||||
Icon,
|
||||
Select,
|
||||
Menu,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
@ -31,7 +32,8 @@ import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Диалог настройки
|
||||
import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов
|
||||
import { ITEM_SHAPE, ITEM_INITIAL, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
||||
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { ITEM_SHAPE, ITEM_INITIAL, ITEMS_INITIAL, ORIENTATION, isItemOkDisabled } from "./common"; //Общие ресурсы и константы формы
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -47,10 +49,13 @@ const STYLES = {
|
||||
//------------------------------------
|
||||
|
||||
//Редактор элемента
|
||||
const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => {
|
||||
const ItemEditor = ({ item = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => {
|
||||
//Собственное состояние - параметры элемента формы
|
||||
const [state, setState] = useState({ ...ITEM_INITIAL, ...item });
|
||||
|
||||
//Собственное состояние - элемент привязки меню выбора источника
|
||||
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState(null);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
@ -123,11 +128,74 @@ const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => {
|
||||
});
|
||||
};
|
||||
|
||||
//Открытие/сокрытие меню выбора источника
|
||||
const toggleValueProvidersMenu = target => setValueProvidersMenuAnchorEl(target instanceof Element ? target : null);
|
||||
|
||||
//При очистке значения/связывания параметра
|
||||
const handleParamClearClick = () => setState(pv => ({ ...pv, name: "" }));
|
||||
|
||||
//При отображении меню связывания параметра с поставщиком данных
|
||||
const handleParamLinkMenuClick = e => setValueProvidersMenuAnchorEl(e.currentTarget);
|
||||
|
||||
//При выборе элемента меню связывания аргумента с поставщиком данных
|
||||
const handleParamLinkClick = valueSource => {
|
||||
//Устанавливаем выбранный параметр
|
||||
setState(pv => ({ ...pv, name: valueSource }));
|
||||
//Закрываем меню
|
||||
toggleValueProvidersMenu();
|
||||
};
|
||||
|
||||
//Список значений
|
||||
const values = Object.keys(valueProviders);
|
||||
|
||||
//Наличие значений
|
||||
const isValues = values && values.length > 0 ? true : false;
|
||||
|
||||
//Меню привязки к поставщикам значений
|
||||
const valueProvidersMenu = isValues && (
|
||||
<Menu anchorEl={valueProvidersMenuAnchorEl} open={Boolean(valueProvidersMenuAnchorEl)} onClose={toggleValueProvidersMenu}>
|
||||
{values.map((value, i) => (
|
||||
<MenuItem key={i} onClick={() => handleParamLinkClick(value)}>
|
||||
{value}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PConfigDialog title={`${item ? "Изменение" : "Добавление"} элемента`} onOk={handleOk} onCancel={handleCancel}>
|
||||
<P8PConfigDialog
|
||||
title={`${item ? TITLES.UPDATE : TITLES.INSERT} элемента`}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okDisabled={isItemOkDisabled(state)}
|
||||
>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
<TextField type={"text"} variant={"standard"} value={state.name} label={"Имя"} id={"name"} onChange={handleChange} />
|
||||
{valueProvidersMenu}
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.name}
|
||||
label={"Имя"}
|
||||
id={"name"}
|
||||
required={true}
|
||||
onChange={handleChange}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleParamClearClick}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
{isValues && (
|
||||
<IconButton onClick={handleParamLinkMenuClick}>
|
||||
<Icon>settings_ethernet</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<TextField type={"text"} variant={"standard"} value={state.caption} label={"Приглашение"} id={"caption"} onChange={handleChange} />
|
||||
<TextField
|
||||
type={"text"}
|
||||
@ -182,6 +250,7 @@ const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => {
|
||||
//Контроль свойств - редактор элемента
|
||||
ItemEditor.propTypes = {
|
||||
item: ITEM_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
@ -191,13 +260,18 @@ ItemEditor.propTypes = {
|
||||
//-----------
|
||||
|
||||
//Форма (редактор настроек)
|
||||
const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = false, items = ITEMS_INITIAL, onSettingsChange = null } = {}) => {
|
||||
const FormEditor = ({
|
||||
id,
|
||||
title = "",
|
||||
orientation = ORIENTATION.V,
|
||||
autoApply = false,
|
||||
items = ITEMS_INITIAL,
|
||||
valueProviders = {},
|
||||
onSettingsChange = null
|
||||
} = {}) => {
|
||||
//Собственное состояние - текущие настройки
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
//Собственное состояние - предоставляемые в панель значения
|
||||
const [providedValues, setProvidedValues] = useState([]);
|
||||
|
||||
//Собственное состояние - редактор элементов формы
|
||||
const [itemEditor, setItemEditor] = useState({ display: false, index: null });
|
||||
|
||||
@ -229,18 +303,13 @@ const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = f
|
||||
const handleItemCancel = () => setItemEditor({ display: false, index: null });
|
||||
|
||||
//При сохранении настроек
|
||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, providedValues, closeEditor });
|
||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, title, orientation, autoApply, items });
|
||||
}, [settings, id, title, orientation, autoApply, items]);
|
||||
|
||||
//При изменении состава элементов формы
|
||||
useEffect(() => {
|
||||
Array.isArray(settings?.items) && setProvidedValues(settings.items.map(item => item.name));
|
||||
}, [settings?.items]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
settings && (
|
||||
@ -248,6 +317,7 @@ const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = f
|
||||
{itemEditor.display && (
|
||||
<ItemEditor
|
||||
item={itemEditor.index !== null ? { ...settings.items[itemEditor.index] } : null}
|
||||
valueProviders={valueProviders}
|
||||
onCancel={handleItemCancel}
|
||||
onOk={handleItemSave}
|
||||
/>
|
||||
@ -299,6 +369,7 @@ FormEditor.propTypes = {
|
||||
orientation: PropTypes.oneOf(Object.values(ORIENTATION)),
|
||||
autoApply: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(ITEM_SHAPE),
|
||||
valueProviders: PropTypes.object,
|
||||
onSettingsChange: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
@ -13,11 +13,17 @@ import { Paper, Stack, Typography, Icon, TextField, IconButton, InputAdornment }
|
||||
import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения
|
||||
import { P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||
import { ITEM_SHAPE, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
||||
import { APP_STYLES } from "../../../../../app.styles";
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { overflow: "auto", ...APP_STYLES.SCROLL }
|
||||
};
|
||||
|
||||
//Иконка компонента
|
||||
const COMPONENT_ICON = "fact_check";
|
||||
|
||||
@ -116,7 +122,7 @@ const Form = ({ title = null, orientation = ORIENTATION.V, autoApply = false, it
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Paper className={`component-view__container ${!haveConfing && "component-view__container__empty"}`} elevation={6}>
|
||||
<Paper className={`component-view__container ${!haveConfing && "component-view__container__empty"}`} sx={STYLES.CONTAINER} elevation={6}>
|
||||
{haveConfing ? (
|
||||
<Stack direction={"column"}>
|
||||
<Stack direction={"row"} justifyContent={"space-between"} alignItems={"center"}>
|
||||
|
||||
39
app/panels/panels_editor/components/indicator/action.js
Normal file
39
app/panels/panels_editor/components/indicator/action.js
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Индикатор (общие ресурсы действия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Доступные области действий индикатора
|
||||
const P8P_CA_INDICATOR_ACTION_AREAS = [
|
||||
{ name: "Компонент", area: "component", hasElement: false },
|
||||
{ name: "Заголовок", area: "inner_caption", hasElement: false },
|
||||
{ name: "Значение", area: "inner_value", hasElement: false }
|
||||
];
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Считывание обработчиков индикатора
|
||||
const getIndicatorHandlers = handlers => {
|
||||
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||
return {
|
||||
onComponentClick: handlers["component."]?.fn,
|
||||
onCaptionClick: handlers["inner_caption."]?.fn,
|
||||
onValueClick: handlers["inner_value."]?.fn
|
||||
};
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CA_INDICATOR_ACTION_AREAS, getIndicatorHandlers };
|
||||
33
app/panels/panels_editor/components/indicator/conditions.js
Normal file
33
app/panels/panels_editor/components/indicator/conditions.js
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Индикатор (общие ресурсы условия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Доступные поля условий индикатора
|
||||
const P8P_CC_INDICATOR_COND_FIELDS = [
|
||||
{ name: "Состояние", value: "state", hasElement: false },
|
||||
{ name: "Значение", value: "value", hasElement: false },
|
||||
{ name: "Подсказка", value: "hint", hasElement: false }
|
||||
];
|
||||
|
||||
//Доступные поля результата условия индикатора
|
||||
const P8P_CC_INDICATOR_RES_FIELDS = [
|
||||
{ name: "Подпись", value: "caption", hasElement: false, icon: "rtt" },
|
||||
{ name: "Значение", value: "value", hasElement: false, icon: "pin" },
|
||||
{ name: "Цвет заливки", value: "backgroundColor", hasElement: false, icon: "color_lens" },
|
||||
{ name: "Цвет шрифта", value: "color", hasElement: false, icon: "format_color_text" }
|
||||
];
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CC_INDICATOR_COND_FIELDS, P8P_CC_INDICATOR_RES_FIELDS };
|
||||
@ -13,32 +13,69 @@ import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||
import { P8PConditions } from "../../../../components/editors/p8p_conditions"; //Условия
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { P8P_CCS_INITIAL, P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||
import { P8P_CA_INDICATOR_ACTION_AREAS } from "./action"; //Общие ресурсы действий индикатора
|
||||
import { P8P_CC_INDICATOR_COND_FIELDS, P8P_CC_INDICATOR_RES_FIELDS } from "./conditions"; //Общие ресурсы условий индикатора
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Индикатор (редактор настроек)
|
||||
const IndicatorEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
|
||||
const IndicatorEditor = ({
|
||||
id,
|
||||
dataSource = null,
|
||||
valueProviders = {},
|
||||
conditions = P8P_CCS_INITIAL,
|
||||
actions = P8P_CAS_INITIAL,
|
||||
onSettingsChange = null
|
||||
} = {}) => {
|
||||
//Собственное состояние - текущие настройки
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource });
|
||||
}, [settings, id, dataSource]);
|
||||
|
||||
//При сохранении изменений элемента
|
||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||
|
||||
//При изменении действий
|
||||
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||
|
||||
//При изменении условий
|
||||
const handleConditionsChange = conditions => setSettings(pv => ({ ...pv, conditions }));
|
||||
|
||||
//При сохранении настроек
|
||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource, conditions, actions });
|
||||
}, [settings, id, dataSource, conditions, actions]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PEditorBox title={"Параметры индикатора"} onSave={handleSave}>
|
||||
<P8PEditorSubHeader title={"Источник данных"} />
|
||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||
<P8PEditorSubHeader title={"Условия"} />
|
||||
<P8PConditions
|
||||
conditions={settings?.conditions}
|
||||
condFields={P8P_CC_INDICATOR_COND_FIELDS}
|
||||
resFields={P8P_CC_INDICATOR_RES_FIELDS}
|
||||
onChange={handleConditionsChange}
|
||||
/>
|
||||
<P8PEditorSubHeader title={"Действия"} />
|
||||
<P8PActions
|
||||
actions={settings?.actions}
|
||||
valueProviders={valueProviders}
|
||||
areas={P8P_CA_INDICATOR_ACTION_AREAS}
|
||||
onChange={handleActionsChange}
|
||||
/>
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
@ -48,6 +85,8 @@ IndicatorEditor.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onSettingsChange: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
@ -11,13 +11,17 @@ import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PIndicator } from "../../../../components/p8p_indicator"; //Компонент индикатора
|
||||
import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { useDataSource, useConditions, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import {
|
||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||
P8P_COMPONENT_INLINE_MESSAGE,
|
||||
P8PComponentInlineMessage
|
||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||
import { P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы условий
|
||||
import { P8P_CA_SHAPE } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||
import { getIndicatorHandlers } from "./action"; //Общие ресурсы действий индикатора
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -31,7 +35,14 @@ const COMPONENT_NAME = "Индикатор";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { height: "100%" }
|
||||
CONTAINER: clickable => ({
|
||||
height: "100%",
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//-----------
|
||||
@ -39,29 +50,39 @@ const STYLES = {
|
||||
//-----------
|
||||
|
||||
//Индикатор (представление)
|
||||
const Indicator = ({ dataSource = null, values = {} } = {}) => {
|
||||
const Indicator = ({ dataSource = null, values = {}, conditions = [], actions = [], onValuesChange = null }) => {
|
||||
//Собственное состояние - данные
|
||||
const [data, error] = useDataSource({ dataSource, values });
|
||||
const [indicator, error, haveConfing, haveData] = useDataSource({
|
||||
dataSource,
|
||||
values,
|
||||
componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.INDICATOR
|
||||
});
|
||||
|
||||
//Флаг настроенности индикатора
|
||||
const haveConfing = dataSource?.stored ? true : false;
|
||||
//Собственное состояние - данные с учетом условий
|
||||
const [indicatorOrigin] = useConditions({ componentData: indicator, conditions });
|
||||
|
||||
//Флаг наличия данных
|
||||
const haveData = data?.init === true && !error ? true : false;
|
||||
//Собственное состояние - обработчики компонента
|
||||
const [handlers] = useComponentHandlers({ actions, onValuesChange });
|
||||
|
||||
//Данные индикатора
|
||||
const indicator = data?.XINDICATOR || {};
|
||||
//Обработчики областей
|
||||
const { onComponentClick, onCaptionClick, onValueClick } = getIndicatorHandlers(handlers);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Paper
|
||||
{...(haveConfing && haveData
|
||||
? { sx: { ...STYLES.CONTAINER } }
|
||||
? { sx: { ...STYLES.CONTAINER(onComponentClick) } }
|
||||
: { className: "component-view__container component-view__container__empty" })}
|
||||
elevation={6}
|
||||
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||
>
|
||||
{haveConfing && haveData ? (
|
||||
<P8PIndicator {...indicator} elevation={0} />
|
||||
<P8PIndicator
|
||||
{...indicatorOrigin}
|
||||
onValueClick={onValueClick ? event => onValueClick({ event, values }) : null}
|
||||
onCaptionClick={onCaptionClick ? event => onCaptionClick({ event, values }) : null}
|
||||
elevation={0}
|
||||
/>
|
||||
) : (
|
||||
<P8PComponentInlineMessage
|
||||
icon={COMPONENT_ICON}
|
||||
@ -77,7 +98,10 @@ const Indicator = ({ dataSource = null, values = {} } = {}) => {
|
||||
//Контроль свойств компонента - Индикатор (представление)
|
||||
Indicator.propTypes = {
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
values: PropTypes.object
|
||||
values: PropTypes.object,
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onValuesChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компонент: Диалог добавления/исправления панели
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог добавления/исправления панели
|
||||
const PanelIUDialog = ({ code = "", name = "", insert = true, onOk, onCancel }) => {
|
||||
//Нажатие на кнопку "Ok"
|
||||
const handleOk = values => onOk && onOk({ ...values });
|
||||
|
||||
//Нажатие на кнопку "Отмена"
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PDialog
|
||||
title={`${insert === true ? TITLES.INSERT : TITLES.UPDATE} панели`}
|
||||
inputs={[
|
||||
{ name: "code", value: code, label: "Мнемокод" },
|
||||
{ name: "name", value: name, label: "Наименование" }
|
||||
]}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог добавления/исправления панели
|
||||
PanelIUDialog.propTypes = {
|
||||
code: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
insert: PropTypes.bool,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelIUDialog };
|
||||
@ -0,0 +1,158 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компонент: Список панелей
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
LIST_PANELS: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL },
|
||||
SMALL_TOOL_ICON: {
|
||||
fontSize: 20
|
||||
}
|
||||
};
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Структура данных о сущности панели
|
||||
const PANELS_LIST_ITEM_SHAPE = PropTypes.shape({
|
||||
rn: PropTypes.number.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
author: PropTypes.string.isRequired,
|
||||
chDate: PropTypes.string.isRequired,
|
||||
modify: PropTypes.oneOf([0, 1]).isRequired,
|
||||
pbl: PropTypes.oneOf([0, 1]).isRequired,
|
||||
ready: PropTypes.oneOf([0, 1]).isRequired
|
||||
});
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог открытия панели
|
||||
const PanelsList = ({
|
||||
panels = [],
|
||||
isEditable = true,
|
||||
current = null,
|
||||
onSelect = null,
|
||||
onPbl = null,
|
||||
onReady = null,
|
||||
onEdit = null,
|
||||
onDelete = null
|
||||
} = {}) => {
|
||||
//При выборе элемента списка
|
||||
const handleSelectClick = query => {
|
||||
onSelect && onSelect(query);
|
||||
};
|
||||
|
||||
//При нажатии на общедоступность
|
||||
const handlePblClick = (e, query) => {
|
||||
e.stopPropagation();
|
||||
onPbl && onPbl(query);
|
||||
};
|
||||
|
||||
//При нажатии на готовность
|
||||
const handleReadyClick = (e, query) => {
|
||||
e.stopPropagation();
|
||||
onReady && onReady(query);
|
||||
};
|
||||
|
||||
//При нажатии на исправлении
|
||||
const handleEditClick = (e, query) => {
|
||||
e.stopPropagation();
|
||||
onEdit && onEdit(query);
|
||||
};
|
||||
|
||||
//При нажатии на удаление
|
||||
const handleDeleteClick = (e, query) => {
|
||||
e.stopPropagation();
|
||||
onDelete && onDelete(query);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<List sx={STYLES.LIST_PANELS}>
|
||||
{panels.map((panel, i) => {
|
||||
const selected = panel.rn === current || panel.code === current;
|
||||
const disabled = !panel.modify;
|
||||
const pblTitle = `${panel.pbl === 1 ? "Общедоступный" : "Приватный"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
||||
const pblIcon = panel.pbl === 1 ? "groups" : "lock_person";
|
||||
const readyTitle = `${panel.ready === 1 ? "Готов" : "Не готов"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
||||
const readyIcon = panel.ready === 1 ? "touch_app" : "do_not_touch";
|
||||
return (
|
||||
<ListItem key={i}>
|
||||
<ListItemButton onClick={() => handleSelectClick(panel)} selected={selected}>
|
||||
<ListItemText
|
||||
primary={panel.name}
|
||||
secondaryTypographyProps={{ component: "div" }}
|
||||
secondary={
|
||||
<Stack direction={"column"}>
|
||||
<Typography variant={"caption"}>{`${panel.code}, ${panel.author}, ${panel.chDate}`}</Typography>
|
||||
{isEditable ? (
|
||||
<Stack direction={"row"}>
|
||||
<div title={pblTitle}>
|
||||
<IconButton disabled={disabled} onClick={e => handlePblClick(e, panel)}>
|
||||
<Icon sx={STYLES.SMALL_TOOL_ICON}>{pblIcon}</Icon>
|
||||
</IconButton>
|
||||
</div>
|
||||
<div title={readyTitle}>
|
||||
<IconButton disabled={disabled} onClick={e => handleReadyClick(e, panel)}>
|
||||
<Icon sx={STYLES.SMALL_TOOL_ICON}>{readyIcon}</Icon>
|
||||
</IconButton>
|
||||
</div>
|
||||
</Stack>
|
||||
) : null}
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{isEditable ? (
|
||||
<Stack direction={"row"}>
|
||||
<IconButton onClick={e => handleEditClick(e, panel)} disabled={disabled} title={BUTTONS.UPDATE}>
|
||||
<Icon>edit</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={e => handleDeleteClick(e, panel)} disabled={disabled} title={BUTTONS.DELETE}>
|
||||
<Icon>delete</Icon>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
) : null}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Список панелей
|
||||
PanelsList.propTypes = {
|
||||
panels: PropTypes.arrayOf(PANELS_LIST_ITEM_SHAPE),
|
||||
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
isEditable: PropTypes.bool,
|
||||
onSelect: PropTypes.func,
|
||||
onPbl: PropTypes.func,
|
||||
onReady: PropTypes.func,
|
||||
onEdit: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelsList };
|
||||
@ -0,0 +1,118 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компонент: Менеджер панелей
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Типовой диалог настройки
|
||||
import { usePanelManager } from "../components_hooks"; //Пользовательские хуки
|
||||
import { PanelsList } from "./panels_list"; //Список панелей
|
||||
import { PanelIUDialog } from "./panel_iu_dialog"; //Диалог добавления/исправления панели
|
||||
import { ImportPanelButton } from "../../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Менеджер панелей
|
||||
const PanelsManager = ({ current = null, isEditable = true, onPanelSelect = null, onCancel = null } = {}) => {
|
||||
//Собственное состояние - изменяемая панель
|
||||
const [modPanel, setModPanel] = useState(null);
|
||||
|
||||
//Работа со списком панелей
|
||||
const [panels, insertPanel, updatePanel, deletePanel, setPanelReady, setPanelPbl, importPanel] = usePanelManager();
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//При добавлении панели
|
||||
const handlePanelAdd = () => setModPanel(true);
|
||||
|
||||
//При загрузке панели из файла
|
||||
const handlePanelImport = fileData => {
|
||||
importPanel(fileData);
|
||||
};
|
||||
|
||||
//При выборе панели
|
||||
const handlePanelSelect = panel => onPanelSelect && onPanelSelect(panel);
|
||||
|
||||
//При установке признака публичности
|
||||
const handlePanelPblSet = panel => setPanelPbl(panel.rn, panel.pbl === 1 ? 0 : 1);
|
||||
|
||||
//При установке признака готовности
|
||||
const handlePanelReadySet = panel => setPanelReady(panel.rn, panel.ready === 1 ? 0 : 1);
|
||||
|
||||
//При исправлении панели
|
||||
const handlePanelEdit = panel => setModPanel({ ...panel });
|
||||
|
||||
//При удалении панели
|
||||
const handlePanelDelete = panel => showMsgWarn("Удалить панель?", () => deletePanel(panel.rn));
|
||||
|
||||
//При закрытии диалога добавления/исправления по "ОК"
|
||||
const handleIUDialogOk = async values => {
|
||||
if (modPanel === true) await insertPanel(values.code, values.name);
|
||||
else await updatePanel(modPanel.rn, values.code, values.name);
|
||||
setModPanel(null);
|
||||
};
|
||||
|
||||
//При закрытии диалога добавления/исправления по "Отмена"
|
||||
const handleIUDialogCancel = () => setModPanel(null);
|
||||
|
||||
//При закрытии менеджера отменой
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PConfigDialog title={"Панели"} onCancel={handleCancel}>
|
||||
{modPanel && (
|
||||
<PanelIUDialog
|
||||
code={modPanel?.code}
|
||||
name={modPanel?.name}
|
||||
insert={modPanel === true}
|
||||
onOk={handleIUDialogOk}
|
||||
onCancel={handleIUDialogCancel}
|
||||
/>
|
||||
)}
|
||||
{isEditable ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handlePanelAdd}>
|
||||
{BUTTONS.INSERT}
|
||||
</Button>
|
||||
<ImportPanelButton id={"input-file-loader-manager"} startIcon={"file_upload"} title={"Загрузить"} onClick={handlePanelImport} />
|
||||
</Box>
|
||||
) : null}
|
||||
<PanelsList
|
||||
panels={panels || []}
|
||||
current={current}
|
||||
isEditable={isEditable}
|
||||
onSelect={handlePanelSelect}
|
||||
onPbl={handlePanelPblSet}
|
||||
onReady={handlePanelReadySet}
|
||||
onEdit={handlePanelEdit}
|
||||
onDelete={handlePanelDelete}
|
||||
/>
|
||||
</P8PConfigDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Менеджер панелей
|
||||
PanelsManager.propTypes = {
|
||||
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
isEditable: PropTypes.bool,
|
||||
onPanelSelect: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelsManager };
|
||||
58
app/panels/panels_editor/components/table/action.js
Normal file
58
app/panels/panels_editor/components/table/action.js
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Таблица (общие ресурсы действия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Собственные значения типа действия
|
||||
const P8P_CA_TABLE_TYPE_VALUE = {
|
||||
COLUMN_VALUE: "Значение графы"
|
||||
};
|
||||
|
||||
//Типы значений действий таблицы
|
||||
const P8P_CA_TABLE_VALUE_TYPES = [...Object.values(P8P_CA_TABLE_TYPE_VALUE)];
|
||||
|
||||
//Доступные области действий таблицы
|
||||
const P8P_CA_TABLE_ACTION_AREAS = [
|
||||
{ name: "Компонент", area: "component", hasElement: false },
|
||||
{ name: "Графа таблицы", area: "column", hasElement: true },
|
||||
{ name: "Строка таблицы", area: "row", hasElement: false }
|
||||
];
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Получение значения собственного типа действия
|
||||
const getDataGridCustomTypeValue = ({ type, value, prms }) => {
|
||||
//Если это значение графы - возвращаем нужное значение, иначе - null
|
||||
return type === P8P_CA_TABLE_TYPE_VALUE.COLUMN_VALUE ? prms.row[value] : null;
|
||||
};
|
||||
|
||||
//Считывание обработчиков таблицы
|
||||
const getDataGridHandlers = (handlers, element = null) => {
|
||||
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||
return {
|
||||
onComponentClick: handlers["component."]?.fn,
|
||||
onRowClick: handlers["row."]?.fn,
|
||||
onColumnClick: element ? handlers[`column.${element}`]?.fn : null
|
||||
};
|
||||
};
|
||||
|
||||
//Проверка наличия обработчиков ячеек
|
||||
const isHasCellRender = (handlers, conditions) => {
|
||||
return Object.keys(handlers).some(handler => handler.startsWith("column.") || handler === "row.") || conditions.length !== 0;
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CA_TABLE_VALUE_TYPES, P8P_CA_TABLE_ACTION_AREAS, getDataGridCustomTypeValue, getDataGridHandlers, isHasCellRender };
|
||||
38
app/panels/panels_editor/components/table/conditions.js
Normal file
38
app/panels/panels_editor/components/table/conditions.js
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Индикатор (общие ресурсы условия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
import { getConditionsValues } from "../../../../components/editors/p8p_component_condition/util"; //Вспомогательные ресурсы условий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Доступные поля условий таблицы
|
||||
const P8P_CC_TABLE_COND_FIELDS = [{ name: "Графа", value: "column", hasElement: true }];
|
||||
|
||||
//Доступные поля результата условия таблицы
|
||||
const P8P_CC_TABLE_RES_FIELDS = [
|
||||
{ name: "Цвет заливки", value: "backgroundColor", hasElement: true, icon: "color_lens" },
|
||||
{ name: "Цвет шрифта", value: "color", hasElement: true, icon: "format_color_text" }
|
||||
];
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Считывание стилей колонки по условиям
|
||||
const getColumnStylesByConditions = (conditions, row, columnName) => {
|
||||
//Считываем стили строки
|
||||
return getConditionsValues(row, conditions, columnName);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CC_TABLE_COND_FIELDS, P8P_CC_TABLE_RES_FIELDS, getColumnStylesByConditions };
|
||||
@ -13,32 +13,66 @@ import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||
import { P8PConditions } from "../../../../components/editors/p8p_conditions"; //Условия
|
||||
import { P8P_CA_TABLE_VALUE_TYPES, P8P_CA_TABLE_ACTION_AREAS } from "./action"; //Общие ресурсы действий таблицы
|
||||
import { P8P_CC_TABLE_COND_FIELDS, P8P_CC_TABLE_RES_FIELDS } from "./conditions"; //Общие ресурсы условий таблицы
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { P8P_CCS_INITIAL, P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Таблица (редактор настроек)
|
||||
const TableEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
|
||||
const TableEditor = ({
|
||||
id,
|
||||
dataSource = null,
|
||||
valueProviders = {},
|
||||
conditions = P8P_CCS_INITIAL,
|
||||
actions = P8P_CAS_INITIAL,
|
||||
onSettingsChange = null
|
||||
} = {}) => {
|
||||
//Собственное состояние - текущие настройки
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource });
|
||||
}, [settings, id, dataSource]);
|
||||
|
||||
//При сохранении изменений элемента
|
||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||
|
||||
//При изменении действий
|
||||
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||
|
||||
//При изменении условий
|
||||
const handleConditionsChange = conditions => setSettings(pv => ({ ...pv, conditions }));
|
||||
|
||||
//При сохранении настроек
|
||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource, conditions, actions });
|
||||
}, [settings, id, dataSource, conditions, actions]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PEditorBox title={"Параметры таблицы"} onSave={handleSave}>
|
||||
<P8PEditorSubHeader title={"Источник данных"} />
|
||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||
<P8PEditorSubHeader title={"Условия"} />
|
||||
<P8PConditions
|
||||
conditions={settings?.conditions}
|
||||
condFields={P8P_CC_TABLE_COND_FIELDS}
|
||||
resFields={P8P_CC_TABLE_RES_FIELDS}
|
||||
onChange={handleConditionsChange}
|
||||
/>
|
||||
<P8PEditorSubHeader title={"Действия"} />
|
||||
<P8PActions
|
||||
actions={settings?.actions}
|
||||
valueProviders={valueProviders}
|
||||
areas={P8P_CA_TABLE_ACTION_AREAS}
|
||||
valueTypes={P8P_CA_TABLE_VALUE_TYPES}
|
||||
onChange={handleActionsChange}
|
||||
/>
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
@ -48,6 +82,8 @@ TableEditor.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onSettingsChange: PropTypes.func
|
||||
};
|
||||
|
||||
|
||||
@ -9,17 +9,22 @@
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
||||
import { Paper, Link } from "@mui/material"; //Интерфейсные элементы
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили
|
||||
import { P8PDataGrid } from "../../../../components/p8p_data_grid"; //Таблица данных
|
||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { useDataSource, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import {
|
||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||
P8P_COMPONENT_INLINE_MESSAGE,
|
||||
P8PComponentInlineMessage
|
||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||
import { P8P_CC_SHAPE, P8P_CCS_INITIAL } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы условий
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { getDataGridCustomTypeValue, getDataGridHandlers, isHasCellRender } from "./action"; //Общие ресурсы действий таблицы
|
||||
import { getColumnStylesByConditions } from "./conditions";
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -33,12 +38,54 @@ const COMPONENT_NAME = "Таблица";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex", height: "100%", overflow: "hidden" },
|
||||
CONTAINER: clickable => ({
|
||||
display: "flex",
|
||||
height: "100%",
|
||||
overflow: "hidden",
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
}),
|
||||
DATA_GRID: { width: "100%" },
|
||||
DATA_GRID_CONTAINER: {
|
||||
height: `calc(100%)`,
|
||||
...APP_STYLES.SCROLL
|
||||
}
|
||||
},
|
||||
DATA_GRID_ROW_CLICABLE: { cursor: "pointer" }
|
||||
};
|
||||
|
||||
//---------------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//---------------------------------------------
|
||||
|
||||
//Форматирование значения ячейки
|
||||
const dataCellRender = ({ row, columnDef, values, handlers, conditions }) => {
|
||||
//Инициализируем обработчики строки
|
||||
const { onRowClick, onColumnClick } = getDataGridHandlers(handlers, columnDef.name);
|
||||
//Инициализируем стили по условию
|
||||
const condStyles = getColumnStylesByConditions(conditions, row, columnDef.name);
|
||||
//Накладываем нужные обработчики
|
||||
return {
|
||||
cellStyle: { ...(onRowClick ? { ...STYLES.DATA_GRID_ROW_CLICABLE } : {}), ...condStyles },
|
||||
cellProps: {
|
||||
...(onRowClick
|
||||
? {
|
||||
onClick: event => {
|
||||
onRowClick({ event, values, prms: { row, columnDef } });
|
||||
}
|
||||
}
|
||||
: {})
|
||||
},
|
||||
data: onColumnClick ? (
|
||||
<Link component="button" variant="body2" underline="hover" onClick={event => onColumnClick({ event, values, prms: { row, columnDef } })}>
|
||||
{row[columnDef.name]}
|
||||
</Link>
|
||||
) : (
|
||||
row[columnDef.name]
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
//-----------
|
||||
@ -46,26 +93,27 @@ const STYLES = {
|
||||
//-----------
|
||||
|
||||
//Таблица (представление)
|
||||
const Table = ({ dataSource = null, values = {} } = {}) => {
|
||||
const Table = ({ dataSource = null, values = {}, conditions = P8P_CCS_INITIAL, actions = P8P_CAS_INITIAL, onValuesChange = null } = {}) => {
|
||||
//Собственное состояние - данные
|
||||
const [data, error] = useDataSource({ dataSource, values });
|
||||
const [dataGrid, error, haveConfing, haveData] = useDataSource({ dataSource, values, componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.TABLE });
|
||||
|
||||
//Флаг настроенности таблицы
|
||||
const haveConfing = dataSource?.stored ? true : false;
|
||||
//Собственное состояние - обработчики компонента
|
||||
const [handlers] = useComponentHandlers({ actions, onValuesChange, getCustomTypeValue: getDataGridCustomTypeValue });
|
||||
|
||||
//Флаг наличия данных
|
||||
const haveData = data?.init === true && !error ? true : false;
|
||||
//Признак необходимости рендера ячеек
|
||||
const hasCellRender = isHasCellRender(handlers, conditions);
|
||||
|
||||
//Данные таблицы
|
||||
const dataGrid = data?.XDATA_GRID || {};
|
||||
//Обработчики областей
|
||||
const { onComponentClick } = getDataGridHandlers(handlers);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Paper
|
||||
{...(haveConfing && haveData
|
||||
? { sx: { ...STYLES.CONTAINER } }
|
||||
? { sx: { ...STYLES.CONTAINER(onComponentClick) } }
|
||||
: { className: "component-view__container component-view__container__empty" })}
|
||||
elevation={6}
|
||||
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||
>
|
||||
{haveConfing && haveData ? (
|
||||
<P8PDataGrid
|
||||
@ -73,6 +121,17 @@ const Table = ({ dataSource = null, values = {} } = {}) => {
|
||||
{...dataGrid}
|
||||
style={STYLES.DATA_GRID}
|
||||
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
|
||||
dataCellRender={
|
||||
hasCellRender
|
||||
? prms =>
|
||||
dataCellRender({
|
||||
...prms,
|
||||
values,
|
||||
handlers,
|
||||
conditions
|
||||
})
|
||||
: null
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<P8PComponentInlineMessage
|
||||
@ -89,7 +148,10 @@ const Table = ({ dataSource = null, values = {} } = {}) => {
|
||||
//Контроль свойств компонента - Таблица (представление)
|
||||
Table.propTypes = {
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
values: PropTypes.object
|
||||
values: PropTypes.object,
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onValuesChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
||||
@ -76,7 +76,8 @@ LayoutItem.propTypes = {
|
||||
onDeleteClick: PropTypes.func,
|
||||
item: PropTypes.object.isRequired,
|
||||
editMode: PropTypes.bool,
|
||||
selected: PropTypes.bool
|
||||
selected: PropTypes.bool,
|
||||
isDragging: PropTypes.bool
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
||||
134
app/panels/panels_editor/layouts.js
Normal file
134
app/panels/panels_editor/layouts.js
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { InputLabel, Input, IconButton, Icon, Button } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { TEXTS } from "../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { P8PAppProgress } from "../../components/p8p_app_progress"; //Индикатор процесса
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
INPUT_FILE: { display: "none" },
|
||||
INPUT_LABEL: { display: "contents" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Компонент "Загрузка из файла"
|
||||
const ImportPanel = ({ id, buttonView, onClick, disabled = false }) => {
|
||||
//Состояние загрузки файла
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
//При выборе файла
|
||||
const handleChange = event => {
|
||||
//Если файл выбран
|
||||
if (event.target.files[0]) {
|
||||
//Инициализируем считыватель файла
|
||||
const reader = new FileReader();
|
||||
//Обработчик начала считывания данных
|
||||
reader.onloadstart = () => setLoading(true);
|
||||
//Обработчик считывания данных
|
||||
reader.onload = async e => {
|
||||
//Обрабатываем текст файла
|
||||
onClick(e.target.result);
|
||||
setLoading(false);
|
||||
};
|
||||
//Обработчик ошибки считывания данных
|
||||
reader.onerror = () => setLoading(false);
|
||||
//Загружаем данные из файла
|
||||
reader.readAsText(event.target.files[0]);
|
||||
}
|
||||
//Очищаем значение
|
||||
event.target.value = null;
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<>
|
||||
{loading ? <P8PAppProgress open={true} text={TEXTS.LOADING} /> : null}
|
||||
<InputLabel htmlFor={id} sx={STYLES.INPUT_LABEL}>
|
||||
<Input
|
||||
id={id}
|
||||
type="file"
|
||||
hidden
|
||||
onChange={handleChange}
|
||||
sx={STYLES.INPUT_FILE}
|
||||
inputProps={{ accept: ".xml" }}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{buttonView}
|
||||
</InputLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Компонент "Загрузка из файла"
|
||||
ImportPanel.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
buttonView: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool
|
||||
};
|
||||
|
||||
//Генерация действия "Загрузить из файла" меню действий
|
||||
export const toolbarImportRenderer = ({ icon, title, disabled, onClick }) => {
|
||||
//Представление кнопки загрузки
|
||||
let importButtonView = (
|
||||
<IconButton title={title} disabled={disabled}>
|
||||
<Icon>{icon}</Icon>
|
||||
</IconButton>
|
||||
);
|
||||
//Генерация содержимого
|
||||
return <ImportPanel id={"input-file-loader-toolbar"} buttonView={importButtonView} onClick={onClick} disabled={disabled} />;
|
||||
};
|
||||
|
||||
//Компонент кнопки импорта панели из файла
|
||||
export const ImportPanelButton = ({ id, startIcon, title, onClick }) => {
|
||||
//Представление кнопки загрузки
|
||||
let importButtonView = (
|
||||
<Button startIcon={startIcon ? <Icon>{startIcon}</Icon> : null} component="span">
|
||||
{title}
|
||||
</Button>
|
||||
);
|
||||
//Генерация содержимого
|
||||
return <ImportPanel id={id} buttonView={importButtonView} onClick={onClick} />;
|
||||
};
|
||||
|
||||
//Контроль свойств - Компонент кнопки импорта панели из файла
|
||||
ImportPanelButton.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
startIcon: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Выгрузка XML файла
|
||||
export const exportXMLFile = (data, fileName) => {
|
||||
//Переводим в данные для файла
|
||||
const blobData = new Blob([data], { type: "text/plain" });
|
||||
//Формируем URL
|
||||
const url = URL.createObjectURL(blobData);
|
||||
//Делаем линк и устанавливаем параметры, после добавляем в DOM
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${fileName.replace(/[/\\?%*:|"<>.,;= ]/g, "_")}.xml`;
|
||||
document.body.appendChild(a);
|
||||
//Выполняем открытие ссылки
|
||||
a.click();
|
||||
//Очищаем от лишних данных
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
358
app/panels/panels_editor/panel_editor.js
Normal file
358
app/panels/panels_editor/panel_editor.js
Normal file
@ -0,0 +1,358 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Редактор панели
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
|
||||
import { Box, Grid, Menu, MenuItem, Popover } from "@mui/material"; //Интерфейсные элементы
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
|
||||
import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
|
||||
import { LayoutItem } from "./layout_item"; //Элемент макета
|
||||
import { ComponentView } from "./component_view"; //Представление компонента панели
|
||||
import { ComponentEditor } from "./component_editor"; //Редактор свойств компонента панели
|
||||
import { COMPONENTS } from "./components/components"; //Описание доступных компонентов
|
||||
import { usePanelDesc, useWindowResize } from "./components/components_hooks"; //Вспомогательные хуки
|
||||
import { toolbarImportRenderer } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
import "react-grid-layout/css/styles.css"; //Стили для адаптивного макета
|
||||
import "react-resizable/css/styles.css"; //Стили для адаптивного макета
|
||||
import "./panels_editor.css"; //Стили редактора панелей
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { PanelPropsEditor } from "./panel_props_editor"; //Редактор глобальных свойств панели
|
||||
import { PanelVariableList } from "./panel_variable_list"; //Список переменных панели
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex", overflow: "auto", ...APP_STYLES.SCROLL },
|
||||
GRID_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})` },
|
||||
GRID_ITEM_INSPECTOR: { backgroundColor: "#e9ecef", height: "100%", overflow: "auto", ...APP_STYLES.SCROLL },
|
||||
POPOVER_CONTAINER: { [`& .MuiPaper-root`]: { maxHeight: "60%", width: "300px", ...APP_STYLES.SCROLL } }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Обёрдка для динамического макета
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
|
||||
//Редактор панели
|
||||
const PanelEditor = ({
|
||||
panel,
|
||||
panelName,
|
||||
editMode,
|
||||
isPanelChanged,
|
||||
isPanelVariablesOpened,
|
||||
onPanelChanged,
|
||||
onPanelClose,
|
||||
onEditModeToggle,
|
||||
onPanelsManagerOpen,
|
||||
onPanelVariablesClose
|
||||
}) => {
|
||||
//Собственное состояние - ID перемещаемого макета
|
||||
//Требуется для разделения onDrag и onClick на макете
|
||||
const [draggingID, setDraggingID] = useState("");
|
||||
|
||||
//Состояние описания и функций панели
|
||||
const [
|
||||
panelDesc,
|
||||
isInit,
|
||||
addComponent,
|
||||
deleteComponent,
|
||||
breakpointChange,
|
||||
layoutsChange,
|
||||
changeValueProviders,
|
||||
changeComponentSettings,
|
||||
changePanelSettings,
|
||||
savePanelDesc,
|
||||
importPanelDesc,
|
||||
exportPanelDesc
|
||||
] = usePanelDesc(panel);
|
||||
|
||||
//Собственное состояние - признак инициализации макетов
|
||||
const [isLayoutsInit, setIsLayoutsInit] = useState(true);
|
||||
//Собственное состояние - редактирование компонента
|
||||
const [editComponent, setEditComponent] = useState(null);
|
||||
//Собственное состояние - элемент открывающий список
|
||||
const [addMenuAnchorEl, setAddMenuAnchorEl] = useState(null);
|
||||
//Собственное состояние - высота строки ResponsiveGridLayout
|
||||
const [rowHeight] = useWindowResize();
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//При перемещении макета
|
||||
const handleOnDrag = layout => {
|
||||
//Если ID перемещаемого макета не установлен - устанавливаем
|
||||
if (!draggingID) setDraggingID(layout.i);
|
||||
};
|
||||
|
||||
//При завершении перемещения макета
|
||||
const handleOnDragStop = () => setDraggingID("");
|
||||
|
||||
//Открытие редактора настроек компонента
|
||||
const handleComponentSettingsEditorOpen = id => setEditComponent(id);
|
||||
|
||||
//Закрытие реактора настроек компонента
|
||||
const handleComponentSettingsEditorClose = () => setEditComponent(null);
|
||||
|
||||
//При нажатии на настройки компонента
|
||||
const handleComponentSettingsClick = id => (editComponent === id ? handleComponentSettingsEditorClose() : handleComponentSettingsEditorOpen(id));
|
||||
|
||||
//Открытие/сокрытие меню добавления
|
||||
const handleAddMenuToggle = target => setAddMenuAnchorEl(target instanceof Element ? target : null);
|
||||
|
||||
//При изменении размера холста
|
||||
const handleBreakpointChange = breakpoint => breakpointChange(breakpoint);
|
||||
|
||||
//При изменении состояния макета
|
||||
const handleLayoutChange = (currentLayout, layouts) => {
|
||||
//Изменяем только в случае, если панель выбрана, проиницализирована и это режим редактирования
|
||||
if (panel && isInit) {
|
||||
//ResponsiveGridLayout реагирует на каждое изменение, при первой загрузке с сервера не требуется перезагрузка макетов
|
||||
if (isLayoutsInit) {
|
||||
setIsLayoutsInit(false);
|
||||
} else {
|
||||
//Изменяем состояние макета
|
||||
layoutsChange(layouts);
|
||||
//Указываем, что панель изменилась (только при редактировании)
|
||||
onPanelChanged(editMode ? true : false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//Добвление компонента в макет
|
||||
const handlePanelComponentAdd = component => {
|
||||
//Добавляем компонент и его макет
|
||||
addComponent(component);
|
||||
//Указываем, что панель изменилась
|
||||
onPanelChanged(true);
|
||||
};
|
||||
|
||||
//Удаление компонента из макета
|
||||
const handlePanelComponentDelete = id => {
|
||||
//Удаляем компонент
|
||||
deleteComponent(id);
|
||||
//Если удаляемый компонент редактируется - закрываем редактор
|
||||
editComponent === id && handleComponentSettingsEditorClose();
|
||||
//Указываем, что панель изменилась
|
||||
onPanelChanged(true);
|
||||
};
|
||||
|
||||
//При нажатии на кнопку добавления
|
||||
const handleAddClick = e => handleAddMenuToggle(e.currentTarget);
|
||||
|
||||
//При выборе элемента меню добавления
|
||||
const handleAddMenuItemClick = component => {
|
||||
handleAddMenuToggle();
|
||||
handlePanelComponentAdd(component);
|
||||
};
|
||||
|
||||
//При изменении значений в компоненте
|
||||
const handleComponentValuesChange = values => {
|
||||
//Изменяем значения проводника
|
||||
changeValueProviders(values);
|
||||
//Указываем, что панель изменилась (только при редактировании)
|
||||
onPanelChanged(editMode ? true : false);
|
||||
};
|
||||
|
||||
//При изменении настроек компонента
|
||||
const handleComponentSettingsChange = ({ id = null, settings = {}, closeEditor = false } = {}) => {
|
||||
if (id && panelDesc.components[id]) {
|
||||
//Изменяем настройки компонента
|
||||
changeComponentSettings(id, settings, () => {
|
||||
if (closeEditor === true) handleComponentSettingsEditorClose();
|
||||
//Указываем, что панель изменилась
|
||||
onPanelChanged(true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//При изменении настроек панели
|
||||
const handlePanelSettingsChange = ({ valueProviders }) => {
|
||||
//Изменяем настройки панели
|
||||
changePanelSettings(valueProviders);
|
||||
//Указываем, что панель изменилась
|
||||
onPanelChanged(true);
|
||||
};
|
||||
|
||||
//При удалении компоненета
|
||||
const handleComponentDeleteClick = id => handlePanelComponentDelete(id);
|
||||
|
||||
//Закрытие панели
|
||||
const handlePanelClose = () => onPanelClose();
|
||||
|
||||
//При запуске панели
|
||||
const handlePanelPlay = () => onEditModeToggle();
|
||||
|
||||
//При сохранении описания панели
|
||||
const handlePanelDescSave = () => savePanelDesc(isChanged => onPanelChanged(isChanged));
|
||||
|
||||
//При импорте настройки панели из файла
|
||||
const handleImportPanel = (fileData, callback) => importPanelDesc(fileData, callback);
|
||||
|
||||
//При выгрузке настройки панели в файл
|
||||
const handleExportPanel = () => {
|
||||
//Если панель выбрана и есть изменения
|
||||
if (panel && isPanelChanged)
|
||||
showMsgWarn(`Панель содержит несохраненные изменения. Выгрузить панель в текущем состоянии?`, () => exportPanelDesc(panelName));
|
||||
else exportPanelDesc(panelName);
|
||||
};
|
||||
|
||||
//Текущие значения панели
|
||||
const values = Object.keys(panelDesc.valueProviders).reduce((res, key) => ({ ...res, ...{ [key]: panelDesc.valueProviders[key].value } }), {});
|
||||
|
||||
//Меню добавления
|
||||
const addMenu = (
|
||||
<Menu anchorEl={addMenuAnchorEl} open={Boolean(addMenuAnchorEl)} onClose={handleAddMenuToggle}>
|
||||
{COMPONENTS.map((comp, i) => (
|
||||
<MenuItem key={i} onClick={() => handleAddMenuItemClick(comp)}>
|
||||
{comp.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
//Панель инструментов
|
||||
const toolBar = (
|
||||
<P8PEditorToolBar
|
||||
items={[
|
||||
{ icon: "file_open", title: "Менеджер панелей", onClick: onPanelsManagerOpen },
|
||||
{ icon: "save", title: "Сохранить", onClick: handlePanelDescSave, disabled: !isPanelChanged },
|
||||
{ icon: "close", title: "Закрыть панель", onClick: handlePanelClose, disabled: !panel },
|
||||
{ icon: "play_arrow", title: "Запустить", onClick: handlePanelPlay, disabled: !panel },
|
||||
{ icon: "add", title: "Добавить элемент", onClick: handleAddClick, disabled: !panel },
|
||||
{
|
||||
icon: "file_upload",
|
||||
title: "Загрузить из файла",
|
||||
onClick: fileData => {
|
||||
handleImportPanel(fileData, isLoaded => onPanelChanged(isLoaded));
|
||||
},
|
||||
customRenderer: toolbarImportRenderer,
|
||||
disabled: !panel
|
||||
},
|
||||
{ icon: "file_download", title: "Выгрузить в файл", onClick: handleExportPanel, disabled: !panel },
|
||||
{ icon: "settings", title: "Параметры панели", onClick: handleComponentSettingsEditorClose, disabled: !panel }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
//При изменении панели
|
||||
useEffect(() => {
|
||||
//Первое изменение состояния макета - инициализация
|
||||
setIsLayoutsInit(true);
|
||||
//Очищаем редактируемый компонент
|
||||
setEditComponent(null);
|
||||
}, [panel]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{addMenu}
|
||||
<Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
|
||||
<Grid item xs={editMode ? 20 : 25}>
|
||||
<ResponsiveGridLayout
|
||||
autoSize={true}
|
||||
rowHeight={rowHeight}
|
||||
className={"layout"}
|
||||
layouts={panelDesc.layouts}
|
||||
breakpoints={{ lg: 1200 }}
|
||||
cols={{ lg: 12 }}
|
||||
onBreakpointChange={handleBreakpointChange}
|
||||
onLayoutChange={handleLayoutChange}
|
||||
useCSSTransforms={true}
|
||||
compactType={"vertical"}
|
||||
isDraggable={editMode}
|
||||
isResizable={editMode}
|
||||
onDrag={(layouts, oldItem) => handleOnDrag(oldItem)}
|
||||
onDragStop={handleOnDragStop}
|
||||
>
|
||||
{panelDesc.layouts[panelDesc.breakpoint].map(item => (
|
||||
<LayoutItem
|
||||
key={item.i}
|
||||
onSettingsClick={handleComponentSettingsClick}
|
||||
onDeleteClick={handleComponentDeleteClick}
|
||||
item={item}
|
||||
editMode={editMode}
|
||||
selected={editMode && editComponent === item.i}
|
||||
>
|
||||
<ComponentView
|
||||
id={item.i}
|
||||
isDragging={item.i === draggingID}
|
||||
path={panelDesc.components[item.i]?.path}
|
||||
settings={panelDesc.components[item.i]?.settings}
|
||||
values={values}
|
||||
onValuesChange={handleComponentValuesChange}
|
||||
/>
|
||||
</LayoutItem>
|
||||
))}
|
||||
</ResponsiveGridLayout>
|
||||
</Grid>
|
||||
{editMode && (
|
||||
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
|
||||
{toolBar}
|
||||
{panel ? (
|
||||
editComponent ? (
|
||||
<ComponentEditor
|
||||
id={editComponent}
|
||||
path={panelDesc.components[editComponent].path}
|
||||
settings={panelDesc.components[editComponent].settings}
|
||||
valueProviders={panelDesc.valueProviders}
|
||||
onSettingsChange={handleComponentSettingsChange}
|
||||
/>
|
||||
) : (
|
||||
<PanelPropsEditor
|
||||
valueProviders={panelDesc.valueProviders}
|
||||
onSettingsChange={handlePanelSettingsChange}
|
||||
onDependencyClick={handleComponentSettingsClick}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
</Grid>
|
||||
)}
|
||||
<Popover
|
||||
id={"variables-popover"}
|
||||
sx={STYLES.POPOVER_CONTAINER}
|
||||
open={!editMode && isPanelVariablesOpened}
|
||||
marginThreshold={10}
|
||||
onClose={onPanelVariablesClose}
|
||||
anchorReference={"anchorPosition"}
|
||||
anchorPosition={{ top: 60, left: window.innerWidth }}
|
||||
transitionDuration={{ appear: 0, enter: 200, exit: 200 }}
|
||||
>
|
||||
<PanelVariableList valueProviders={panelDesc.valueProviders} onValuesChange={handleComponentValuesChange} />
|
||||
</Popover>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Редактор панели
|
||||
PanelEditor.propTypes = {
|
||||
panel: PropTypes.number.isRequired,
|
||||
panelName: PropTypes.string.isRequired,
|
||||
editMode: PropTypes.bool.isRequired,
|
||||
isPanelChanged: PropTypes.bool.isRequired,
|
||||
isPanelVariablesOpened: PropTypes.bool.isRequired,
|
||||
onPanelChanged: PropTypes.func.isRequired,
|
||||
onPanelClose: PropTypes.func.isRequired,
|
||||
onEditModeToggle: PropTypes.func.isRequired,
|
||||
onPanelsManagerOpen: PropTypes.func.isRequired,
|
||||
onPanelVariablesClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelEditor };
|
||||
230
app/panels/panels_editor/panel_props_editor.js
Normal file
230
app/panels/panels_editor/panel_props_editor.js
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Редактор глобальных свойств панели
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Chip, Stack, TextField, Button, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PConfigDialog } from "../../components/editors/p8p_config_dialog"; //Диалог настройки
|
||||
import { isElementNameCorrect } from "../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов
|
||||
import { P8PEditorSubHeader } from "../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8PEditorBox } from "../../components/editors/p8p_editor_box"; //Контейнер редактора
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { STYLES as COMMON_STYLES } from "../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов
|
||||
import { P8PChipList } from "../../components/editors/p8p_chip_list"; //Дополнительные настройки редактора
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
STACK_DEPENENCIES: { gap: "5px", ...APP_STYLES.SCROLL, overflow: "auto", maxHeight: "160px", width: "260px" },
|
||||
CHIP_DEPENDENCY: { ...COMMON_STYLES.CHIP(true, false), minHeight: "32px" }
|
||||
};
|
||||
|
||||
//Начальное состояние переменной
|
||||
const VARIABLE_INITIAL = {
|
||||
dependencies: [],
|
||||
description: "",
|
||||
value: ""
|
||||
};
|
||||
|
||||
//Структура переменной
|
||||
export const VARIABLE_SHAPE = PropTypes.shape({
|
||||
dependencies: PropTypes.arrayOf(PropTypes.string),
|
||||
description: PropTypes.string.isRequired,
|
||||
value: PropTypes.string
|
||||
});
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Редактор переменной
|
||||
const VariableEditor = ({ variableName = "", variable = null, dependencies = [], onDependencyClick, onOk, onCancel } = {}) => {
|
||||
//Собственное состояние - имя переменной
|
||||
const [name, setName] = useState(variableName || "");
|
||||
|
||||
//Собственное состояние - параметры переменной
|
||||
const [state, setState] = useState({ ...VARIABLE_INITIAL, ...variable });
|
||||
|
||||
//При закрытии редактора с сохранением
|
||||
const handleOk = () => onOk(name, { ...state });
|
||||
|
||||
//При закрытии редактора с отменой
|
||||
const handleCancel = () => onCancel();
|
||||
|
||||
//При изменении параметра переменной
|
||||
const handleChange = e => {
|
||||
//Если это поле наименования, то проверяем корректность
|
||||
if (e.target.id === "name" && isElementNameCorrect(e.target.value)) setName(e.target.value);
|
||||
//Устанавливаем значение
|
||||
else setState(pv => ({ ...pv, [e.target.id]: e.target.value }));
|
||||
};
|
||||
|
||||
//Доступность сохранения настроек элемента
|
||||
const okDisabled = !name || !state.description ? true : false;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PConfigDialog title={`${variable ? "Изменение" : "Добавление"} элемента`} onOk={handleOk} onCancel={handleCancel} okDisabled={okDisabled}>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
<TextField type={"text"} variant={"standard"} value={name} label={"Имя"} id={"name"} onChange={handleChange} required={true} />
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.description}
|
||||
label={"Описание"}
|
||||
id={"description"}
|
||||
onChange={handleChange}
|
||||
required={true}
|
||||
/>
|
||||
</Stack>
|
||||
{dependencies.length !== 0 ? (
|
||||
<>
|
||||
<P8PEditorSubHeader title={"Зависимости"} />
|
||||
<Stack sx={STYLES.STACK_DEPENENCIES} direction={"column"} p={1} useFlexGap={true}>
|
||||
{dependencies.map((item, i) => (
|
||||
<Chip
|
||||
key={i}
|
||||
label={item}
|
||||
variant={"outlined"}
|
||||
onClick={() => onDependencyClick && onDependencyClick(item)}
|
||||
sx={STYLES.CHIP_DEPENDENCY}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
) : null}
|
||||
</P8PConfigDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - редактор переменной
|
||||
VariableEditor.propTypes = {
|
||||
variableName: PropTypes.string,
|
||||
variable: VARIABLE_SHAPE,
|
||||
dependencies: PropTypes.array,
|
||||
onDependencyClick: PropTypes.func.isRequired,
|
||||
onOk: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор глобальных свойств панели
|
||||
const PanelPropsEditor = ({ valueProviders = {}, onSettingsChange, onDependencyClick } = {}) => {
|
||||
//Собственное состояние - редактор переменных панели
|
||||
const [variableEditor, setVariableEditor] = useState({ display: false, item: null });
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgErr } = useContext(MessagingСtx);
|
||||
|
||||
//При добавлении новой переменной
|
||||
const handleVariableAdd = () => setVariableEditor({ display: true, item: null });
|
||||
|
||||
//При нажатии на переменную
|
||||
const handleVariableClick = index => setVariableEditor({ display: true, item: variables[index] });
|
||||
|
||||
//При отмене сохранения изменений переменной
|
||||
const handleVariableCancel = () => setVariableEditor({ display: false, index: null });
|
||||
|
||||
//При сохранении изменений переменной
|
||||
const handleVariableSave = (variableName, variable) => {
|
||||
//Текст ошибки
|
||||
let msgError = "";
|
||||
//Если это добавление или изменилось наименование - проверяем на дублирование
|
||||
if ((!variableEditor.item || variableEditor.item !== variableName) && Object.prototype.hasOwnProperty.call(valueProviders, variableName)) {
|
||||
msgError = `Дублирование наименования параметра "${variableName}".`;
|
||||
}
|
||||
//Если это исправление наименования параметра, но он используется
|
||||
if (variableEditor.item && variableEditor.item !== variableName && valueProviders[variableEditor.item].dependencies.length !== 0) {
|
||||
msgError = `Переменная имеет связи с компонентами. Изменение наименования запрещено.`;
|
||||
}
|
||||
//Если есть ошибка - выводим
|
||||
if (msgError) {
|
||||
showMsgErr(msgError);
|
||||
} else {
|
||||
//Копируем параметры
|
||||
let newValueProviders = { ...valueProviders };
|
||||
//Удаляем старое значение, если требуется
|
||||
variableEditor.item && variableEditor.item !== variableName ? delete newValueProviders[variableEditor.item] : null;
|
||||
//Добавляем новый параметр
|
||||
newValueProviders = { ...newValueProviders, [variableName]: { ...variable } };
|
||||
//Обновляем проводники панели
|
||||
onSettingsChange({ valueProviders: { ...newValueProviders } });
|
||||
//Закрываем редактирование
|
||||
setVariableEditor({ display: false, index: null });
|
||||
}
|
||||
};
|
||||
|
||||
//При удалении переменной
|
||||
const handleVariableDelete = index => {
|
||||
const variable = variables[index];
|
||||
//Если переменная не используется в компонентах - удаляем
|
||||
if (valueProviders[variable].dependencies.length === 0) {
|
||||
const newValueProviders = { ...valueProviders };
|
||||
delete newValueProviders[variable];
|
||||
onSettingsChange({ valueProviders: { ...newValueProviders } });
|
||||
} else {
|
||||
showMsgErr(`Переменная имеет связи с компонентами. Удаление запрещено.`);
|
||||
}
|
||||
};
|
||||
|
||||
//При нажатии на зависимый компонент
|
||||
const handleDependencyClick = id => {
|
||||
//Закрываем редактирование
|
||||
setVariableEditor({ display: false, index: null });
|
||||
//Открываем настройку компонента
|
||||
onDependencyClick(id);
|
||||
};
|
||||
|
||||
//Текущие переменные панели
|
||||
const variables = Object.keys(valueProviders).reduce((res, key) => [...res, key], []);
|
||||
|
||||
//Определяем структуру переменных для отображения
|
||||
const variableChips = variables.map(item => ({ text: item, title: item }));
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PEditorBox title={"Параметры панели"}>
|
||||
{variableEditor.display && (
|
||||
<VariableEditor
|
||||
variableName={variableEditor.item}
|
||||
variable={variableEditor.item !== null ? { ...valueProviders[variableEditor.item] } : null}
|
||||
dependencies={variableEditor.item ? valueProviders[variableEditor.item].dependencies : []}
|
||||
onDependencyClick={handleDependencyClick}
|
||||
onCancel={handleVariableCancel}
|
||||
onOk={handleVariableSave}
|
||||
/>
|
||||
)}
|
||||
<P8PEditorSubHeader title={"Переменные"} />
|
||||
<P8PChipList items={variableChips} onClick={handleVariableClick} onDelete={handleVariableDelete} />
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handleVariableAdd}>
|
||||
Добавить переменную
|
||||
</Button>
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - редактор глобальных свойств панели
|
||||
PanelPropsEditor.propTypes = {
|
||||
valueProviders: PropTypes.object,
|
||||
onSettingsChange: PropTypes.func.isRequired,
|
||||
onDependencyClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelPropsEditor };
|
||||
90
app/panels/panels_editor/panel_variable_list.js
Normal file
90
app/panels/panels_editor/panel_variable_list.js
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Список переменных панели
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Typography, Stack } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PEditorBox } from "../../components/editors/p8p_editor_box"; //Контейнер редактора
|
||||
import { P8PChipList } from "../../components/editors/p8p_chip_list"; //Дополнительные настройки редактора
|
||||
import { hasValue } from "../../core/utils"; //Вспомогательные процедуры и функции
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
STACK_ITEM: { alignItems: "center" },
|
||||
TYPOGRAPHY_INFO: { overflow: "hidden" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
const chipRender = item => {
|
||||
//Определяем описание переменной
|
||||
const description = item.description;
|
||||
//Определяем значение переменной
|
||||
const value = item.value ?? "";
|
||||
//Определяем заголовок
|
||||
const title = `${description}${value ? " - " : ""}${value}`;
|
||||
return (
|
||||
<Stack direction="row" spacing={1} sx={STYLES.STACK_ITEM}>
|
||||
<Typography variant="body2" title={title} sx={STYLES.TYPOGRAPHY_INFO}>
|
||||
<strong>{description}</strong>
|
||||
{`${value ? " - " : ""}${value}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Список переменных панели
|
||||
const PanelVariableList = ({ valueProviders = {}, onValuesChange = null }) => {
|
||||
//При очистке значения переменной
|
||||
const handleVariableClear = index => onValuesChange && onValuesChange({ [values[index].key]: "" });
|
||||
|
||||
//Определяем структуру переменных
|
||||
const values = Object.keys(valueProviders).reduce(
|
||||
(prev, key) => [
|
||||
...prev,
|
||||
{
|
||||
description: valueProviders[key].description,
|
||||
value: valueProviders[key].value,
|
||||
deleteIcon: "filter_alt_off",
|
||||
disableDelete: !hasValue(valueProviders[key].value),
|
||||
key
|
||||
}
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PEditorBox title={"Переменные"}>
|
||||
<P8PChipList items={values} labelRender={chipRender} onDelete={onValuesChange ? index => handleVariableClear(index) : null} />
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - список переменных панели
|
||||
PanelVariableList.propTypes = {
|
||||
valueProviders: PropTypes.object,
|
||||
onValuesChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelVariableList };
|
||||
@ -7,242 +7,171 @@
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState, useContext } from "react"; //Классы React
|
||||
import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
|
||||
import { Box, Grid, Menu, MenuItem, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
|
||||
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
||||
import { Box, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
|
||||
import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
|
||||
import { genGUID } from "../../core/utils"; //Общие вспомогательные функции
|
||||
import { LayoutItem } from "./layout_item"; //Элемент макета
|
||||
import { ComponentView } from "./component_view"; //Представление компонента панели
|
||||
import { ComponentEditor } from "./component_editor"; //Редактор свойств компонента панели
|
||||
import { COMPONETNS } from "./components/components"; //Описание доступных компонентов
|
||||
import "react-grid-layout/css/styles.css"; //Стили для адаптивного макета
|
||||
import "react-resizable/css/styles.css"; //Стили для адаптивного макета
|
||||
import "./panels_editor.css"; //Стили редактора панелей
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
import { PanelsManager } from "./components/panels_manager/panels_manager"; //Менеджер панелей
|
||||
import { usePanel } from "./components/components_hooks"; //Вспомогательные хуки
|
||||
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
|
||||
import { PanelEditor } from "./panel_editor"; //Редактор панели
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Заголовок панели по умолчанию
|
||||
const APP_BAR_TITLE_DEFAULT = "Редактор панелей";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex" },
|
||||
GRID_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})` },
|
||||
GRID_ITEM_INSPECTOR: { backgroundColor: "#e9ecef" },
|
||||
FAB_EDIT: { position: "absolute", top: 12, right: 12, zIndex: 2000 }
|
||||
};
|
||||
|
||||
//Заголовоки по умолчанию
|
||||
const PANEL_CAPTION_EDIT_MODE = "Редактор панелей";
|
||||
const PANEL_CAPTION_EXECUTE_MODE = "Исполнение панели";
|
||||
|
||||
//Начальное состояние размера макета
|
||||
const INITIAL_BREAKPOINT = "lg";
|
||||
|
||||
//Начальное состояние макета
|
||||
const INITIAL_LAYOUTS = {
|
||||
[INITIAL_BREAKPOINT]: []
|
||||
BOX_PLAY_MODE: {
|
||||
position: "absolute",
|
||||
top: 12,
|
||||
right: 15,
|
||||
zIndex: 2000,
|
||||
display: "flex",
|
||||
height: "42px",
|
||||
width: "145px",
|
||||
justifyContent: "flex-end",
|
||||
gap: "10px"
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Обёрдка для динамического макета
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
|
||||
//Корневой компонент редактора панелей
|
||||
const PanelsEditor = () => {
|
||||
//Собственное состояние
|
||||
const [components, setComponents] = useState({});
|
||||
const [valueProviders, setValueProviders] = useState({});
|
||||
const [layouts, setLayouts] = useState(INITIAL_LAYOUTS);
|
||||
const [breakpoint, setBreakpoint] = useState(INITIAL_BREAKPOINT);
|
||||
const [editMode, setEditMode] = useState(true);
|
||||
const [editComponent, setEditComponent] = useState(null);
|
||||
const [addMenuAnchorEl, setAddMenuAnchorEl] = useState(null);
|
||||
//Собственное состояние - иниализация панели
|
||||
const [init, setInit] = useState(true);
|
||||
//Собственное состояние - иниализируемая панель
|
||||
const [panel, panelName, editMode, isEditAvaliable, isPanelChanged, loadPanel, selectPanel, closePanel, changeEditMode, setPanelChanged] =
|
||||
usePanel();
|
||||
//Отображения менеджера панелей
|
||||
const [openPanelsManager, setOpenPanelsManager] = useState(false);
|
||||
//Отображение параметров панели
|
||||
const [openPanelVariables, setOpenPanelVariables] = useState(false);
|
||||
|
||||
//Подключение к контексту навигации
|
||||
const { getNavigationSearch } = useContext(NavigationCtx);
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
//Подключение к контексту приложения
|
||||
const { setAppBarTitle } = useContext(ApplicationСtx);
|
||||
|
||||
//Добвление компонента в макет
|
||||
const addComponent = component => {
|
||||
const id = genGUID();
|
||||
setLayouts(pv => ({ ...pv, [breakpoint]: [...pv[breakpoint], { i: id, x: 0, y: 0, w: 4, h: 10 }] }));
|
||||
setComponents(pv => ({ ...pv, [id]: { ...component } }));
|
||||
};
|
||||
|
||||
//Удаление компонента из макета
|
||||
const deleteComponent = id => {
|
||||
setLayouts(pv => ({ ...pv, [breakpoint]: layouts[breakpoint].filter(item => item.i !== id) }));
|
||||
setComponents(pv => ({ ...pv, [id]: { ...pv[id], deleted: true } }));
|
||||
if (valueProviders[id]) {
|
||||
const vPTmp = { ...valueProviders };
|
||||
delete vPTmp[id];
|
||||
setValueProviders(vPTmp);
|
||||
}
|
||||
editComponent === id && closeComponentSettingsEditor();
|
||||
};
|
||||
|
||||
//Включение/выключение режима редиктирования
|
||||
const toggleEditMode = () => {
|
||||
if (!editMode) setAppBarTitle(PANEL_CAPTION_EDIT_MODE);
|
||||
else setAppBarTitle(PANEL_CAPTION_EXECUTE_MODE);
|
||||
setEditMode(!editMode);
|
||||
};
|
||||
|
||||
//Открытие редактора настроек компонента
|
||||
const openComponentSettingsEditor = id => setEditComponent(id);
|
||||
|
||||
//Закрытие реактора настроек компонента
|
||||
const closeComponentSettingsEditor = () => setEditComponent(null);
|
||||
|
||||
//Открытие/сокрытие меню добавления
|
||||
const toggleAddMenu = target => setAddMenuAnchorEl(target instanceof Element ? target : null);
|
||||
|
||||
//При изменении размера холста
|
||||
const handleBreakpointChange = breakpoint => setBreakpoint(breakpoint);
|
||||
|
||||
//При изменении состояния макета
|
||||
const handleLayoutChange = (currentLayout, layouts) => setLayouts(layouts);
|
||||
|
||||
//При нажатии на кнопку добалвения
|
||||
const handleAddClick = e => toggleAddMenu(e.currentTarget);
|
||||
|
||||
//При выборе элемента меню добавления
|
||||
const handleAddMenuItemClick = component => {
|
||||
toggleAddMenu();
|
||||
addComponent(component);
|
||||
};
|
||||
|
||||
//При изменении значений в компоненте
|
||||
const handleComponentValuesChange = (id, values) => setValueProviders(pv => ({ ...pv, [id]: { ...values } }));
|
||||
|
||||
//При нажатии на настройки компонента
|
||||
const handleComponentSettingsClick = id => (editComponent === id ? closeComponentSettingsEditor() : openComponentSettingsEditor(id));
|
||||
|
||||
//При изменении настроек компонента
|
||||
const handleComponentSettingsChange = ({ id = null, settings = {}, providedValues = [], closeEditor = false } = {}) => {
|
||||
if (id && components[id]) {
|
||||
const providedValuesInit = providedValues.reduce((res, providedValue) => ({ ...res, [providedValue]: undefined }), {});
|
||||
if (valueProviders[id]) {
|
||||
const vPTmp = { ...valueProviders[id] };
|
||||
Object.keys(valueProviders[id]).forEach(key => !providedValues.includes(key) && delete vPTmp[key]);
|
||||
setValueProviders(pv => ({ ...pv, [id]: { ...providedValuesInit, ...vPTmp } }));
|
||||
} else setValueProviders(pv => ({ ...pv, [id]: providedValuesInit }));
|
||||
setComponents(pv => ({ ...pv, [editComponent]: { ...pv[editComponent], settings: { ...settings } } }));
|
||||
if (closeEditor === true) closeComponentSettingsEditor();
|
||||
//При выборе панели
|
||||
const handlePanelSelect = panel => {
|
||||
//Если панель выбрана и есть изменения
|
||||
if (panel && isPanelChanged)
|
||||
showMsgWarn(
|
||||
`Панель содержит несохраненные изменения. Закрыть панель без изменений?`,
|
||||
() => {
|
||||
selectPanel(panel.rn, panel.name, panel.modify === 1 ? true : false);
|
||||
setOpenPanelsManager(false);
|
||||
},
|
||||
() => setOpenPanelsManager(false)
|
||||
);
|
||||
else {
|
||||
selectPanel(panel.rn, panel.name, panel.modify === 1 ? true : false);
|
||||
setOpenPanelsManager(false);
|
||||
}
|
||||
};
|
||||
|
||||
//При удалении компоненета
|
||||
const handleComponentDeleteClick = id => deleteComponent(id);
|
||||
//Закрытие панели
|
||||
const handlePanelClose = () => {
|
||||
//Если панель выбрана и есть изменения
|
||||
if (panel && isPanelChanged)
|
||||
showMsgWarn(`Панель содержит несохраненные изменения. Закрыть панель без изменений?`, () => {
|
||||
closePanel();
|
||||
setOpenPanelsManager(true);
|
||||
});
|
||||
else {
|
||||
closePanel();
|
||||
setOpenPanelsManager(true);
|
||||
}
|
||||
};
|
||||
|
||||
//При подключении к странице
|
||||
//Открытие менеджера панелей
|
||||
const handleOpenPanelsManager = () => setOpenPanelsManager(true);
|
||||
|
||||
//Закрытие менеджера панелей
|
||||
const handleCancelPanelsManager = () => setOpenPanelsManager(false);
|
||||
|
||||
//Открытие/закрытие параметров панели
|
||||
const handleToggleOpenPanelVariables = () => setOpenPanelVariables(!openPanelVariables);
|
||||
|
||||
//Включение/выключение режима редактирования
|
||||
const handleToggleEditMode = () => {
|
||||
//Включаем/отключаем панель
|
||||
changeEditMode(!editMode);
|
||||
//Изначально отключаем отображение параметров панели
|
||||
setOpenPanelVariables(false);
|
||||
};
|
||||
|
||||
//Дополнительные кнопки режима использования панели
|
||||
const runTimeButtons = !editMode && (
|
||||
<Box sx={STYLES.BOX_PLAY_MODE}>
|
||||
{isEditAvaliable ? (
|
||||
<Fab size={"small"} color={"grey.700"} title={"Редактировать"} onClick={handleToggleEditMode} disabled={openPanelsManager}>
|
||||
<Icon>edit</Icon>
|
||||
</Fab>
|
||||
) : null}
|
||||
<Fab size={"small"} color={"grey.700"} title={"Менеджер панелей"} onClick={handleOpenPanelsManager} disabled={openPanelsManager}>
|
||||
<Icon>file_open</Icon>
|
||||
</Fab>
|
||||
<Fab size={"small"} color={"grey.700"} title={"Переменные панели"} onClick={handleToggleOpenPanelVariables} disabled={openPanelsManager}>
|
||||
<Icon>mediation</Icon>
|
||||
</Fab>
|
||||
</Box>
|
||||
);
|
||||
|
||||
//При открытии панели
|
||||
useEffect(() => {
|
||||
//addComponent(COMPONETNS[0]);
|
||||
//addComponent(COMPONETNS[3]);
|
||||
//addComponent(COMPONETNS[4]);
|
||||
//addComponent(COMPONETNS[1]);
|
||||
//addComponent(COMPONETNS[2]);
|
||||
//Считаем параметры открытия панели
|
||||
const panelPrms = getNavigationSearch();
|
||||
//Если указан рег. номер панели - устанавливаем изначальный рег. номер панели
|
||||
if (panelPrms.SCODE) loadPanel(panelPrms.SCODE);
|
||||
//Если рег. номера панели нет - открываем менеджер панелей
|
||||
else handleOpenPanelsManager();
|
||||
//Указываем, что инициализация пройдена
|
||||
setInit(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//Текущие значения панели
|
||||
const values = Object.keys(valueProviders).reduce((res, key) => ({ ...res, ...valueProviders[key] }), {});
|
||||
|
||||
//Меню добавления
|
||||
const addMenu = (
|
||||
<Menu anchorEl={addMenuAnchorEl} open={Boolean(addMenuAnchorEl)} onClose={toggleAddMenu}>
|
||||
{COMPONETNS.map((comp, i) => (
|
||||
<MenuItem key={i} onClick={() => handleAddMenuItemClick(comp)}>
|
||||
{comp.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
//Кнопка редактирования
|
||||
const editButton = !editMode && (
|
||||
<Fab sx={STYLES.FAB_EDIT} size={"small"} color={"grey.700"} title={"Редактировать"} onClick={toggleEditMode}>
|
||||
<Icon>edit</Icon>
|
||||
</Fab>
|
||||
);
|
||||
|
||||
//Панель инструмментов
|
||||
const toolBar = (
|
||||
<P8PEditorToolBar
|
||||
items={[
|
||||
{ icon: "play_arrow", title: "Запустить", onClick: toggleEditMode },
|
||||
{
|
||||
icon: "add",
|
||||
title: "Добавить элемент",
|
||||
onClick: handleAddClick
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
//При изменении мнемокода панели
|
||||
useEffect(() => {
|
||||
//Устанавливаем заголовок в шапке приложения
|
||||
setAppBarTitle(panelName ? `Панель [${panelName}]` : APP_BAR_TITLE_DEFAULT);
|
||||
}, [panelName, setAppBarTitle]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{editButton}
|
||||
{addMenu}
|
||||
<Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
|
||||
<Grid item xs={editMode ? 20 : 25}>
|
||||
<ResponsiveGridLayout
|
||||
rowHeight={5}
|
||||
className={"layout"}
|
||||
layouts={layouts}
|
||||
breakpoints={{ lg: 1200 }}
|
||||
cols={{ lg: 12 }}
|
||||
onBreakpointChange={handleBreakpointChange}
|
||||
onLayoutChange={handleLayoutChange}
|
||||
useCSSTransforms={true}
|
||||
compactType={"vertical"}
|
||||
isDraggable={editMode}
|
||||
isResizable={editMode}
|
||||
>
|
||||
{layouts[breakpoint].map(item => (
|
||||
<LayoutItem
|
||||
key={item.i}
|
||||
onSettingsClick={handleComponentSettingsClick}
|
||||
onDeleteClick={handleComponentDeleteClick}
|
||||
item={item}
|
||||
editMode={editMode}
|
||||
selected={editMode && editComponent === item.i}
|
||||
>
|
||||
<ComponentView
|
||||
id={item.i}
|
||||
path={components[item.i]?.path}
|
||||
settings={components[item.i]?.settings}
|
||||
values={values}
|
||||
onValuesChange={handleComponentValuesChange}
|
||||
/>
|
||||
</LayoutItem>
|
||||
))}
|
||||
</ResponsiveGridLayout>
|
||||
</Grid>
|
||||
{editMode && (
|
||||
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
|
||||
{toolBar}
|
||||
{editComponent && (
|
||||
<>
|
||||
<ComponentEditor
|
||||
id={editComponent}
|
||||
path={components[editComponent].path}
|
||||
settings={components[editComponent].settings}
|
||||
valueProviders={valueProviders}
|
||||
onSettingsChange={handleComponentSettingsChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
<>
|
||||
{!init ? (
|
||||
<>
|
||||
{runTimeButtons}
|
||||
{openPanelsManager && (
|
||||
<PanelsManager current={panel} isEditable={true} onPanelSelect={handlePanelSelect} onCancel={handleCancelPanelsManager} />
|
||||
)}
|
||||
{panel && (
|
||||
<PanelEditor
|
||||
panel={panel}
|
||||
panelName={panelName}
|
||||
editMode={editMode}
|
||||
isPanelChanged={isPanelChanged}
|
||||
isPanelVariablesOpened={openPanelVariables}
|
||||
onPanelChanged={setPanelChanged}
|
||||
onPanelClose={handlePanelClose}
|
||||
onEditModeToggle={handleToggleEditMode}
|
||||
onPanelsManagerOpen={handleOpenPanelsManager}
|
||||
onPanelVariablesClose={handleToggleOpenPanelVariables}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
22
db/P8PNL_PE_PANEL.sql
Normal file
22
db/P8PNL_PE_PANEL.sql
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Общие - Редактор панелей
|
||||
Панели
|
||||
*/
|
||||
create table P8PNL_PE_PANEL
|
||||
(
|
||||
RN number(17) not null, -- Рег. номер записи
|
||||
CODE varchar2(100) not null, -- Мнемокод
|
||||
NAME varchar2(2000) not null, -- Наименование
|
||||
AUTHOR varchar2(30) not null, -- Автор
|
||||
CH_DATE date not null, -- Дата последнего изменения
|
||||
READY number(1) default 0 not null, -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||
PBL number(1) default 0 not null, -- Флаг публичности (0 - нет, 1 - да)
|
||||
PANEL clob, -- Описание панели
|
||||
constraint C_P8PNL_PE_PANEL_RN_PK primary key (RN),
|
||||
constraint C_P8PNL_PE_PANEL_CODE_NB check (rtrim(CODE) is not null),
|
||||
constraint C_P8PNL_PE_PANEL_NAME_NB check (rtrim(NAME) is not null),
|
||||
constraint C_P8PNL_PE_PANEL_AUTHOR_FK foreign key (AUTHOR) references USERLIST (AUTHID) on delete cascade,
|
||||
constraint C_P8PNL_PE_PANEL_READY_VAL check (READY in (0, 1)),
|
||||
constraint C_P8PNL_PE_PANEL_PBL_VAL check (PBL in (0, 1)),
|
||||
constraint C_P8PNL_PE_PANEL_UN unique (CODE)
|
||||
);
|
||||
@ -6,6 +6,85 @@ create or replace package PKG_P8PANELS_PE as
|
||||
SCODE in varchar2, -- Мнемокод пользовательской процедуры
|
||||
COUT out clob -- Сериализованный список аргументов
|
||||
);
|
||||
|
||||
/* Получение списка панелей */
|
||||
procedure PANEL_LIST
|
||||
(
|
||||
COUT out clob -- Сериализованный список панелей
|
||||
);
|
||||
|
||||
/* Считывание базовой информации о панели */
|
||||
procedure PANEL_ATTRS_GET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NCHECK_ACCESS in number := 1, -- Признак проверки прав доступа (0 - нет, 1 - да)
|
||||
COUT out clob -- Базовой информация о панели
|
||||
);
|
||||
|
||||
/* Считывание базовой информации о панели (по мнемокоду) */
|
||||
procedure PANEL_ATTRS_GET_BY_CODE
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод панели
|
||||
NCHECK_ACCESS in number := 1, -- Признак проверки прав доступа (0 - нет, 1 - да)
|
||||
COUT out clob -- Базовой информация о панели
|
||||
);
|
||||
|
||||
/* Добавление панели */
|
||||
procedure PANEL_INSERT
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2, -- Наименование
|
||||
NRN out number -- Рег. номер добавленной панели
|
||||
);
|
||||
|
||||
/* Исправление панели */
|
||||
procedure PANEL_UPDATE
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2 -- Наименование
|
||||
);
|
||||
|
||||
/* Удаление панели */
|
||||
procedure PANEL_DELETE
|
||||
(
|
||||
NRN in number -- Рег. номер панели
|
||||
);
|
||||
|
||||
/* Установка признака "готовности" панели */
|
||||
procedure PANEL_READY_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||
);
|
||||
|
||||
/* Установка признака "публичности" панели */
|
||||
procedure PANEL_PBL_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||
);
|
||||
|
||||
/* Импорт панели из файла */
|
||||
procedure PANEL_IMPORT
|
||||
(
|
||||
CPANEL in clob, -- Описание импортируемой панели
|
||||
NRN out number -- Рег. номер импортированной панели
|
||||
);
|
||||
|
||||
/* Получение описания панели */
|
||||
procedure PANEL_DESC_GET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
COUT out clob -- Сериализованное описание панели
|
||||
);
|
||||
|
||||
/* Сохранение описания панели */
|
||||
procedure PANEL_DESC_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
CPANEL in clob -- Описание панели
|
||||
);
|
||||
|
||||
end PKG_P8PANELS_PE;
|
||||
/
|
||||
@ -18,110 +97,169 @@ create or replace package body PKG_P8PANELS_PE as
|
||||
COUT out clob -- Сериализованный список аргументов
|
||||
)
|
||||
is
|
||||
SRESP_ARG PKG_STD.TSTRING; -- Имя выходного визуализируемого параметра
|
||||
begin
|
||||
/* Обращаемся к процедуре */
|
||||
for C in (select T.RN NRN,
|
||||
T.PROCNAME SPROC_NAME,
|
||||
T.PROCTYPE NPROC_TYPE
|
||||
from USERPROCS T
|
||||
where T.CODE = SCODE)
|
||||
loop
|
||||
/* Проверим возможность использования ПП в качестве источника данных */
|
||||
if (C.NPROC_TYPE <> 0) then
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь тип "Хранимая процедура".',
|
||||
SCODE);
|
||||
end if;
|
||||
if (C.SPROC_NAME is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: не указана хранимая процедура.',
|
||||
SCODE);
|
||||
end if;
|
||||
/* Начинаем формирование XML */
|
||||
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
|
||||
/* Открываем корень */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => 'XDATA');
|
||||
/* Открываем описание процедуры */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => 'XUSERPROC');
|
||||
/* Обходим параметры */
|
||||
for P in (select T.PARAMTYPE NTYPE,
|
||||
T.PARAMNAME SNAME,
|
||||
T.NAME SCAPTION,
|
||||
T.DATATYPE NDATA_TYPE,
|
||||
case T.DATATYPE
|
||||
when 0 then
|
||||
'STR'
|
||||
when 1 then
|
||||
'NUMB'
|
||||
when 2 then
|
||||
'DATE'
|
||||
else
|
||||
null
|
||||
end SDATA_TYPE,
|
||||
T.MANDATORY NREQ,
|
||||
T.VISUALIZE NVISUALIZE
|
||||
from USERPROCSPARAMS T
|
||||
where T.PRN = C.NRN
|
||||
order by T.POSITION)
|
||||
loop
|
||||
/* В результирующий список забираем только входные поддерживаемого типа */
|
||||
if ((P.NTYPE = 0) and (P.SDATA_TYPE is not null)) then
|
||||
/* Открываем описание аргумента */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => 'arguments');
|
||||
/* Описываем аргумент */
|
||||
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => P.SNAME);
|
||||
PKG_XFAST.ATTR(SNAME => 'caption', SVALUE => P.SCAPTION);
|
||||
PKG_XFAST.ATTR(SNAME => 'dataType', SVALUE => P.SDATA_TYPE);
|
||||
PKG_XFAST.ATTR(SNAME => 'req',
|
||||
BVALUE => case P.NREQ
|
||||
when 1 then
|
||||
true
|
||||
else
|
||||
false
|
||||
end);
|
||||
/* Закрываем описание аргумента */
|
||||
PKG_XFAST.UP();
|
||||
end if;
|
||||
/* Если встретился визуализируемый параметр типа CLOB */
|
||||
if ((P.NVISUALIZE = 1) and (P.NDATA_TYPE = 4)) then
|
||||
if (SRESP_ARG is null) then
|
||||
SRESP_ARG := P.SNAME;
|
||||
else
|
||||
/* Это уже второй такой - ошибка */
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: имеет более одного выходного параметра типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
|
||||
SCODE);
|
||||
end if;
|
||||
end if;
|
||||
end loop;
|
||||
/* Сформируем описание хранимой процедуры */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => 'stored');
|
||||
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => C.SPROC_NAME);
|
||||
PKG_XFAST.ATTR(SNAME => 'respArg', SVALUE => SRESP_ARG);
|
||||
PKG_XFAST.UP();
|
||||
/* Закрываем описание процедуры */
|
||||
PKG_XFAST.UP();
|
||||
/* Закрываем описание корня */
|
||||
PKG_XFAST.UP();
|
||||
/* Сериализуем */
|
||||
COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
|
||||
/* Завершаем формирование XML */
|
||||
PKG_XFAST.EPILOGUE();
|
||||
end loop;
|
||||
/* Если ничего не нашли */
|
||||
if (COUT is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не определена.',
|
||||
COALESCE(SCODE, '<НЕ УКАЗАНА>'));
|
||||
end if;
|
||||
/* Проверим возможность использования ПП в качестве источника данных - должен быть выходной визуализируемый параметр типа CLOB */
|
||||
if (SRESP_ARG is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь выходной параметр типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
|
||||
SCODE);
|
||||
end if;
|
||||
/* Базовое описание пользовательской процедуры */
|
||||
PKG_P8PANELS_PE_BASE.USERPROCS_DESC(SCODE => SCODE, COUT => COUT);
|
||||
end USERPROCS_DESC;
|
||||
|
||||
/* Получение списка панелей */
|
||||
procedure PANEL_LIST
|
||||
(
|
||||
COUT out clob -- Сериализованный список панелей
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Базово сформируем список */
|
||||
COUT := PKG_P8PANELS_PE_BASE.PANEL_LIST_GET(SUSER => UTILIZER());
|
||||
end PANEL_LIST;
|
||||
|
||||
/* Считывание базовой информации о панели */
|
||||
procedure PANEL_ATTRS_GET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NCHECK_ACCESS in number := 1, -- Признак проверки прав доступа (0 - нет, 1 - да)
|
||||
COUT out clob -- Базовой информация о панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Если права необходимо проверить */
|
||||
if (NCHECK_ACCESS = 1) then
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER());
|
||||
end if;
|
||||
/* Считываем базовую информацию о панели */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ATTRS_GET(NRN => NRN, COUT => COUT);
|
||||
end PANEL_ATTRS_GET;
|
||||
|
||||
/* Считывание базовой информации о панели (по мнемокоду) */
|
||||
procedure PANEL_ATTRS_GET_BY_CODE
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод панели
|
||||
NCHECK_ACCESS in number := 1, -- Признак проверки прав доступа (0 - нет, 1 - да)
|
||||
COUT out clob -- Базовой информация о панели
|
||||
)
|
||||
is
|
||||
NRN PKG_STD.TREF; -- Рег. номер панели
|
||||
begin
|
||||
/* Считываем рег. номер панели */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_FIND_BY_CODE(NFLAG_SMART => 0, NFLAG_OPTION => 0, SCODE => SCODE, NRN => NRN);
|
||||
/* Если права необходимо проверить */
|
||||
if (NCHECK_ACCESS = 1) then
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER());
|
||||
end if;
|
||||
/* Считываем базовую информацию о панели */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ATTRS_GET(NRN => NRN, COUT => COUT);
|
||||
end PANEL_ATTRS_GET_BY_CODE;
|
||||
|
||||
/* Добавление панели */
|
||||
procedure PANEL_INSERT
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2, -- Наименование
|
||||
NRN out number -- Рег. номер добавленной панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Базовое добавление */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_INSERT(SCODE => SCODE, SNAME => SNAME, NRN => NRN);
|
||||
end PANEL_INSERT;
|
||||
|
||||
/* Исправление панели */
|
||||
procedure PANEL_UPDATE
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2 -- Наименование
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Базовое исправление */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_UPDATE(NRN => NRN, SCODE => SCODE, SNAME => SNAME);
|
||||
end PANEL_UPDATE;
|
||||
|
||||
/* Удаление панели */
|
||||
procedure PANEL_DELETE
|
||||
(
|
||||
NRN in number -- Рег. номер панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Базовое удаление */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_DELETE(NRN => NRN);
|
||||
end PANEL_DELETE;
|
||||
|
||||
/* Установка признака "готовности" панели */
|
||||
procedure PANEL_READY_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Базовая установка признака готовности */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_READY_SET(NRN => NRN, NREADY => NREADY);
|
||||
end PANEL_READY_SET;
|
||||
|
||||
/* Установка признака "публичности" панели */
|
||||
procedure PANEL_PBL_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Базовая установка признака публичности */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_PBL_SET(NRN => NRN, NPBL => NPBL);
|
||||
end PANEL_PBL_SET;
|
||||
|
||||
/* Импорт панели из файла */
|
||||
procedure PANEL_IMPORT
|
||||
(
|
||||
CPANEL in clob, -- Описание импортируемой панели
|
||||
NRN out number -- Рег. номер импортированной панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Базовое импортирование панели */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_IMPORT(CPANEL => CPANEL, NRN => NRN);
|
||||
end PANEL_IMPORT;
|
||||
|
||||
/* Получение описания панели */
|
||||
procedure PANEL_DESC_GET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
COUT out clob -- Сериализованное описание панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Получим описание панели */
|
||||
COUT := PKG_P8PANELS_PE_BASE.PANEL_DESC_GET(NRN => NRN);
|
||||
end PANEL_DESC_GET;
|
||||
|
||||
/* Сохранение описания панели */
|
||||
procedure PANEL_DESC_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
CPANEL in clob -- Описание панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим права доступа */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||
/* Сохраняем описание панели */
|
||||
PKG_P8PANELS_PE_BASE.PANEL_DESC_SET(NRN => NRN, CPANEL => CPANEL);
|
||||
end PANEL_DESC_SET;
|
||||
|
||||
end PKG_P8PANELS_PE;
|
||||
/
|
||||
|
||||
702
db/PKG_P8PANELS_PE_BASE.pck
Normal file
702
db/PKG_P8PANELS_PE_BASE.pck
Normal file
@ -0,0 +1,702 @@
|
||||
create or replace package PKG_P8PANELS_PE_BASE as
|
||||
|
||||
/* Описание пользовательской процедуры */
|
||||
procedure USERPROCS_DESC
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод пользовательской процедуры
|
||||
COUT out clob -- Сериализованный список аргументов
|
||||
);
|
||||
|
||||
/* Поиск рег. номера панели по мнемокоду */
|
||||
procedure PANEL_FIND_BY_CODE
|
||||
(
|
||||
NFLAG_SMART in number, -- Признак генерации исключения (0 - да, 1 - нет)
|
||||
NFLAG_OPTION in number, -- Признак генерации исключения для пустого sCODE (0 - да, 1 - нет)
|
||||
SCODE in varchar2, -- Мнемокод панели
|
||||
NRN out number -- Рег. номер панели
|
||||
);
|
||||
|
||||
/* Получение признака возможности изменения панели */
|
||||
function PANEL_ACCESS_SIGN_MODIFY
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
) return number; -- Признак возможности изменения панели (0 - нет, 1 - да)
|
||||
|
||||
|
||||
/* Проверка возможности изменения панели */
|
||||
procedure PANEL_ACCESS_MODIFY
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
);
|
||||
|
||||
/* Получение признака возможности просмотра панели */
|
||||
function PANEL_ACCESS_SIGN_VIEW
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
) return number; -- Признак возможности просмотра панели (0 - нет, 1 - да)
|
||||
|
||||
/* Проверка возможности просмотра панели */
|
||||
procedure PANEL_ACCESS_VIEW
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
);
|
||||
|
||||
/* Считывание базовой информации о панели */
|
||||
procedure PANEL_ATTRS_GET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
COUT out clob -- Базовой информация о панели
|
||||
);
|
||||
|
||||
/* Добавление панели */
|
||||
procedure PANEL_INSERT
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2, -- Наименование
|
||||
NREADY in number := 0, -- Признак готовности к использованию
|
||||
NPBL in number := 0, -- Публичность
|
||||
CPANEL in clob := null, -- Описание панели
|
||||
NRN out number -- Рег. номер добавленного запроса
|
||||
);
|
||||
|
||||
/* Исправление панели */
|
||||
procedure PANEL_UPDATE
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2 -- Наименование
|
||||
);
|
||||
|
||||
/* Удаление панели */
|
||||
procedure PANEL_DELETE
|
||||
(
|
||||
NRN in number -- Рег. номер панели
|
||||
);
|
||||
|
||||
/* Установка признака "готовности" панели */
|
||||
procedure PANEL_READY_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||
);
|
||||
|
||||
/* Установка признака "публичности" панели */
|
||||
procedure PANEL_PBL_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||
);
|
||||
|
||||
/* Загрузка описания панели */
|
||||
function PANEL_DESC_GET
|
||||
(
|
||||
NRN in number -- Рег. номер панели
|
||||
) return clob; -- XML-описание панели
|
||||
|
||||
/* Сохранение описания панели */
|
||||
procedure PANEL_DESC_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
CPANEL in clob -- Описание панели
|
||||
);
|
||||
|
||||
/* Импорт панели из файла */
|
||||
procedure PANEL_IMPORT
|
||||
(
|
||||
CPANEL in clob, -- Описание импортируемой панели
|
||||
NRN out number -- Рег. номер импортированной панели
|
||||
);
|
||||
|
||||
/* Формирование списка панелей */
|
||||
function PANEL_LIST_GET
|
||||
(
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
) return clob; -- Список панелей
|
||||
|
||||
end PKG_P8PANELS_PE_BASE;
|
||||
/
|
||||
create or replace package body PKG_P8PANELS_PE_BASE as
|
||||
|
||||
/* Константы - Теги для сериализации */
|
||||
STAG_DATA constant PKG_STD.TSTRING := 'XDATA'; -- Данные
|
||||
STAG_PANELS constant PKG_STD.TSTRING := 'XPANELS'; -- Панели
|
||||
STAG_PANEL constant PKG_STD.TSTRING := 'XPANEL'; -- Панель
|
||||
STAG_PANEL_INFO constant PKG_STD.TSTRING := 'XPANEL_INFO'; -- Информация о панели
|
||||
STAG_PANEL_ATTRS constant PKG_STD.TSTRING := 'XPANEL_ATTRS'; -- Атрибуты панели
|
||||
|
||||
/* Константы - Атрибуты для сериализации */
|
||||
SATTR_RN constant PKG_STD.TSTRING := 'rn'; -- Регистрационный номер
|
||||
SATTR_CODE constant PKG_STD.TSTRING := 'code'; -- Код
|
||||
SATTR_NAME constant PKG_STD.TSTRING := 'name'; -- Имя
|
||||
SATTR_AUTHOR constant PKG_STD.TSTRING := 'author'; -- Автор
|
||||
SATTR_CH_DATE constant PKG_STD.TSTRING := 'chDate'; -- Дата изменения
|
||||
SATTR_READY constant PKG_STD.TSTRING := 'ready'; -- Готовность к использованию
|
||||
SATTR_PBL constant PKG_STD.TSTRING := 'pbl'; -- Публичность
|
||||
SATTR_MODIFY constant PKG_STD.TSTRING := 'modify'; -- Изменяемость
|
||||
|
||||
/* Описание пользовательской процедуры */
|
||||
procedure USERPROCS_DESC
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод пользовательской процедуры
|
||||
COUT out clob -- Сериализованный список аргументов
|
||||
)
|
||||
is
|
||||
SRESP_ARG PKG_STD.TSTRING; -- Имя выходного визуализируемого параметра
|
||||
begin
|
||||
/* Обращаемся к процедуре */
|
||||
for C in (select T.RN NRN,
|
||||
T.PROCNAME SPROC_NAME,
|
||||
T.PROCTYPE NPROC_TYPE
|
||||
from USERPROCS T
|
||||
where T.CODE = SCODE)
|
||||
loop
|
||||
/* Проверим возможность использования ПП в качестве источника данных */
|
||||
if (C.NPROC_TYPE <> 0) then
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь тип "Хранимая процедура".',
|
||||
SCODE);
|
||||
end if;
|
||||
if (C.SPROC_NAME is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: не указана хранимая процедура.',
|
||||
SCODE);
|
||||
end if;
|
||||
/* Начинаем формирование XML */
|
||||
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
|
||||
/* Открываем корень */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => 'XDATA');
|
||||
/* Открываем описание процедуры */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => 'XUSERPROC');
|
||||
/* Обходим параметры */
|
||||
for P in (select T.PARAMTYPE NTYPE,
|
||||
T.PARAMNAME SNAME,
|
||||
T.NAME SCAPTION,
|
||||
T.DATATYPE NDATA_TYPE,
|
||||
case T.DATATYPE
|
||||
when 0 then
|
||||
'STR'
|
||||
when 1 then
|
||||
'NUMB'
|
||||
when 2 then
|
||||
'DATE'
|
||||
else
|
||||
null
|
||||
end SDATA_TYPE,
|
||||
T.MANDATORY NREQ,
|
||||
T.VISUALIZE NVISUALIZE
|
||||
from USERPROCSPARAMS T
|
||||
where T.PRN = C.NRN
|
||||
order by T.POSITION)
|
||||
loop
|
||||
/* В результирующий список забираем только входные поддерживаемого типа */
|
||||
if ((P.NTYPE = 0) and (P.SDATA_TYPE is not null)) then
|
||||
/* Открываем описание аргумента */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => 'arguments');
|
||||
/* Описываем аргумент */
|
||||
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => P.SNAME);
|
||||
PKG_XFAST.ATTR(SNAME => 'caption', SVALUE => P.SCAPTION);
|
||||
PKG_XFAST.ATTR(SNAME => 'dataType', SVALUE => P.SDATA_TYPE);
|
||||
PKG_XFAST.ATTR(SNAME => 'req',
|
||||
BVALUE => case P.NREQ
|
||||
when 1 then
|
||||
true
|
||||
else
|
||||
false
|
||||
end);
|
||||
/* Закрываем описание аргумента */
|
||||
PKG_XFAST.UP();
|
||||
end if;
|
||||
/* Если встретился визуализируемый параметр типа CLOB */
|
||||
if ((P.NVISUALIZE = 1) and (P.NDATA_TYPE = 4)) then
|
||||
if (SRESP_ARG is null) then
|
||||
SRESP_ARG := P.SNAME;
|
||||
else
|
||||
/* Это уже второй такой - ошибка */
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: имеет более одного выходного параметра типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
|
||||
SCODE);
|
||||
end if;
|
||||
end if;
|
||||
end loop;
|
||||
/* Сформируем описание хранимой процедуры */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => 'stored');
|
||||
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => C.SPROC_NAME);
|
||||
PKG_XFAST.ATTR(SNAME => 'respArg', SVALUE => SRESP_ARG);
|
||||
PKG_XFAST.UP();
|
||||
/* Закрываем описание процедуры */
|
||||
PKG_XFAST.UP();
|
||||
/* Закрываем описание корня */
|
||||
PKG_XFAST.UP();
|
||||
/* Сериализуем */
|
||||
COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
|
||||
/* Завершаем формирование XML */
|
||||
PKG_XFAST.EPILOGUE();
|
||||
end loop;
|
||||
/* Если ничего не нашли */
|
||||
if (COUT is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не определена.',
|
||||
COALESCE(SCODE, '<НЕ УКАЗАНА>'));
|
||||
end if;
|
||||
/* Проверим возможность использования ПП в качестве источника данных - должен быть выходной визуализируемый параметр типа CLOB */
|
||||
if (SRESP_ARG is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь выходной параметр типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
|
||||
SCODE);
|
||||
end if;
|
||||
end USERPROCS_DESC;
|
||||
|
||||
/* Поиск рег. номера панели по мнемокоду */
|
||||
procedure PANEL_FIND_BY_CODE
|
||||
(
|
||||
NFLAG_SMART in number, -- Признак генерации исключения (0 - да, 1 - нет)
|
||||
NFLAG_OPTION in number, -- Признак генерации исключения для пустого sCODE (0 - да, 1 - нет)
|
||||
SCODE in varchar2, -- Мнемокод панели
|
||||
NRN out number -- Рег. номер панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Если мнемокод не задан */
|
||||
if (RTRIM(SCODE) is null) then
|
||||
/* Генерация исключения */
|
||||
if (NFLAG_OPTION = 0) then
|
||||
P_EXCEPTION(NFLAG_SMART, 'Не задан мнемокод панели.');
|
||||
end if;
|
||||
/* Мнемокод задан */
|
||||
else
|
||||
/* Поиск записи */
|
||||
begin
|
||||
select RN into NRN from P8PNL_PE_PANEL where CODE = SCODE;
|
||||
exception
|
||||
when NO_DATA_FOUND then
|
||||
P_EXCEPTION(NFLAG_SMART, 'Панель "%s" не определена.', SCODE);
|
||||
end;
|
||||
end if;
|
||||
end PANEL_FIND_BY_CODE;
|
||||
|
||||
/* Считывание записи панели */
|
||||
function PANEL_GET
|
||||
(
|
||||
NRN in number -- Рег. номер панели
|
||||
) return P8PNL_PE_PANEL%rowtype -- Запись панели
|
||||
is
|
||||
RRES P8PNL_PE_PANEL%rowtype; -- Буфер для результата
|
||||
begin
|
||||
/* Считываем запись */
|
||||
select T.* into RRES from P8PNL_PE_PANEL T where T.RN = NRN;
|
||||
/* Возвращаем запись */
|
||||
return RRES;
|
||||
exception
|
||||
when NO_DATA_FOUND then
|
||||
PKG_MSG.RECORD_NOT_FOUND(NFLAG_SMART => 0, NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||
end PANEL_GET;
|
||||
|
||||
/* Получение признака возможности изменения панели */
|
||||
function PANEL_ACCESS_SIGN_MODIFY
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
) return number -- Признак возможности изменения панели (0 - нет, 1 - да)
|
||||
is
|
||||
RP P8PNL_PE_PANEL%rowtype; -- Проверяемая запись панели
|
||||
begin
|
||||
/* Читаем панель */
|
||||
RP := PANEL_GET(NRN => NRN);
|
||||
/* Менять можно только свою панель */
|
||||
if (RP.AUTHOR = SUSER) then
|
||||
return 1;
|
||||
end if;
|
||||
/* Проверки не пройдены - менять нельзя */
|
||||
return 0;
|
||||
end PANEL_ACCESS_SIGN_MODIFY;
|
||||
|
||||
/* Проверка возможности изменения панели */
|
||||
procedure PANEL_ACCESS_MODIFY
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Получим признак возможности изменения */
|
||||
if (PANEL_ACCESS_SIGN_MODIFY(NRN => NRN, SUSER => SUSER) = 0) then
|
||||
/* Менять нельзя */
|
||||
P_EXCEPTION(0, 'У Вас нет прав доступа для изменения панели.');
|
||||
end if;
|
||||
end PANEL_ACCESS_MODIFY;
|
||||
|
||||
/* Получение признака возможности просмотра панели */
|
||||
function PANEL_ACCESS_SIGN_VIEW
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
) return number -- Признак возможности просмотра панели (0 - нет, 1 - да)
|
||||
is
|
||||
RP P8PNL_PE_PANEL%rowtype; -- Проверяемая запись панели
|
||||
begin
|
||||
/* Читаем панель */
|
||||
RP := PANEL_GET(NRN => NRN);
|
||||
/* Смотреть можно только свою или публичную и готовую панель */
|
||||
if (((RP.PBL = 1) and (RP.READY = 1)) or (RP.AUTHOR = SUSER)) then
|
||||
return 1;
|
||||
end if;
|
||||
/* Проверки не пройдены - нельзя смотреть */
|
||||
return 0;
|
||||
end PANEL_ACCESS_SIGN_VIEW;
|
||||
|
||||
/* Проверка возможности просмотра панели */
|
||||
procedure PANEL_ACCESS_VIEW
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Получим признак возможности просмотра */
|
||||
if (PANEL_ACCESS_SIGN_VIEW(NRN => NRN, SUSER => SUSER) = 0) then
|
||||
/* Смотреть нельзя */
|
||||
P_EXCEPTION(0, 'У Вас нет прав доступа для просмотра панели.');
|
||||
end if;
|
||||
end PANEL_ACCESS_VIEW;
|
||||
|
||||
/* Проверка атрибутов панели */
|
||||
procedure PANEL_CHECK
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2 -- Наименование
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Мнемокод должен быть задан */
|
||||
if (SCODE is null) then
|
||||
P_EXCEPTION(0, 'Не задан мнемокод панели.');
|
||||
end if;
|
||||
/* Наименование должно быть задано */
|
||||
if (SNAME is null) then
|
||||
P_EXCEPTION(0, 'Не задано наименование панели.');
|
||||
end if;
|
||||
end PANEL_CHECK;
|
||||
|
||||
/* Синхронизация даты изменения панели с текущим временем */
|
||||
procedure PANEL_CH_DATE_SYNC
|
||||
(
|
||||
NRN in number -- Рег. номер панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Установим текущую дату изменения */
|
||||
update P8PNL_PE_PANEL T set T.CH_DATE = sysdate where T.RN = NRN;
|
||||
/* Контроль изменения данных */
|
||||
if (sql%notfound) then
|
||||
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||
end if;
|
||||
end PANEL_CH_DATE_SYNC;
|
||||
|
||||
/* Считывание базовой информации о панели */
|
||||
procedure PANEL_ATTRS_GET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
COUT out clob -- Базовой информация о панели
|
||||
)
|
||||
is
|
||||
RP P8PNL_PE_PANEL%rowtype; -- Считываемая запись панели
|
||||
begin
|
||||
/* Читаем панель */
|
||||
RP := PANEL_GET(NRN => NRN);
|
||||
/* Начинаем формирование XML */
|
||||
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
|
||||
/* Открываем корень */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
|
||||
/* Открываем информацию о панели */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => STAG_PANEL_ATTRS);
|
||||
/* Добавляем описание панели */
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_RN, NVALUE => RP.RN);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_CODE, SVALUE => RP.CODE);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RP.NAME);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_READY, NVALUE => RP.READY);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_PBL, NVALUE => RP.PBL);
|
||||
/* Закрываем информацию о панели */
|
||||
PKG_XFAST.UP();
|
||||
/* Закрываем корень */
|
||||
PKG_XFAST.UP();
|
||||
/* Сериализуем */
|
||||
COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
|
||||
/* Завершаем формирование XML */
|
||||
PKG_XFAST.EPILOGUE();
|
||||
exception
|
||||
when others then
|
||||
/* Завершаем формирование XML */
|
||||
PKG_XFAST.EPILOGUE();
|
||||
/* Вернем ошибку */
|
||||
PKG_STATE.DIAGNOSTICS_STACKED();
|
||||
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
||||
end PANEL_ATTRS_GET;
|
||||
|
||||
/* Добавление панели */
|
||||
procedure PANEL_INSERT
|
||||
(
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2, -- Наименование
|
||||
NREADY in number := 0, -- Признак готовности к использованию
|
||||
NPBL in number := 0, -- Публичность
|
||||
CPANEL in clob := null, -- Описание панели
|
||||
NRN out number -- Рег. номер добавленного запроса
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим параметры */
|
||||
PANEL_CHECK(SCODE => SCODE, SNAME => SNAME);
|
||||
/* Формируем рег. номер */
|
||||
NRN := GEN_ID();
|
||||
/* Добавляем данные */
|
||||
insert into P8PNL_PE_PANEL
|
||||
(RN, CODE, name, AUTHOR, CH_DATE, READY, PBL, PANEL)
|
||||
values
|
||||
(NRN, SCODE, SNAME, UTILIZER(), sysdate, NREADY, NPBL, CPANEL);
|
||||
end PANEL_INSERT;
|
||||
|
||||
/* Исправление панели */
|
||||
procedure PANEL_UPDATE
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
SCODE in varchar2, -- Мнемокод
|
||||
SNAME in varchar2 -- Наименование
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим параметры */
|
||||
PANEL_CHECK(SCODE => SCODE, SNAME => SNAME);
|
||||
/* Изменяем данные */
|
||||
update P8PNL_PE_PANEL T
|
||||
set T.CODE = SCODE,
|
||||
T.NAME = SNAME
|
||||
where T.RN = NRN;
|
||||
/* Контроль изменения данных */
|
||||
if (sql%notfound) then
|
||||
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||
end if;
|
||||
/* Обновим дату изменения панели */
|
||||
PANEL_CH_DATE_SYNC(NRN => NRN);
|
||||
end PANEL_UPDATE;
|
||||
|
||||
/* Удаление панели */
|
||||
procedure PANEL_DELETE
|
||||
(
|
||||
NRN in number -- Рег. номер панели
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Удаляем запись */
|
||||
delete from P8PNL_PE_PANEL T where T.RN = NRN;
|
||||
/* Контроль изменения данных */
|
||||
if (sql%notfound) then
|
||||
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||
end if;
|
||||
end PANEL_DELETE;
|
||||
|
||||
/* Установка признака "готовности" панели */
|
||||
procedure PANEL_READY_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим параметры */
|
||||
if (NREADY is null) then
|
||||
P_EXCEPTION(0,
|
||||
'Не задано значение признака готовности панели к использованию.');
|
||||
end if;
|
||||
if (NREADY not in (0, 1)) then
|
||||
P_EXCEPTION(0,
|
||||
'Значение признака готовности панели к использованию задано некорректно (ожидалось 0 или 1).');
|
||||
end if;
|
||||
/* Установим флаг готовности к использованию */
|
||||
update P8PNL_PE_PANEL T set T.READY = NREADY where T.RN = NRN;
|
||||
/* Контроль изменения данных */
|
||||
if (sql%notfound) then
|
||||
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||
end if;
|
||||
/* Обновим дату изменения панели */
|
||||
PANEL_CH_DATE_SYNC(NRN => NRN);
|
||||
end PANEL_READY_SET;
|
||||
|
||||
/* Установка признака "публичности" панели */
|
||||
procedure PANEL_PBL_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||
)
|
||||
is
|
||||
begin
|
||||
/* Проверим параметры */
|
||||
if (NPBL is null) then
|
||||
P_EXCEPTION(0, 'Не задано значение признака публичности запроса.');
|
||||
end if;
|
||||
if (NPBL not in (0, 1)) then
|
||||
P_EXCEPTION(0,
|
||||
'Значение признака публичноти запроса задано некорректно (ожидалось 0 или 1).');
|
||||
end if;
|
||||
/* Установим флаг публичности */
|
||||
update P8PNL_PE_PANEL T set T.PBL = NPBL where T.RN = NRN;
|
||||
/* Контроль изменения данных */
|
||||
if (sql%notfound) then
|
||||
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||
end if;
|
||||
/* Обновим дату изменения панели */
|
||||
PANEL_CH_DATE_SYNC(NRN => NRN);
|
||||
end PANEL_PBL_SET;
|
||||
|
||||
/* Загрузка описания панели */
|
||||
function PANEL_DESC_GET
|
||||
(
|
||||
NRN in number -- Рег. номер панели
|
||||
) return clob -- XML-описание панели
|
||||
is
|
||||
RP P8PNL_PE_PANEL%rowtype; -- Запись панели
|
||||
begin
|
||||
/* Читаем панель */
|
||||
RP := PANEL_GET(NRN => NRN);
|
||||
/* Возвращаем результат */
|
||||
return RP.PANEL;
|
||||
end PANEL_DESC_GET;
|
||||
|
||||
/* Сохранение описания панели */
|
||||
procedure PANEL_DESC_SET
|
||||
(
|
||||
NRN in number, -- Рег. номер панели
|
||||
CPANEL in clob -- Описание панели
|
||||
)
|
||||
is
|
||||
CPANEL_DATA clob; -- Данные описания панели
|
||||
begin
|
||||
/* Считываем данные описания панели */
|
||||
CPANEL_DATA := BLOB2CLOB(LBDATA => BASE64_DECODE(LCSRCE => CPANEL), SCHARSET => PKG_CHARSET.CHARSET_UTF_());
|
||||
/* Установим данные панели */
|
||||
update P8PNL_PE_PANEL T set T.PANEL = CPANEL_DATA where T.RN = NRN;
|
||||
/* Контроль изменения данных */
|
||||
if (sql%notfound) then
|
||||
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||
end if;
|
||||
/* Обновим дату изменения панели */
|
||||
PANEL_CH_DATE_SYNC(NRN => NRN);
|
||||
exception
|
||||
when others then
|
||||
/* Завершаем формирование XML */
|
||||
PKG_XFAST.EPILOGUE();
|
||||
/* Вернем ошибку */
|
||||
PKG_STATE.DIAGNOSTICS_STACKED();
|
||||
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
||||
end PANEL_DESC_SET;
|
||||
|
||||
/* Импорт панели */
|
||||
procedure PANEL_IMPORT
|
||||
(
|
||||
CPANEL in clob, -- Описание импортируемой панели
|
||||
NRN out number -- Рег. номер импортированной панели
|
||||
)
|
||||
is
|
||||
RPARSEDOC PKG_XPATH.TDOCUMENT; -- Документ XML
|
||||
RROOT PKG_XPATH.TNODE; -- Буфер узла документа
|
||||
RP P8PNL_PE_PANEL%rowtype; -- Запись панели
|
||||
CPANEL_DATA clob; -- Данные описания панели
|
||||
begin
|
||||
/* Считываем данные описания панели */
|
||||
CPANEL_DATA := BLOB2CLOB(LBDATA => BASE64_DECODE(LCSRCE => CPANEL), SCHARSET => PKG_CHARSET.CHARSET_UTF_());
|
||||
/* Преобразовываем CLOB */
|
||||
RPARSEDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CPANEL_DATA);
|
||||
/* Считываем корневой узел */
|
||||
RROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => RPARSEDOC);
|
||||
/* Считываем мнемокод */
|
||||
RP.CODE := PKG_XPATH.VALUE(RNODE => RROOT, SPATTERN => STAG_PANEL || '/' || STAG_PANEL_INFO || '/' || SATTR_CODE);
|
||||
/* Считываем наименование */
|
||||
RP.NAME := PKG_XPATH.VALUE(RNODE => RROOT, SPATTERN => STAG_PANEL || '/' || STAG_PANEL_INFO || '/' || SATTR_NAME);
|
||||
/* Если в описании панели не содержится основной информации о панели */
|
||||
if ((RP.CODE is null) or (RP.NAME is null)) then
|
||||
P_EXCEPTION(0,
|
||||
'В описании импортируемой панели отсутствует информация о мнемокоде или наименовании.');
|
||||
end if;
|
||||
/* Считываем готовность к использованию */
|
||||
RP.READY := PKG_XPATH.VALUE_NUM(RNODE => RROOT,
|
||||
SPATTERN => STAG_PANEL || '/' || STAG_PANEL_INFO || '/' || SATTR_READY);
|
||||
/* Считываем публичность */
|
||||
RP.PBL := PKG_XPATH.VALUE_NUM(RNODE => RROOT, SPATTERN => STAG_PANEL || '/' || STAG_PANEL_INFO || '/' || SATTR_PBL);
|
||||
/* Удаляем лишнюю информацию из описания панели и устанавливаем его */
|
||||
RP.PANEL := REGEXP_REPLACE(CPANEL_DATA, '<' || STAG_PANEL_INFO || '>.*?</' || STAG_PANEL_INFO || '>', '');
|
||||
/* Добавляем панель */
|
||||
PANEL_INSERT(SCODE => RP.CODE,
|
||||
SNAME => RP.NAME,
|
||||
NREADY => COALESCE(RP.READY, 0),
|
||||
NPBL => COALESCE(RP.PBL, 0),
|
||||
CPANEL => RP.PANEL,
|
||||
NRN => NRN);
|
||||
end PANEL_IMPORT;
|
||||
|
||||
/* Формирование списка панелей */
|
||||
function PANEL_LIST_GET
|
||||
(
|
||||
SUSER in varchar2 -- Имя пользователя
|
||||
) return clob -- Список панелей
|
||||
is
|
||||
CRES clob; -- Буфер для сериализации
|
||||
begin
|
||||
/* Начинаем формирование XML */
|
||||
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true);
|
||||
/* Открываем корень */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
|
||||
/* Открываем список запросов */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => STAG_PANELS);
|
||||
/* Обходим запросы - данного пользователя и публичные */
|
||||
for C in (select T.RN NRN,
|
||||
T.CODE SCODE,
|
||||
T.NAME SNAME,
|
||||
UL.NAME SAUTHOR,
|
||||
TO_CHAR(T.CH_DATE, 'dd.mm.yyyy hh24:mi:ss') SCH_DATE,
|
||||
T.READY NREADY,
|
||||
T.PBL NPBL,
|
||||
PANEL_ACCESS_SIGN_MODIFY(T.RN, SUSER) NMODIFY
|
||||
from P8PNL_PE_PANEL T,
|
||||
USERLIST UL
|
||||
where T.AUTHOR = UL.AUTHID
|
||||
and PANEL_ACCESS_SIGN_VIEW(T.RN, SUSER) = 1
|
||||
order by T.CODE)
|
||||
loop
|
||||
/* Открываем описание панели */
|
||||
PKG_XFAST.DOWN_NODE(SNAME => STAG_PANEL);
|
||||
/* Панель */
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_RN, NVALUE => C.NRN);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_CODE, SVALUE => C.SCODE);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => C.SNAME);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_AUTHOR, SVALUE => C.SAUTHOR);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_CH_DATE, SVALUE => C.SCH_DATE);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_READY, NVALUE => C.NREADY);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_PBL, NVALUE => C.NPBL);
|
||||
PKG_XFAST.ATTR(SNAME => SATTR_MODIFY, NVALUE => C.NMODIFY);
|
||||
/* Закрываем описание панели */
|
||||
PKG_XFAST.UP();
|
||||
end loop;
|
||||
/* Закрываем список панелей */
|
||||
PKG_XFAST.UP();
|
||||
/* Закрываем корень */
|
||||
PKG_XFAST.UP();
|
||||
/* Сериализуем */
|
||||
CRES := PKG_XFAST.SERIALIZE_TO_CLOB();
|
||||
/* Завершаем формирование XML */
|
||||
PKG_XFAST.EPILOGUE();
|
||||
/* Возвращаем результат */
|
||||
return CRES;
|
||||
exception
|
||||
when others then
|
||||
/* Завершаем формирование XML */
|
||||
PKG_XFAST.EPILOGUE();
|
||||
/* Вернем ошибку */
|
||||
PKG_STATE.DIAGNOSTICS_STACKED();
|
||||
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
||||
end PANEL_LIST_GET;
|
||||
|
||||
end PKG_P8PANELS_PE_BASE;
|
||||
/
|
||||
Loading…
x
Reference in New Issue
Block a user