ЦИТК-978 - Доработка панели "Редактор панелей"

This commit is contained in:
Dollerino 2025-11-12 14:12:11 +03:00
parent b7f9daa258
commit b49e0e9a80
56 changed files with 6046 additions and 580 deletions

View 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 };

View 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 };

View 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
};

View 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 };

View 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 };

View 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 };

View 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 };

View 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 };

View File

@ -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 };

View 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 };

View 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 };

View 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 };

View 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 };

View 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 };

View 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 };

View File

@ -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
};
//----------------

View File

@ -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>

View File

@ -67,6 +67,7 @@ const P8P_DATA_SOURCE_SHAPE = PropTypes.shape({
const P8P_DATA_SOURCE_INITIAL = {
type: "",
userProc: "",
query: "",
stored: "",
respArg: "",
arguments: []

View File

@ -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>
</>
);
};

View File

@ -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 };

View 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 };

View File

@ -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
};
//----------------

View File

@ -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>
);
};

View File

@ -23,8 +23,17 @@ const STYLES = {
})
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Проверка корректности наименования элемента формы
const isElementNameCorrect = elementName => {
return new RegExp(/^[\w\d_]*$/).test(elementName);
};
//----------------
//Интерфейс модуля
//----------------
export { STYLES };
export { STYLES, isElementNameCorrect };

View File

@ -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>
);

View File

@ -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
};

View File

@ -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
};

View File

@ -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,

View 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 };

View File

@ -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
};

View File

@ -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
};
//----------------

View File

@ -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 };

View File

@ -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 };

View File

@ -47,3 +47,6 @@ export const ORIENTATION = {
H: "H",
V: "v"
};
//Доступность сохранения настроек элемента
export const isItemOkDisabled = item => (!item.name ? true : false);

View File

@ -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
};

View File

@ -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"}>

View 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 };

View 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 };

View File

@ -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
};

View File

@ -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
};
//----------------

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View 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 };

View 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 };

View File

@ -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
};

View File

@ -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
};
//----------------

View File

@ -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
};
//----------------

View 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);
};

View 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 };

View 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 };

View 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 };

View File

@ -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
View 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)
);

View File

@ -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
View 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;
/