forked from CITKParus/P8-Panels
Compare commits
21 Commits
a81797f5ac
...
2672bcd8be
Author | SHA1 | Date | |
---|---|---|---|
|
2672bcd8be | ||
c6688bd451 | |||
|
fa71c76a7d | ||
|
418e77bf74 | ||
|
e4683cf991 | ||
|
416eae7d88 | ||
|
fbbbd7c247 | ||
|
f4c665a74b | ||
|
a639c6371c | ||
|
4f2a1d4034 | ||
|
5a08fdf605 | ||
|
be351f7920 | ||
|
4d59203604 | ||
|
c734b62ba0 | ||
|
939efc0733 | ||
|
b2888efd62 | ||
|
b1b1288e60 | ||
|
50e3970c93 | ||
|
f418951695 | ||
|
fe02011a25 | ||
|
6ebbd0f08f |
72
README.md
72
README.md
@ -516,7 +516,7 @@ c:\inetpub\p8web20\WebClient\Modules\P8-Panels>npm run build
|
|||||||
- `isRespErr` - функция, проверка результата исполнения серверного объекта на наличие ошибок
|
- `isRespErr` - функция, проверка результата исполнения серверного объекта на наличие ошибок
|
||||||
- `getRespErrMessage` - функция, получение ошибки исполнения серверного объекта
|
- `getRespErrMessage` - функция, получение ошибки исполнения серверного объекта
|
||||||
- `getRespPayload` - функция, получение выходных значений, полученных после успешного исполнения
|
- `getRespPayload` - функция, получение выходных значений, полученных после успешного исполнения
|
||||||
- `executeStored` -функция, асинхронное исполнение хранимой процедуры/функции БД Системы
|
- `executeStored` - функция, асинхронное исполнение хранимой процедуры/функции БД Системы
|
||||||
- `getConfig` - функция, асинхронное считывание параметров конфигурации, определённых в "p8panels.config" (возвращает их JSON-представление)
|
- `getConfig` - функция, асинхронное считывание параметров конфигурации, определённых в "p8panels.config" (возвращает их JSON-представление)
|
||||||
|
|
||||||
При формировании ответов, функции, получающие данные с сервера, возвращают типовые значения:
|
При формировании ответов, функции, получающие данные с сервера, возвращают типовые значения:
|
||||||
@ -584,7 +584,8 @@ c:\inetpub\p8web20\WebClient\Modules\P8-Panels>npm run build
|
|||||||
throwError = true,
|
throwError = true,
|
||||||
showErrorMessage = true,
|
showErrorMessage = true,
|
||||||
fullResponse = false,
|
fullResponse = false,
|
||||||
spreadOutArguments = true
|
spreadOutArguments = true,
|
||||||
|
signal = null
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -597,7 +598,8 @@ c:\inetpub\p8web20\WebClient\Modules\P8-Panels>npm run build
|
|||||||
`throwError` - необязательный, логический, признак генерации исключения, если `false` - возвращает ошибку в типовом формате\
|
`throwError` - необязательный, логический, признак генерации исключения, если `false` - возвращает ошибку в типовом формате\
|
||||||
`showErrorMessage` - необязательный, логический, признак отображения типового клиентского сообщение об ошибке, в случае её возникновения (только если `throwError = true`)\
|
`showErrorMessage` - необязательный, логический, признак отображения типового клиентского сообщение об ошибке, в случае её возникновения (только если `throwError = true`)\
|
||||||
`fullResponse` - необязательный, логический, признак возврата полного типового ответа сервера, если `false` - возвращается только содержимое `XPAYLOAD`\
|
`fullResponse` - необязательный, логический, признак возврата полного типового ответа сервера, если `false` - возвращается только содержимое `XPAYLOAD`\
|
||||||
`spreadOutArguments` - необязательный, логический, признак "разделения" значений выходных параметров исполняемого обхекта (игнорируется при наличии `respArg`), если `true` - `XPAYLOAD` будет содержать ответ в виде `{"ВЫХОДНОЙ_ПАРАМЕТР1": "ЗНАЧЕНИЕ", "ВЫХОДНОЙ_ПАРАМЕТР2": "ЗНАЧЕНИЕ", ...}`, если `false` - `XPAYLOAD` будет содержать ответ в виде `{XOUT_ARGUMENTS: [{SNAME: "ВЫХОДНОЙ_ПАРАМЕТР1", VALUE: "ЗНАЧЕНИЕ"}, {SNAME: "ВЫХОДНОЙ_ПАРАМЕТР2", VALUE: "ЗНАЧЕНИЕ"}, ...]}`
|
`spreadOutArguments` - необязательный, логический, признак "разделения" значений выходных параметров исполняемого обхекта (игнорируется при наличии `respArg`), если `true` - `XPAYLOAD` будет содержать ответ в виде `{"ВЫХОДНОЙ_ПАРАМЕТР1": "ЗНАЧЕНИЕ", "ВЫХОДНОЙ_ПАРАМЕТР2": "ЗНАЧЕНИЕ", ...}`, если `false` - `XPAYLOAD` будет содержать ответ в виде `{XOUT_ARGUMENTS: [{SNAME: "ВЫХОДНОЙ_ПАРАМЕТР1", VALUE: "ЗНАЧЕНИЕ"}, {SNAME: "ВЫХОДНОЙ_ПАРАМЕТР2", VALUE: "ЗНАЧЕНИЕ"}, ...]}`\
|
||||||
|
`signal` - необязательный, объект, экземпляр `AbortSignal` (например, `AbortController.signal`) для управления прерыванием выполнения запроса
|
||||||
|
|
||||||
**Результат:** объект с данными, размещёнными в `XPAYLOAD` ответа сервера (если `fullResponse = false`) или полный типовой ответ (описан выше).
|
**Результат:** объект с данными, размещёнными в `XPAYLOAD` ответа сервера (если `fullResponse = false`) или полный типовой ответ (описан выше).
|
||||||
|
|
||||||
@ -1265,7 +1267,7 @@ const Messages = ({ title }) => {
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
###### `undefined showMsg(message)`
|
###### `undefined showLoader(message)`
|
||||||
|
|
||||||
Отображает модальный индикатор процесса с указанным сообщением.
|
Отображает модальный индикатор процесса с указанным сообщением.
|
||||||
|
|
||||||
@ -1332,14 +1334,14 @@ const Loader = ({ title }) => {
|
|||||||
- состоят из значительного числа интерфейсных примитивов
|
- состоят из значительного числа интерфейсных примитивов
|
||||||
- имеют специальный API на стороне сервера БД Системы для управления их содержимым
|
- имеют специальный API на стороне сервера БД Системы для управления их содержимым
|
||||||
|
|
||||||
Необходимо понимать, что с одной стороны, наличие серверного API в БД значительно упрощает взаимодействие с компонентом, с другой стороны - ограничивает возможности его примерения только теми прикладными задачами и функциональными возможностями, которые заложены в него. При этом "примитивы" HTML и MUI, хоть и сложнее в применении, но позволяют "собирать" практически любые интерфейсные решения на вкус разработчика.
|
Необходимо понимать, что с одной стороны, наличие серверного API в БД значительно упрощает взаимодействие с компонентом, с другой стороны - ограничивает возможности его применения только теми прикладными задачами и функциональными возможностями, которые заложены в него. При этом "примитивы" HTML и MUI, хоть и сложнее в применении, но позволяют "собирать" практически любые интерфейсные решения на вкус разработчика.
|
||||||
|
|
||||||
##### Таблица данных "P8PDataGrid"
|
##### Таблица данных "P8PDataGrid"
|
||||||
|
|
||||||
Предназначена для формирования табличных представлений данных с поддержкой:
|
Предназначена для формирования табличных представлений данных с поддержкой:
|
||||||
|
|
||||||
- постраничного вывода данных
|
- постраничного вывода данных
|
||||||
- сортировки и отбора данных по колонкам на строне сервера БД
|
- сортировки и отбора данных по колонкам на стороне сервера БД
|
||||||
- сложных заголовков с возможностью отображения/сокрытия уровней
|
- сложных заголовков с возможностью отображения/сокрытия уровней
|
||||||
- разворачивающихся строк (accordion)
|
- разворачивающихся строк (accordion)
|
||||||
- группировки строк с возможностью отображения/сокрытия содержимого группы
|
- группировки строк с возможностью отображения/сокрытия содержимого группы
|
||||||
@ -1366,6 +1368,7 @@ const MyPanel = () => {
|
|||||||
|
|
||||||
**Свойства**
|
**Свойства**
|
||||||
|
|
||||||
|
`style` - необязательный, объект, если задан, то будет применён в качестве атрибута `style` коневого контейнера (`div`) компонента\
|
||||||
`columnsDef` - необязательный, массив, описание колонок таблицы, содержит объекты вида `{caption: <ЗАГОЛОВОК_КОЛОНКИ>, dataType: <ТИП_ДАННЫХ - NUMB|STR|DATE>, filter: <ПРИЗНАК_ВОЗМОЖНОСТИ_ОТБОРА - true|false>, hint: <ОПИСАНИЕ_КОЛОНКИ_МОЖЕТ_СОДЕРЖАТЬ_HTML_РАЗМЕТКУ>, name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, order: <ПРИЗНАК_ВОЗМОЖНОСТИ_СОРТИРОВКИ - true|false>, values: <МАССИВ_ПРЕДОПРЕДЕЛЁННЫХ_ЗНАЧЕНИЙ>, visible: <ПРИЗНАК_ВИДИМОСТИ_КОЛОНКИ - true|false>,expandable: <ПРИЗНАК_РАЗВОРАЧИВАЕМОСТИ_ГРУППОВОГО_ЗАГОЛОВКА - true|false>, expanded: <ПРИЗНАК_РАЗВЕРНУТОСТИ_ГРУППОВОГО_ЗАГОЛОВКА - true|false>, parent: <НАИМЕНОВАНИЕ_РОДИТЕЛЬСКОЙ_КОЛОНКИ_В_ГРУППОВОМ_ЗАГОЛОВКЕ>, width: <ШИРИНА_КОЛОНКИ>}`\
|
`columnsDef` - необязательный, массив, описание колонок таблицы, содержит объекты вида `{caption: <ЗАГОЛОВОК_КОЛОНКИ>, dataType: <ТИП_ДАННЫХ - NUMB|STR|DATE>, filter: <ПРИЗНАК_ВОЗМОЖНОСТИ_ОТБОРА - true|false>, hint: <ОПИСАНИЕ_КОЛОНКИ_МОЖЕТ_СОДЕРЖАТЬ_HTML_РАЗМЕТКУ>, name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, order: <ПРИЗНАК_ВОЗМОЖНОСТИ_СОРТИРОВКИ - true|false>, values: <МАССИВ_ПРЕДОПРЕДЕЛЁННЫХ_ЗНАЧЕНИЙ>, visible: <ПРИЗНАК_ВИДИМОСТИ_КОЛОНКИ - true|false>,expandable: <ПРИЗНАК_РАЗВОРАЧИВАЕМОСТИ_ГРУППОВОГО_ЗАГОЛОВКА - true|false>, expanded: <ПРИЗНАК_РАЗВЕРНУТОСТИ_ГРУППОВОГО_ЗАГОЛОВКА - true|false>, parent: <НАИМЕНОВАНИЕ_РОДИТЕЛЬСКОЙ_КОЛОНКИ_В_ГРУППОВОМ_ЗАГОЛОВКЕ>, width: <ШИРИНА_КОЛОНКИ>}`\
|
||||||
`filtersInitial` - необязательныей, массив, начальное состояние фильтров таблицы, содержит объекты вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, from: <НАЧАЛО_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>, to: <ОКОНЧАНИЕ_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>}`\
|
`filtersInitial` - необязательныей, массив, начальное состояние фильтров таблицы, содержит объекты вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, from: <НАЧАЛО_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>, to: <ОКОНЧАНИЕ_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>}`\
|
||||||
`groups` - необязательный, массив групп данных, содержит объекты вида `{name: <ИМЯ_ГРУППЫ>, caption: <ЗАГОЛОВОК_ГРУППЫ>, expandable: <ПРИЗНАК_РАЗВОРАЧИВАЕМОСТИ_ГРУППЫ - true|false>, expanded: <ПРИЗНАК_РАЗВЕРНУТОСТИ_ГРУППЫ - true|false>}`\
|
`groups` - необязательный, массив групп данных, содержит объекты вида `{name: <ИМЯ_ГРУППЫ>, caption: <ЗАГОЛОВОК_ГРУППЫ>, expandable: <ПРИЗНАК_РАЗВОРАЧИВАЕМОСТИ_ГРУППЫ - true|false>, expanded: <ПРИЗНАК_РАЗВЕРНУТОСТИ_ГРУППЫ - true|false>}`\
|
||||||
@ -1975,7 +1978,7 @@ const Chart = ({ title }) => {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/data_grid.js" данного репозитория соответственно.
|
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/chart.js" данного репозитория соответственно.
|
||||||
|
|
||||||
##### Диаграмма ганта "P8PGantt"
|
##### Диаграмма ганта "P8PGantt"
|
||||||
|
|
||||||
@ -2938,6 +2941,61 @@ export { Cyclogram };
|
|||||||
|
|
||||||
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/cyclogram.js" данного репозитория соответственно.
|
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/cyclogram.js" данного репозитория соответственно.
|
||||||
|
|
||||||
|
##### Индикатор "P8PIndicator"
|
||||||
|
|
||||||
|
Компонент предназначен для отображения данных в виде индикатора. Поддерживается:
|
||||||
|
|
||||||
|
- Цветовая индикация предопределёнными цветами в зависимости от состояния (не определено, позитивное, негативное, пограничное)
|
||||||
|
- Цветовая индикация пользовательскими цветами
|
||||||
|
- Обработка нажатий
|
||||||
|
- Отображение иконки
|
||||||
|
- Упрвление внешним видом (парение, рамка)
|
||||||
|
- Интерактивные подсказки
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Подключение**
|
||||||
|
|
||||||
|
Клиентская часть индикатора реализована в компоненте `P8PIndicator`, объявленном в "app/components/p8p_indicator". Для использования компонента на панели его необходимо импортировать:
|
||||||
|
|
||||||
|
```
|
||||||
|
import { P8PIndicator } from "../../components/p8p_indicator";
|
||||||
|
|
||||||
|
const MyPanel = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<P8PIndicator .../>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Свойства**
|
||||||
|
|
||||||
|
`caption` - обязательный, строка, подпись индикатора\
|
||||||
|
`value` - обязательный, строка, значение индикатора\
|
||||||
|
`icon` - необязательный, строка, код иконки индикатора из символов шрифта [Google Material Icons](https://fonts.google.com/icons?icon.set=Material+Icons) (по умолчанию - не указана, если указана - отображается в левой части области индикатора)\
|
||||||
|
`state` - необязательный, строка, состояние индикатора, принимает значения `UNDEFINED|OK|ERR|WARN` (по умолчанию - `UNDEFINED`, см. константу `P8P_INDICATOR_STATE` в исходном коде компонента), определяет цвет заливки индикатора, если не указаны пользовательские цвета (см. ниже свойства `backgroundColor`и`color`)\
|
||||||
|
`square` - необязательный, логический, определяет необходимость скругления углов области индикатора (по умолчанию - `false`)\
|
||||||
|
`elevation` - необязательный, число, высота парения индикатора (по умолчанию - 3, используется только при `variant = 'elevation'`)\
|
||||||
|
`variant` - необязательный, строка, вариант исполнения, принимает значения `elevation|outlined` (по умолчанию - `elevation`, см. константу `P8P_INDICATOR_VARIANT` в исходном коде компонента), определяет внешний вид индикатора - парящая область или область с рамкой\
|
||||||
|
`hint` - необязательный, строка, текст подсказки для индикатора (если указан - слева от значения индикатора формируется кнопка открытия диалога с текстом подсказки, поддерживается HTML-форматирование)\
|
||||||
|
`onClick` - необязательный, функция, будет вызвана при нажатии пользователем на индикатор (если указана - индикатор формируется в виде кнопки), сигнатура функции `f()`, результат функции не интерпретируется\
|
||||||
|
`backgroundColor` - необязательный, строка, HTML-код пользовательского цвета фона, если указан - будет использован (вне зависимости от `state`) для заливки области индикатора (по умолчанию - не указан) \
|
||||||
|
`color` - необязательный, строка, HTML-код пользовательского цвета шрифта, если указан - будет использован (вне зависимости от `state`) для значения, подписи и иконки индикатора (по умолчанию - не указан)
|
||||||
|
|
||||||
|
**API на сервере БД**
|
||||||
|
|
||||||
|
Компонент `P8PIndicator` требует от разработчика передачи данных в определённом формате. С целью снижения трудозатрат на приведение собранных хранимым объектом данных Системы к форматам, потребляемым `P8PIndicator`, реализован специальный API на стороне сервера БД.
|
||||||
|
|
||||||
|
Для индикатора это (см. детальные описания программных интерфейсов в пакете `PKG_P8PANELS_VISUAL`):
|
||||||
|
`PKG_P8PANELS_VISUAL.TINDICATOR_MAKE` - функция, инициализация индикатора, возвращает объект для хранения его описания\
|
||||||
|
`PKG_P8PANELS_VISUAL.TINDICATOR_TO_XML` - функция, производит сериализацию объекта, описывающего индикатор, в специальный XML-формат, корректно интерпретируемый клиентским компонентом `P8PIndicator` при передаче в WEB-приложение
|
||||||
|
|
||||||
|
**Пример**
|
||||||
|
|
||||||
|
Полный актуальный исходный код примера можно увидеть в "app/panels/samples/indicator.js" данного репозитория.
|
||||||
|
|
||||||
### Ограничения дизайна пользовательского интерфейса
|
### Ограничения дизайна пользовательского интерфейса
|
||||||
|
|
||||||
Фреймворк позволяет реализовать любые пользовательские интерфейсы, вёрстка которых не противоречит возможностям современного HTML. Тем не менее, при разработке пользовательских интерфейсов панелей важно придерживаться предложенных ниже правил. Это позволит создавать их в едином ключе и упростит работу конечного пользователя при их освоении.
|
Фреймворк позволяет реализовать любые пользовательские интерфейсы, вёрстка которых не противоречит возможностям современного HTML. Тем не менее, при разработке пользовательских интерфейсов панелей важно придерживаться предложенных ниже правил. Это позволит создавать их в едином ключе и упростит работу конечного пользователя при их освоении.
|
||||||
|
@ -3,10 +3,49 @@
|
|||||||
Типовые стили
|
Типовые стили
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import { STATE } from "./app.text"; //Текстовые ресурсы и константы
|
||||||
|
import { red, green, orange, grey } from "@mui/material/colors";
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
|
//Цвета
|
||||||
|
export const APP_COLORS = {
|
||||||
|
[STATE.UNDEFINED]: {
|
||||||
|
color: "#dcdcdca0",
|
||||||
|
contrColor: "black"
|
||||||
|
},
|
||||||
|
[STATE.INFO]: {
|
||||||
|
color: "white",
|
||||||
|
contrColor: "black"
|
||||||
|
},
|
||||||
|
[STATE.OK]: {
|
||||||
|
color: green[200],
|
||||||
|
contrColor: green[900]
|
||||||
|
},
|
||||||
|
[STATE.ERR]: {
|
||||||
|
color: red[200],
|
||||||
|
contrColor: red[900]
|
||||||
|
},
|
||||||
|
[STATE.WARN]: {
|
||||||
|
color: orange[200],
|
||||||
|
contrColor: orange[900]
|
||||||
|
},
|
||||||
|
HOVER: {
|
||||||
|
color: grey[200],
|
||||||
|
contrColor: grey[900]
|
||||||
|
},
|
||||||
|
ACTIVE: {
|
||||||
|
color: grey[400],
|
||||||
|
contrColor: grey[900]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
export const APP_STYLES = {
|
export const APP_STYLES = {
|
||||||
SCROLL: {
|
SCROLL: {
|
||||||
|
16
app.text.js
16
app.text.js
@ -18,7 +18,8 @@ export const TITLES = {
|
|||||||
//Текст
|
//Текст
|
||||||
export const TEXTS = {
|
export const TEXTS = {
|
||||||
LOADING: "Ожидайте...", //Ожидание завершения процесса
|
LOADING: "Ожидайте...", //Ожидание завершения процесса
|
||||||
NO_DATA_FOUND: "Данных не найдено" //Отсутствие данных
|
NO_DATA_FOUND: "Данных не найдено", //Отсутствие данных
|
||||||
|
NO_DATA_FOUND_SHORT: "Н.Д." //Отсутствие данных (кратко)
|
||||||
};
|
};
|
||||||
|
|
||||||
//Текст кнопок
|
//Текст кнопок
|
||||||
@ -35,7 +36,9 @@ export const BUTTONS = {
|
|||||||
ORDER_ASC: "По возрастанию", //Сортировка по возрастанию
|
ORDER_ASC: "По возрастанию", //Сортировка по возрастанию
|
||||||
ORDER_DESC: "По убыванию", //Сортировка по убыванию
|
ORDER_DESC: "По убыванию", //Сортировка по убыванию
|
||||||
FILTER: "Фильтр", //Фильтрация
|
FILTER: "Фильтр", //Фильтрация
|
||||||
MORE: "Ещё" //Догрузка данных
|
MORE: "Ещё", //Догрузка данных
|
||||||
|
APPLY: "Применить", //Сохранение без закрытия интерфейса ввода
|
||||||
|
SAVE: "Сохранить" //Сохранение
|
||||||
};
|
};
|
||||||
|
|
||||||
//Метки атрибутов, сопроводительные надписи
|
//Метки атрибутов, сопроводительные надписи
|
||||||
@ -63,3 +66,12 @@ export const ERRORS = {
|
|||||||
export const ERRORS_HTTP = {
|
export const ERRORS_HTTP = {
|
||||||
404: "Адрес не найден"
|
404: "Адрес не найден"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Типовые статусы
|
||||||
|
export const STATE = {
|
||||||
|
UNDEFINED: "UNDEFINED",
|
||||||
|
INFO: "INFORMATION",
|
||||||
|
OK: "OK",
|
||||||
|
ERR: "ERR",
|
||||||
|
WARN: "WARN"
|
||||||
|
};
|
||||||
|
@ -86,6 +86,9 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
|
|||||||
//Подключение к контексту навигации
|
//Подключение к контексту навигации
|
||||||
const { navigateRoot, navigatePanel } = useContext(NavigationCtx);
|
const { navigateRoot, navigatePanel } = useContext(NavigationCtx);
|
||||||
|
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { appState } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//Отработка действия навигации домой
|
//Отработка действия навигации домой
|
||||||
const handleHomeNavigate = () => navigateRoot();
|
const handleHomeNavigate = () => navigateRoot();
|
||||||
|
|
||||||
@ -98,6 +101,7 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
|
|||||||
{...P8P_APP_WORKSPACE_CONFIG_PROPS}
|
{...P8P_APP_WORKSPACE_CONFIG_PROPS}
|
||||||
panels={panels}
|
panels={panels}
|
||||||
selectedPanel={selectedPanel}
|
selectedPanel={selectedPanel}
|
||||||
|
caption={appState.appBarTitle}
|
||||||
onHomeNavigate={handleHomeNavigate}
|
onHomeNavigate={handleHomeNavigate}
|
||||||
onItemNavigate={handleItemNavigate}
|
onItemNavigate={handleItemNavigate}
|
||||||
>
|
>
|
||||||
|
@ -18,6 +18,8 @@ import Typography from "@mui/material/Typography"; //Текст
|
|||||||
import Button from "@mui/material/Button"; //Кнопки
|
import Button from "@mui/material/Button"; //Кнопки
|
||||||
import Container from "@mui/material/Container"; //Контейнер
|
import Container from "@mui/material/Container"; //Контейнер
|
||||||
import Box from "@mui/material/Box"; //Обёртка
|
import Box from "@mui/material/Box"; //Обёртка
|
||||||
|
import { BUTTONS, STATE } from "../../app.text"; //Типовые текстовые ресурсы и константы
|
||||||
|
import { APP_COLORS } from "../../app.styles"; //Типовые стили
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -25,9 +27,9 @@ import Box from "@mui/material/Box"; //Обёртка
|
|||||||
|
|
||||||
//Варианты исполнения
|
//Варианты исполнения
|
||||||
const P8P_APP_MESSAGE_VARIANT = {
|
const P8P_APP_MESSAGE_VARIANT = {
|
||||||
INFO: "information",
|
INFO: STATE.INFO,
|
||||||
WARN: "warning",
|
WARN: STATE.WARN,
|
||||||
ERR: "error"
|
ERR: STATE.ERR
|
||||||
};
|
};
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
@ -36,28 +38,35 @@ const STYLES = {
|
|||||||
wordBreak: "break-word"
|
wordBreak: "break-word"
|
||||||
},
|
},
|
||||||
INFO: {
|
INFO: {
|
||||||
titleText: {},
|
titleText: {
|
||||||
bodyText: {}
|
color: APP_COLORS[STATE.INFO].contrColor
|
||||||
|
},
|
||||||
|
bodyText: {
|
||||||
|
color: APP_COLORS[STATE.INFO].contrColor
|
||||||
|
}
|
||||||
},
|
},
|
||||||
WARN: {
|
WARN: {
|
||||||
titleText: {
|
titleText: {
|
||||||
color: "orange"
|
color: APP_COLORS[STATE.WARN].contrColor
|
||||||
},
|
},
|
||||||
bodyText: {
|
bodyText: {
|
||||||
color: "orange"
|
color: APP_COLORS[STATE.WARN].contrColor
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ERR: {
|
ERR: {
|
||||||
titleText: {
|
titleText: {
|
||||||
color: "red"
|
color: APP_COLORS[STATE.ERR].contrColor
|
||||||
},
|
},
|
||||||
bodyText: {
|
bodyText: {
|
||||||
color: "red"
|
color: APP_COLORS[STATE.ERR].contrColor
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
INLINE_MESSAGE: {
|
INLINE_MESSAGE: {
|
||||||
with: "100%",
|
with: "100%",
|
||||||
textAlign: "center"
|
textAlign: "center"
|
||||||
|
},
|
||||||
|
FULL_ERROR_TEXT_BUTTON: {
|
||||||
|
color: APP_COLORS[STATE.WARN].contrColor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -104,12 +113,7 @@ const P8PAppMessage = ({
|
|||||||
|
|
||||||
//Заголовок
|
//Заголовок
|
||||||
let titlePart;
|
let titlePart;
|
||||||
if (title && titleText)
|
if (title && titleText) titlePart = <DialogTitle style={{ ...style.DEFAULT, ...style.titleText }}>{titleText}</DialogTitle>;
|
||||||
titlePart = (
|
|
||||||
<DialogTitle id="message-dialog-title" style={{ ...style.DEFAULT, ...style.titleText }}>
|
|
||||||
{titleText}
|
|
||||||
</DialogTitle>
|
|
||||||
);
|
|
||||||
|
|
||||||
//Кнопка Отмена
|
//Кнопка Отмена
|
||||||
let cancelBtnPart;
|
let cancelBtnPart;
|
||||||
@ -120,7 +124,7 @@ const P8PAppMessage = ({
|
|||||||
let okBtnPart;
|
let okBtnPart;
|
||||||
if (okBtn && okBtnCaption)
|
if (okBtn && okBtnCaption)
|
||||||
okBtnPart = (
|
okBtnPart = (
|
||||||
<Button onClick={() => (onOk ? onOk() : null)} color="primary" autoFocus>
|
<Button onClick={() => (onOk ? onOk() : null)} autoFocus>
|
||||||
{okBtnCaption}
|
{okBtnCaption}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@ -129,7 +133,7 @@ const P8PAppMessage = ({
|
|||||||
let fullErrorTextBtn;
|
let fullErrorTextBtn;
|
||||||
if (fullErrorText && showErrMoreCaption && hideErrMoreCaption && variant === P8P_APP_MESSAGE_VARIANT.ERR)
|
if (fullErrorText && showErrMoreCaption && hideErrMoreCaption && variant === P8P_APP_MESSAGE_VARIANT.ERR)
|
||||||
fullErrorTextBtn = (
|
fullErrorTextBtn = (
|
||||||
<Button onClick={() => setShowFullErrorText(!showFullErrorText)} color="warning" autoFocus>
|
<Button onClick={() => setShowFullErrorText(!showFullErrorText)} sx={STYLES.FULL_ERROR_TEXT_BUTTON} autoFocus>
|
||||||
{!showFullErrorText ? showErrMoreCaption : hideErrMoreCaption}
|
{!showFullErrorText ? showErrMoreCaption : hideErrMoreCaption}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@ -147,17 +151,10 @@ const P8PAppMessage = ({
|
|||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog open={open || false} onClose={() => (onCancel ? onCancel() : null)}>
|
||||||
open={open || false}
|
|
||||||
aria-labelledby="message-dialog-title"
|
|
||||||
aria-describedby="message-dialog-description"
|
|
||||||
onClose={() => (onCancel ? onCancel() : null)}
|
|
||||||
>
|
|
||||||
{titlePart}
|
{titlePart}
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText id="message-dialog-description" style={style.bodyText}>
|
<DialogContentText style={style.bodyText}>{!showFullErrorText ? text : fullErrorText}</DialogContentText>
|
||||||
{!showFullErrorText ? text : fullErrorText}
|
|
||||||
</DialogContentText>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
{actionsPart}
|
{actionsPart}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -189,13 +186,19 @@ const P8PAppInlineMessage = ({ variant, text, okBtn, onOk, okBtnCaption }) => {
|
|||||||
<Container style={STYLES.INLINE_MESSAGE}>
|
<Container style={STYLES.INLINE_MESSAGE}>
|
||||||
<Box p={1}>
|
<Box p={1}>
|
||||||
<Typography
|
<Typography
|
||||||
color={variant === P8P_APP_MESSAGE_VARIANT.ERR ? "error" : variant === P8P_APP_MESSAGE_VARIANT.WARN ? "primary" : "textSecondary"}
|
color={
|
||||||
|
variant === P8P_APP_MESSAGE_VARIANT.ERR
|
||||||
|
? APP_COLORS[STATE.ERR].contrColor
|
||||||
|
: variant === P8P_APP_MESSAGE_VARIANT.WARN
|
||||||
|
? APP_COLORS[STATE.WARN].contrColor
|
||||||
|
: APP_COLORS[STATE.INFO].contrColor
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</Typography>
|
</Typography>
|
||||||
{okBtn && okBtnCaption ? (
|
{okBtn && okBtnCaption ? (
|
||||||
<Box pt={1}>
|
<Box pt={1}>
|
||||||
<Button onClick={() => (onOk ? onOk() : null)} color="primary" autoFocus>
|
<Button onClick={() => (onOk ? onOk() : null)} autoFocus>
|
||||||
{okBtnCaption}
|
{okBtnCaption}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
@ -247,6 +250,28 @@ const P8PAppInlineWarn = props => buildVariantInlineMessage(props, P8P_APP_MESSA
|
|||||||
//Встраиваемое сообщение информации
|
//Встраиваемое сообщение информации
|
||||||
const P8PAppInlineInfo = props => buildVariantInlineMessage(props, P8P_APP_MESSAGE_VARIANT.INFO);
|
const P8PAppInlineInfo = props => buildVariantInlineMessage(props, P8P_APP_MESSAGE_VARIANT.INFO);
|
||||||
|
|
||||||
|
//Диалог подсказки
|
||||||
|
const P8PHintDialog = ({ title, hint, onOk }) => {
|
||||||
|
return (
|
||||||
|
<Dialog open={true} onClose={() => (onOk ? onOk() : null)}>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: hint }}></div>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => (onOk ? onOk() : null)}>{BUTTONS.OK}</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Диалог подсказки
|
||||||
|
P8PHintDialog.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
hint: PropTypes.string.isRequired,
|
||||||
|
onOk: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
@ -260,5 +285,6 @@ export {
|
|||||||
P8PAppInlineMessage,
|
P8PAppInlineMessage,
|
||||||
P8PAppInlineError,
|
P8PAppInlineError,
|
||||||
P8PAppInlineWarn,
|
P8PAppInlineWarn,
|
||||||
P8PAppInlineInfo
|
P8PAppInlineInfo,
|
||||||
|
P8PHintDialog
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,7 @@ const STYLES = {
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Рабочее пространство
|
//Рабочее пространство
|
||||||
const P8PAppWorkspace = ({ children, panels = [], selectedPanel, closeCaption, homeCaption, onHomeNavigate, onItemNavigate } = {}) => {
|
const P8PAppWorkspace = ({ children, panels = [], selectedPanel, caption, closeCaption, homeCaption, onHomeNavigate, onItemNavigate } = {}) => {
|
||||||
//Собственное состояния
|
//Собственное состояния
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ const P8PAppWorkspace = ({ children, panels = [], selectedPanel, closeCaption, h
|
|||||||
<Icon>{open ? "chevron_left" : "menu"}</Icon>
|
<Icon>{open ? "chevron_left" : "menu"}</Icon>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h6" noWrap component="div">
|
<Typography variant="h6" noWrap component="div">
|
||||||
{selectedPanel?.caption}
|
{caption || selectedPanel?.caption}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
@ -120,6 +120,7 @@ P8PAppWorkspace.propTypes = {
|
|||||||
children: PropTypes.element,
|
children: PropTypes.element,
|
||||||
panels: PropTypes.arrayOf(P8P_PANELS_MENU_PANEL_SHAPE).isRequired,
|
panels: PropTypes.arrayOf(P8P_PANELS_MENU_PANEL_SHAPE).isRequired,
|
||||||
selectedPanel: P8P_PANELS_MENU_PANEL_SHAPE,
|
selectedPanel: P8P_PANELS_MENU_PANEL_SHAPE,
|
||||||
|
caption: PropTypes.string,
|
||||||
closeCaption: PropTypes.string.isRequired,
|
closeCaption: PropTypes.string.isRequired,
|
||||||
homeCaption: PropTypes.string.isRequired,
|
homeCaption: PropTypes.string.isRequired,
|
||||||
onHomeNavigate: PropTypes.func,
|
onHomeNavigate: PropTypes.func,
|
||||||
|
@ -36,6 +36,7 @@ const P8P_DATA_GRID_FILTERS_HEIGHT = P8P_TABLE_FILTERS_HEIGHT;
|
|||||||
|
|
||||||
//Таблица данных
|
//Таблица данных
|
||||||
const P8PDataGrid = ({
|
const P8PDataGrid = ({
|
||||||
|
style = {},
|
||||||
columnsDef = [],
|
columnsDef = [],
|
||||||
filtersInitial,
|
filtersInitial,
|
||||||
groups = [],
|
groups = [],
|
||||||
@ -114,6 +115,7 @@ const P8PDataGrid = ({
|
|||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<P8PTable
|
<P8PTable
|
||||||
|
style={style}
|
||||||
columnsDef={columnsDef}
|
columnsDef={columnsDef}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
@ -154,6 +156,7 @@ const P8PDataGrid = ({
|
|||||||
|
|
||||||
//Контроль свойств - Таблица данных
|
//Контроль свойств - Таблица данных
|
||||||
P8PDataGrid.propTypes = {
|
P8PDataGrid.propTypes = {
|
||||||
|
style: PropTypes.object,
|
||||||
columnsDef: PropTypes.array,
|
columnsDef: PropTypes.array,
|
||||||
filtersInitial: PropTypes.arrayOf(P8P_DATA_GRID_FILTER_SHAPE),
|
filtersInitial: PropTypes.arrayOf(P8P_DATA_GRID_FILTER_SHAPE),
|
||||||
groups: PropTypes.array,
|
groups: PropTypes.array,
|
||||||
|
186
app/components/p8p_indicator.js
Normal file
186
app/components/p8p_indicator.js
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга
|
||||||
|
Компонент: Индикатор
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { IconButton, Icon, Typography, Paper, Stack } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||||
|
import { P8PHintDialog } from "./p8p_app_message"; //Диалог подсказки
|
||||||
|
import { TEXTS, STATE } from "../../app.text"; //Типовые текстовые ресурсы и константы
|
||||||
|
import { APP_COLORS } from "../../app.styles"; //Типовые стили
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Варианты исполнения
|
||||||
|
|
||||||
|
const P8P_INDICATOR_VARIANT = {
|
||||||
|
ELEVATION: "elevation",
|
||||||
|
OUTLINED: "outlined"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Состояния
|
||||||
|
const P8P_INDICATOR_STATE = {
|
||||||
|
UNDEFINED: STATE.UNDEFINED,
|
||||||
|
OK: STATE.OK,
|
||||||
|
WARN: STATE.WARN,
|
||||||
|
ERR: STATE.ERR
|
||||||
|
};
|
||||||
|
//Цвета заливки
|
||||||
|
const BG_COLOR = {
|
||||||
|
[STATE.OK]: APP_COLORS[STATE.OK].color,
|
||||||
|
[STATE.ERR]: APP_COLORS[STATE.ERR].color,
|
||||||
|
[STATE.WARN]: APP_COLORS[STATE.WARN].color
|
||||||
|
};
|
||||||
|
|
||||||
|
//Цвета текста и иконок
|
||||||
|
const COLOR = {
|
||||||
|
[STATE.OK]: APP_COLORS[STATE.OK].contrColor,
|
||||||
|
[STATE.ERR]: APP_COLORS[STATE.ERR].contrColor,
|
||||||
|
[STATE.WARN]: APP_COLORS[STATE.WARN].contrColor
|
||||||
|
};
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: (state, clickable, userColor, userBackgroundColor) => ({
|
||||||
|
padding: "10px",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
overflow: "hidden",
|
||||||
|
...getBackgroundColor(state, userBackgroundColor),
|
||||||
|
...getColor(state, userColor),
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
...(clickable
|
||||||
|
? {
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": { backgroundColor: APP_COLORS.HOVER.color },
|
||||||
|
"&:active": { backgroundColor: APP_COLORS.ACTIVE.color }
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}),
|
||||||
|
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" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------------------
|
||||||
|
//Вспомогательные функции
|
||||||
|
//-----------------------
|
||||||
|
|
||||||
|
//Подбор цвета заливки
|
||||||
|
const getBackgroundColor = (state, userColor) =>
|
||||||
|
userColor ? { backgroundColor: userColor } : BG_COLOR[state] ? { backgroundColor: BG_COLOR[state] } : {};
|
||||||
|
|
||||||
|
//Подбор цвета текста
|
||||||
|
const getColor = (state, userColor) => (userColor ? { color: userColor } : COLOR[state] ? { color: COLOR[state] } : {});
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Индикатор
|
||||||
|
const P8PIndicator = ({
|
||||||
|
caption,
|
||||||
|
value,
|
||||||
|
icon = null,
|
||||||
|
state = STATE.UNDEFINED,
|
||||||
|
square = false,
|
||||||
|
elevation = 3,
|
||||||
|
variant = P8P_INDICATOR_VARIANT.ELEVATION,
|
||||||
|
hint = null,
|
||||||
|
onClick = null,
|
||||||
|
backgroundColor = null,
|
||||||
|
color = null
|
||||||
|
} = {}) => {
|
||||||
|
//Собственное состояние - отображение окна подсказки
|
||||||
|
const [showHint, setShowHint] = useState(false);
|
||||||
|
|
||||||
|
//При нажатии на индикатор
|
||||||
|
const handleClick = () => (onClick && !showHint ? onClick() : null);
|
||||||
|
|
||||||
|
//При нажатии на кнопку получения подсказки
|
||||||
|
const handleHintClick = e => {
|
||||||
|
setShowHint(true);
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на кнопку закрытия подсказки
|
||||||
|
const handleHintClose = () => setShowHint(false);
|
||||||
|
|
||||||
|
//Представление текста значения индикатора
|
||||||
|
const valueTextView = <Typography variant={"h4"}>{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}</Typography>;
|
||||||
|
|
||||||
|
//Представление текста подписи индикатора
|
||||||
|
const captionView = (
|
||||||
|
<Typography align={"left"} noWrap={true} sx={STYLES.CAPTION_TYPOGRAPHY} title={caption}>
|
||||||
|
{caption}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
|
//Представление подписи индикатора
|
||||||
|
const valueView = hint ? (
|
||||||
|
<>
|
||||||
|
{showHint && <P8PHintDialog title={caption} hint={hint} onOk={handleHintClose} />}
|
||||||
|
<Stack direction={"row"} alignItems={"start"}>
|
||||||
|
{valueTextView}
|
||||||
|
<IconButton onClick={handleHintClick}>
|
||||||
|
<Icon sx={STYLES.HINT_ICON(state, color)}>help_outline</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
valueTextView
|
||||||
|
);
|
||||||
|
|
||||||
|
//Флаг активности индикатора
|
||||||
|
const clickable = onClick ? true : false;
|
||||||
|
|
||||||
|
//Представление
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={variant === P8P_INDICATOR_VARIANT.ELEVATION ? elevation : 0}
|
||||||
|
sx={STYLES.CONTAINER(state, clickable, color, backgroundColor)}
|
||||||
|
square={square}
|
||||||
|
variant={variant}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<Stack direction={"row"} alignItems={"center"} justifyContent={"space-between"}>
|
||||||
|
<Stack direction={"column"} alignItems={"start"} pr={2} sx={STYLES.VALUE_CAPTION_STACK}>
|
||||||
|
{valueView}
|
||||||
|
{captionView}
|
||||||
|
</Stack>
|
||||||
|
{icon ? <Icon sx={STYLES.ICON(state, color)}>{icon}</Icon> : null}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Индикатор
|
||||||
|
P8PIndicator.propTypes = {
|
||||||
|
caption: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
icon: PropTypes.string,
|
||||||
|
state: PropTypes.oneOf(Object.values(P8P_INDICATOR_STATE)),
|
||||||
|
square: PropTypes.bool,
|
||||||
|
elevation: PropTypes.number,
|
||||||
|
variant: PropTypes.oneOf(Object.values(P8P_INDICATOR_VARIANT)),
|
||||||
|
hint: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
backgroundColor: PropTypes.string,
|
||||||
|
color: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator };
|
@ -34,7 +34,7 @@ import {
|
|||||||
Link
|
Link
|
||||||
} from "@mui/material"; //Интерфейсные компоненты
|
} from "@mui/material"; //Интерфейсные компоненты
|
||||||
import { useTheme } from "@mui/material/styles"; //Взаимодействие со стилями MUI
|
import { useTheme } from "@mui/material/styles"; //Взаимодействие со стилями MUI
|
||||||
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
|
import { P8PAppInlineError, P8PHintDialog } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
|
||||||
import { P8P_TABLE_AT, HEADER_INITIAL_STATE, hasValue, p8pTableReducer } from "./p8p_table_reducer"; //Редьюсер состояния
|
import { P8P_TABLE_AT, HEADER_INITIAL_STATE, hasValue, p8pTableReducer } from "./p8p_table_reducer"; //Редьюсер состояния
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
@ -288,28 +288,6 @@ P8PTableColumnMenu.propTypes = {
|
|||||||
onItemClick: PropTypes.func
|
onItemClick: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
//Диалог подсказки
|
|
||||||
const P8PTableColumnHintDialog = ({ columnDef, okBtnCaption, onOk }) => {
|
|
||||||
return (
|
|
||||||
<Dialog open={true} aria-labelledby="filter-dialog-title" aria-describedby="filter-dialog-description" onClose={() => (onOk ? onOk() : null)}>
|
|
||||||
<DialogTitle id="filter-dialog-title">{columnDef.caption}</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: columnDef.hint }}></div>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => (onOk ? onOk() : null)}>{okBtnCaption}</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Контроль свойств - Диалог подсказки
|
|
||||||
P8PTableColumnHintDialog.propTypes = {
|
|
||||||
columnDef: PropTypes.object.isRequired,
|
|
||||||
okBtnCaption: PropTypes.string.isRequired,
|
|
||||||
onOk: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
//Диалог фильтра
|
//Диалог фильтра
|
||||||
const P8PTableColumnFilterDialog = ({
|
const P8PTableColumnFilterDialog = ({
|
||||||
columnDef,
|
columnDef,
|
||||||
@ -486,6 +464,7 @@ P8PTableFiltersChips.propTypes = {
|
|||||||
|
|
||||||
//Таблица
|
//Таблица
|
||||||
const P8PTable = ({
|
const P8PTable = ({
|
||||||
|
style = {},
|
||||||
columnsDef = [],
|
columnsDef = [],
|
||||||
groups = [],
|
groups = [],
|
||||||
rows = [],
|
rows = [],
|
||||||
@ -700,10 +679,8 @@ const P8PTable = ({
|
|||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ ...(style || {}) }}>
|
||||||
{displayHintColumn ? (
|
{displayHintColumn ? <P8PHintDialog title={displayHintColumnDef.caption} hint={displayHintColumnDef.hint} onOk={handleHintOk} /> : null}
|
||||||
<P8PTableColumnHintDialog columnDef={displayHintColumnDef} okBtnCaption={okFilterBtnCaption} onOk={handleHintOk} />
|
|
||||||
) : null}
|
|
||||||
{filterColumn ? (
|
{filterColumn ? (
|
||||||
<P8PTableColumnFilterDialog
|
<P8PTableColumnFilterDialog
|
||||||
columnDef={filterColumnDef}
|
columnDef={filterColumnDef}
|
||||||
@ -898,6 +875,7 @@ const P8PTable = ({
|
|||||||
|
|
||||||
//Контроль свойств - Таблица
|
//Контроль свойств - Таблица
|
||||||
P8PTable.propTypes = {
|
P8PTable.propTypes = {
|
||||||
|
style: PropTypes.object,
|
||||||
columnsDef: PropTypes.arrayOf(
|
columnsDef: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
|
@ -56,6 +56,9 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
|
|||||||
//Установка списка панелей
|
//Установка списка панелей
|
||||||
const setPanels = panels => dispatch({ type: APP_AT.LOAD_PANELS, payload: panels });
|
const setPanels = panels => dispatch({ type: APP_AT.LOAD_PANELS, payload: panels });
|
||||||
|
|
||||||
|
//Установка заголовка в шапке приложения
|
||||||
|
const setAppBarTitle = appBarTitle => dispatch({ type: APP_AT.SET_APP_BAR_TITLE, payload: appBarTitle });
|
||||||
|
|
||||||
//Поиск раздела по имени
|
//Поиск раздела по имени
|
||||||
const findPanelByName = name => state.panels.find(panel => panel.name == name);
|
const findPanelByName = name => state.panels.find(panel => panel.name == name);
|
||||||
|
|
||||||
@ -169,6 +172,7 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
|
|||||||
return (
|
return (
|
||||||
<ApplicationСtx.Provider
|
<ApplicationСtx.Provider
|
||||||
value={{
|
value={{
|
||||||
|
setAppBarTitle,
|
||||||
findPanelByName,
|
findPanelByName,
|
||||||
pOnlineShowTab,
|
pOnlineShowTab,
|
||||||
pOnlineShowUnit,
|
pOnlineShowUnit,
|
||||||
|
@ -12,12 +12,14 @@ const APP_AT = {
|
|||||||
SET_URL_BASE: "SET_URL_BASE", //Установка базового URL приложения
|
SET_URL_BASE: "SET_URL_BASE", //Установка базового URL приложения
|
||||||
LOAD_PANELS: "LOAD_PANELS", //Загрузка списка панелей
|
LOAD_PANELS: "LOAD_PANELS", //Загрузка списка панелей
|
||||||
SET_INITIALIZED: "SET_INITIALIZED", //Установка флага инициализированности приложения
|
SET_INITIALIZED: "SET_INITIALIZED", //Установка флага инициализированности приложения
|
||||||
SET_DISPLAY_SIZE: "SET_DISPLAY_SIZE" //Установка текущего типового размера экрана
|
SET_DISPLAY_SIZE: "SET_DISPLAY_SIZE", //Установка текущего типового размера экрана
|
||||||
|
SET_APP_BAR_TITLE: "SET_APP_BAR_TITLE" //Установка заголовка в шапке приложения
|
||||||
};
|
};
|
||||||
|
|
||||||
//Состояние приложения по умолчанию
|
//Состояние приложения по умолчанию
|
||||||
const INITIAL_STATE = displaySizeGetter => ({
|
const INITIAL_STATE = displaySizeGetter => ({
|
||||||
displaySize: displaySizeGetter(),
|
displaySize: displaySizeGetter(),
|
||||||
|
appBarTitle: "",
|
||||||
urlBase: "",
|
urlBase: "",
|
||||||
panels: [],
|
panels: [],
|
||||||
panelsLoaded: false,
|
panelsLoaded: false,
|
||||||
@ -46,6 +48,8 @@ const handlers = {
|
|||||||
[APP_AT.SET_INITIALIZED]: state => ({ ...state, initialized: true }),
|
[APP_AT.SET_INITIALIZED]: state => ({ ...state, initialized: true }),
|
||||||
//Установка текущего типового размера экрана
|
//Установка текущего типового размера экрана
|
||||||
[APP_AT.SET_DISPLAY_SIZE]: (state, { payload }) => ({ ...state, displaySize: payload }),
|
[APP_AT.SET_DISPLAY_SIZE]: (state, { payload }) => ({ ...state, displaySize: payload }),
|
||||||
|
//Установка заголовка в шапке приложения
|
||||||
|
[APP_AT.SET_APP_BAR_TITLE]: (state, { payload }) => ({ ...state, appBarTitle: payload }),
|
||||||
//Обработчик по умолчанию
|
//Обработчик по умолчанию
|
||||||
DEFAULT: state => state
|
DEFAULT: state => state
|
||||||
};
|
};
|
||||||
|
@ -64,7 +64,8 @@ export const BackEndContext = ({ client, children }) => {
|
|||||||
throwError = true,
|
throwError = true,
|
||||||
showErrorMessage = true,
|
showErrorMessage = true,
|
||||||
fullResponse = false,
|
fullResponse = false,
|
||||||
spreadOutArguments = true
|
spreadOutArguments = true,
|
||||||
|
signal = null
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
try {
|
try {
|
||||||
if (loader !== false) showLoader(loaderMessage);
|
if (loader !== false) showLoader(loaderMessage);
|
||||||
@ -76,7 +77,8 @@ export const BackEndContext = ({ client, children }) => {
|
|||||||
tagValueProcessor,
|
tagValueProcessor,
|
||||||
attributeValueProcessor,
|
attributeValueProcessor,
|
||||||
throwError,
|
throwError,
|
||||||
spreadOutArguments
|
spreadOutArguments,
|
||||||
|
signal
|
||||||
});
|
});
|
||||||
if (fullResponse === true || isRespErr(result)) return result;
|
if (fullResponse === true || isRespErr(result)) return result;
|
||||||
else return result.XPAYLOAD;
|
else return result.XPAYLOAD;
|
||||||
|
@ -41,7 +41,7 @@ export const NavigationContext = ({ children }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { findPanelByName } = useContext(ApplicationСtx);
|
const { findPanelByName, setAppBarTitle } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//Проверка наличия параметров запроса
|
//Проверка наличия параметров запроса
|
||||||
const isNavigationSearch = () => (location.search ? true : false);
|
const isNavigationSearch = () => (location.search ? true : false);
|
||||||
@ -65,6 +65,8 @@ export const NavigationContext = ({ children }) => {
|
|||||||
const navigateTo = ({ path, search, state, replace = false }) => {
|
const navigateTo = ({ path, search, state, replace = false }) => {
|
||||||
//Если указано куда переходить
|
//Если указано куда переходить
|
||||||
if (path) {
|
if (path) {
|
||||||
|
//Сброс кастомного заголовка
|
||||||
|
setAppBarTitle("");
|
||||||
//Переходим к адресу
|
//Переходим к адресу
|
||||||
if (state) navigate(path, { state: JSON.stringify(state), replace });
|
if (state) navigate(path, { state: JSON.stringify(state), replace });
|
||||||
else navigate({ pathname: path, search: queryString.stringify(search), replace });
|
else navigate({ pathname: path, search: queryString.stringify(search), replace });
|
||||||
|
@ -34,6 +34,7 @@ const ERR_APPSERVER = "Ошибка сервера приложений"; //Об
|
|||||||
const ERR_UNEXPECTED = "Неожиданный ответ сервера"; //Неожиданный ответ сервера
|
const ERR_UNEXPECTED = "Неожиданный ответ сервера"; //Неожиданный ответ сервера
|
||||||
const ERR_NETWORK = "Ошибка соединения с сервером"; //Ошибка сети
|
const ERR_NETWORK = "Ошибка соединения с сервером"; //Ошибка сети
|
||||||
const ERR_UNAUTH = "Сеанс завершен. Пройдите аутентификацию повторно."; //Ошибка аутентификации
|
const ERR_UNAUTH = "Сеанс завершен. Пройдите аутентификацию повторно."; //Ошибка аутентификации
|
||||||
|
const ERR_ABORTED = "Запрос прерван принудительно";
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
@ -76,7 +77,16 @@ const getRespErrMessage = resp => (isRespErr(resp) && resp.SMESSAGE ? resp.SMESS
|
|||||||
const getRespPayload = resp => (resp && resp.XPAYLOAD ? resp.XPAYLOAD : null);
|
const getRespPayload = resp => (resp && resp.XPAYLOAD ? resp.XPAYLOAD : null);
|
||||||
|
|
||||||
//Исполнение действия на сервере
|
//Исполнение действия на сервере
|
||||||
const executeAction = async ({ serverURL, action, payload = {}, isArray, transformTagName, tagValueProcessor, attributeValueProcessor } = {}) => {
|
const executeAction = async ({
|
||||||
|
serverURL,
|
||||||
|
action,
|
||||||
|
payload = {},
|
||||||
|
isArray,
|
||||||
|
transformTagName,
|
||||||
|
tagValueProcessor,
|
||||||
|
attributeValueProcessor,
|
||||||
|
signal = null
|
||||||
|
} = {}) => {
|
||||||
console.log(`EXECUTING ${action ? action : ""} ON ${serverURL} WITH PAYLOAD:`);
|
console.log(`EXECUTING ${action ? action : ""} ON ${serverURL} WITH PAYLOAD:`);
|
||||||
console.log(payload ? payload : "NO PAYLOAD");
|
console.log(payload ? payload : "NO PAYLOAD");
|
||||||
let response = null;
|
let response = null;
|
||||||
@ -92,11 +102,14 @@ const executeAction = async ({ serverURL, action, payload = {}, isArray, transfo
|
|||||||
body: await buildXML(rqBody),
|
body: await buildXML(rqBody),
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/xml"
|
"content-type": "application/xml"
|
||||||
}
|
},
|
||||||
|
...(signal ? { signal } : {})
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
//Прервано принудительно
|
||||||
|
if (signal?.aborted === true) throw new Error(ERR_ABORTED);
|
||||||
//Сетевая ошибка
|
//Сетевая ошибка
|
||||||
throw new Error(`${ERR_NETWORK}: ${e.message}`);
|
else throw new Error(`${ERR_NETWORK}: ${e.message || "неопределённая ошибка"}`);
|
||||||
}
|
}
|
||||||
//Проверим на наличие ошибок HTTP - если есть вернём их
|
//Проверим на наличие ошибок HTTP - если есть вернём их
|
||||||
if (!response.ok) throw new Error(`${ERR_APPSERVER}: ${response.statusText}`);
|
if (!response.ok) throw new Error(`${ERR_APPSERVER}: ${response.statusText}`);
|
||||||
@ -136,7 +149,8 @@ const executeStored = async ({
|
|||||||
tagValueProcessor,
|
tagValueProcessor,
|
||||||
attributeValueProcessor,
|
attributeValueProcessor,
|
||||||
throwError = true,
|
throwError = true,
|
||||||
spreadOutArguments = false
|
spreadOutArguments = false,
|
||||||
|
signal = null
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
let res = null;
|
let res = null;
|
||||||
try {
|
try {
|
||||||
@ -157,7 +171,8 @@ const executeStored = async ({
|
|||||||
payload: { SSTORED: stored, XARGUMENTS: serverArgs, SRESP_ARG: respArg },
|
payload: { SSTORED: stored, XARGUMENTS: serverArgs, SRESP_ARG: respArg },
|
||||||
isArray,
|
isArray,
|
||||||
tagValueProcessor,
|
tagValueProcessor,
|
||||||
attributeValueProcessor
|
attributeValueProcessor,
|
||||||
|
signal
|
||||||
});
|
});
|
||||||
if (spreadOutArguments === true && Array.isArray(res?.XPAYLOAD?.XOUT_ARGUMENTS)) {
|
if (spreadOutArguments === true && Array.isArray(res?.XPAYLOAD?.XOUT_ARGUMENTS)) {
|
||||||
let spreadArgs = {};
|
let spreadArgs = {};
|
||||||
@ -193,6 +208,11 @@ const getConfig = async ({ throwError = true } = {}) => {
|
|||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
ERR_APPSERVER,
|
||||||
|
ERR_UNEXPECTED,
|
||||||
|
ERR_NETWORK,
|
||||||
|
ERR_UNAUTH,
|
||||||
|
ERR_ABORTED,
|
||||||
SERV_DATA_TYPE_STR,
|
SERV_DATA_TYPE_STR,
|
||||||
SERV_DATA_TYPE_NUMB,
|
SERV_DATA_TYPE_NUMB,
|
||||||
SERV_DATA_TYPE_DATE,
|
SERV_DATA_TYPE_DATE,
|
||||||
|
@ -102,7 +102,7 @@ const getDisplaySize = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Глубокое копирование объекта
|
//Глубокое копирование объекта
|
||||||
const deepCopyObject = obj => JSON.parse(JSON.stringify(obj));
|
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj)));
|
||||||
|
|
||||||
//Конвертация объекта в Base64 XML
|
//Конвертация объекта в Base64 XML
|
||||||
const object2Base64XML = (obj, builderOptions) => {
|
const object2Base64XML = (obj, builderOptions) => {
|
||||||
|
52
app/panels/panels_editor/component_editor.js
Normal file
52
app/panels/panels_editor/component_editor.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Редактор свойств компонента панели
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Box, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов
|
||||||
|
import "./panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Редактор свойств компонента панели
|
||||||
|
const ComponentEditor = ({ id, path, settings = {}, valueProviders = {}, onSettingsChange = null } = {}) => {
|
||||||
|
//Подгрузка модуля редактора компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
|
||||||
|
const [ComponentEditor, init] = useComponentModule({ path, module: "editor" });
|
||||||
|
|
||||||
|
//Расчёт флага наличия компонента
|
||||||
|
const haveComponent = path ? true : false;
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Box className={"component-editor__wrap"}>
|
||||||
|
{haveComponent && init && (
|
||||||
|
<ComponentEditor.default id={id} {...settings} valueProviders={valueProviders} onSettingsChange={onSettingsChange} />
|
||||||
|
)}
|
||||||
|
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - редактор свойств компонента панели
|
||||||
|
ComponentEditor.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
path: PropTypes.string.isRequired,
|
||||||
|
settings: PropTypes.object,
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
onSettingsChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { ComponentEditor };
|
72
app/panels/panels_editor/component_view.js
Normal file
72
app/panels/panels_editor/component_view.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Представление компонента панели
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Box, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов
|
||||||
|
import "./panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Представление компонента панели
|
||||||
|
const ComponentView = ({ id, path, settings = {}, values = {}, onValuesChange = null } = {}) => {
|
||||||
|
//Подгрузка модуля представления компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
|
||||||
|
const [ComponentView, init] = useComponentModule({ path, module: "view" });
|
||||||
|
|
||||||
|
//При смене значений
|
||||||
|
const handleValuesChange = values => onValuesChange && onValuesChange(id, values);
|
||||||
|
|
||||||
|
//Расчёт флага наличия компонента
|
||||||
|
const haveComponent = path ? true : false;
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Box className={"component-view__wrap"}>
|
||||||
|
{haveComponent && init && <ComponentView.default id={id} {...settings} values={values} onValuesChange={handleValuesChange} />}
|
||||||
|
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - компонент панели
|
||||||
|
ComponentView.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
path: PropTypes.string.isRequired,
|
||||||
|
settings: PropTypes.object,
|
||||||
|
values: PropTypes.object,
|
||||||
|
onValuesChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { ComponentView };
|
||||||
|
|
||||||
|
//--------------------------
|
||||||
|
//ВАЖНО: Можно на React.lazy
|
||||||
|
//--------------------------
|
||||||
|
|
||||||
|
//ПРИМЕР:
|
||||||
|
/*
|
||||||
|
import React, { Suspense, lazy } from "react"; //Классы React
|
||||||
|
const ComponentView = ({ path = null, props = {} } = {}) => {
|
||||||
|
const haveComponent = path ? true : false;
|
||||||
|
const ComponentView = haveComponent ? lazy(() => import(`./components/${path}/view`)) : null;
|
||||||
|
return (
|
||||||
|
<Paper sx={STYLES.CONTAINER}>
|
||||||
|
{haveComponent && (<Suspense fallback={null}><ComponentView {...props}/></Suspense>)}
|
||||||
|
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
*/
|
56
app/panels/panels_editor/components/chart/editor.js
Normal file
56
app/panels/panels_editor/components/chart/editor.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: График (редактор настроек)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов
|
||||||
|
import "../../panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//График (редактор настроек)
|
||||||
|
const ChartEditor = ({ id, dataSource = null, valueProviders = {}, 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 handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<EditorBox title={"Параметры графика"} onSave={handleSave}>
|
||||||
|
<EditorSubHeader title={"Источник данных"} />
|
||||||
|
<DataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||||
|
</EditorBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - График (редактор настроек)
|
||||||
|
ChartEditor.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
dataSource: DATA_SOURCE_SHAPE,
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
onSettingsChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export default ChartEditor;
|
79
app/panels/panels_editor/components/chart/view.js
Normal file
79
app/panels/panels_editor/components/chart/view.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: График (представление)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8PChart } from "../../../../components/p8p_chart"; //График
|
||||||
|
import { useComponentDataSource } from "../components_hooks"; //Хуки для данных
|
||||||
|
import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов
|
||||||
|
import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений
|
||||||
|
import "../../panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Иконка компонента
|
||||||
|
const COMPONENT_ICON = "bar_chart";
|
||||||
|
|
||||||
|
//Наименование компонента
|
||||||
|
const COMPONENT_NAME = "График";
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CHART: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", display: "flex" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//График (представление)
|
||||||
|
const Chart = ({ dataSource = null, values = {} } = {}) => {
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, error] = useComponentDataSource({ dataSource, values });
|
||||||
|
|
||||||
|
//Флаг настроенности графика
|
||||||
|
const haveConfing = dataSource?.stored ? true : false;
|
||||||
|
|
||||||
|
//Флаг наличия данных
|
||||||
|
const haveData = data?.init === true && !error ? true : false;
|
||||||
|
|
||||||
|
//Данные графика
|
||||||
|
const chart = data?.XCHART || {};
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Paper className={"component-view__container component-view__container__empty"} elevation={6}>
|
||||||
|
{haveConfing && haveData ? (
|
||||||
|
<P8PChart style={STYLES.CHART} {...chart} />
|
||||||
|
) : (
|
||||||
|
<ComponentInlineMessage
|
||||||
|
icon={COMPONENT_ICON}
|
||||||
|
name={COMPONENT_NAME}
|
||||||
|
message={!haveConfing ? COMPONENT_MESSAGES.NO_SETTINGS : error ? error : COMPONENT_MESSAGES.NO_DATA_FOUND}
|
||||||
|
type={error ? COMPONENT_MESSAGE_TYPE.ERROR : COMPONENT_MESSAGE_TYPE.COMMON}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - График (представление)
|
||||||
|
Chart.propTypes = {
|
||||||
|
dataSource: DATA_SOURCE_SHAPE,
|
||||||
|
values: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export default Chart;
|
129
app/panels/panels_editor/components/components.js
Normal file
129
app/panels/panels_editor/components/components.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Описание
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
const COMPONETNS = [
|
||||||
|
{
|
||||||
|
name: "Форма",
|
||||||
|
path: "form",
|
||||||
|
settings: {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Индикатор",
|
||||||
|
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: "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: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { COMPONETNS };
|
174
app/panels/panels_editor/components/components_hooks.js
Normal file
174
app/panels/panels_editor/components/components_hooks.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Хуки компонентов
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import { useState, useContext, useEffect, useRef } from "react"; //Классы React
|
||||||
|
import client from "../../../core/client"; //Клиент взаимодействия с сервером приложений
|
||||||
|
import { formatErrorMessage } from "../../../core/utils"; //Общие вспомогательные функции
|
||||||
|
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
|
import { DATA_SOURCE_TYPE, ARGUMENT_DATA_TYPE } from "./editors_common"; //Общие объекты редакторов
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Загрузка модуля компонента из модуля (можно применять как альтернативу React.lazy)
|
||||||
|
const useComponentModule = ({ path = null, module = "view" } = {}) => {
|
||||||
|
//Собственное состояние - импортированный модуль компонента
|
||||||
|
const [componentModule, setComponentModule] = useState(null);
|
||||||
|
|
||||||
|
//Собственное состояние - флаг готовности
|
||||||
|
const [init, setInit] = useState(false);
|
||||||
|
|
||||||
|
//При подмонтировании к странице
|
||||||
|
useEffect(() => {
|
||||||
|
//Динамическая загрузка модуля компонента из библиотеки
|
||||||
|
const importComponentModule = async () => {
|
||||||
|
setInit(false);
|
||||||
|
const moduleContent = await import(`./${path}/${module}`);
|
||||||
|
setComponentModule(moduleContent);
|
||||||
|
setInit(true);
|
||||||
|
};
|
||||||
|
if (path) importComponentModule();
|
||||||
|
}, [path, module]);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [componentModule, init];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Описание пользовательской процедуры
|
||||||
|
const useUserProcDesc = ({ code, refresh }) => {
|
||||||
|
//Собственное состояние - флаг загрузки
|
||||||
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//При необходимости обновить данные компонента
|
||||||
|
useEffect(() => {
|
||||||
|
//Загрузка данных с сервера
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_EDITOR.USERPROCS_DESC",
|
||||||
|
args: { SCODE: code },
|
||||||
|
respArg: "COUT",
|
||||||
|
isArray: name => name === "arguments",
|
||||||
|
loader: false
|
||||||
|
});
|
||||||
|
setData(data?.XUSERPROC || null);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//Если надо обновить и есть для чего получать данные
|
||||||
|
if (refresh > 0)
|
||||||
|
if (code) loadData();
|
||||||
|
else setData(null);
|
||||||
|
}, [refresh, code, executeStored]);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [data, isLoading];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Получение данных компонента из источника
|
||||||
|
const useComponentDataSource = ({ dataSource, values }) => {
|
||||||
|
//Контроллер для прерывания запросов
|
||||||
|
const abortController = useRef(null);
|
||||||
|
|
||||||
|
//Собственное состояние - параметры исполнения
|
||||||
|
const [state, setState] = useState({ stored: null, storedArgs: [], respArg: null, reqSet: false });
|
||||||
|
|
||||||
|
//Собственное состояние - флаг загрузки
|
||||||
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, setData] = useState({ init: false });
|
||||||
|
|
||||||
|
//Собственное состояние - ошибка получения данных
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//При необходимости обновить данные
|
||||||
|
useEffect(() => {
|
||||||
|
//Загрузка данных с сервера
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
abortController.current?.abort?.();
|
||||||
|
abortController.current = new AbortController();
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: state.stored,
|
||||||
|
args: { ...(state.storedArgs ? state.storedArgs : {}) },
|
||||||
|
respArg: state.respArg,
|
||||||
|
loader: false,
|
||||||
|
signal: abortController.current.signal,
|
||||||
|
showErrorMessage: false
|
||||||
|
});
|
||||||
|
setError(null);
|
||||||
|
setData({ ...data, init: true });
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message !== client.ERR_ABORTED) {
|
||||||
|
setError(formatErrorMessage(e.message).text);
|
||||||
|
setData({ init: false });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (state.reqSet) {
|
||||||
|
if (state.stored) loadData();
|
||||||
|
} else setData({ init: false });
|
||||||
|
return () => abortController.current?.abort?.();
|
||||||
|
}, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored]);
|
||||||
|
|
||||||
|
//При изменении свойств
|
||||||
|
useEffect(() => {
|
||||||
|
setState(pv => {
|
||||||
|
if (dataSource?.type == DATA_SOURCE_TYPE.USER_PROC) {
|
||||||
|
const { stored, respArg } = dataSource;
|
||||||
|
let reqSet = true;
|
||||||
|
const storedArgs = {};
|
||||||
|
dataSource.arguments.forEach(argument => {
|
||||||
|
let v = argument.valueSource ? values[argument.valueSource] : argument.value;
|
||||||
|
storedArgs[argument.name] =
|
||||||
|
argument.dataType == ARGUMENT_DATA_TYPE.NUMB
|
||||||
|
? isNaN(parseFloat(v))
|
||||||
|
? null
|
||||||
|
: parseFloat(v)
|
||||||
|
: argument.dataType == ARGUMENT_DATA_TYPE.DATE
|
||||||
|
? new Date(v)
|
||||||
|
: String(v === undefined ? "" : v);
|
||||||
|
if (argument.req === true && [undefined, null, ""].includes(storedArgs[argument.name])) reqSet = false;
|
||||||
|
});
|
||||||
|
if (pv.stored != stored || pv.respArg != respArg || JSON.stringify(pv.storedArgs) != JSON.stringify(storedArgs)) {
|
||||||
|
if (!reqSet) {
|
||||||
|
setError("Не заданы обязательные параметры источника данных");
|
||||||
|
setData({ init: false });
|
||||||
|
}
|
||||||
|
return { stored, respArg, storedArgs, reqSet };
|
||||||
|
} else return pv;
|
||||||
|
} else return pv;
|
||||||
|
});
|
||||||
|
}, [dataSource, values]);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [data, error, isLoading];
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { useComponentModule, useUserProcDesc, useComponentDataSource };
|
434
app/panels/panels_editor/components/editors_common.js
Normal file
434
app/panels/panels_editor/components/editors_common.js
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Общие компоненты редакторов свойств
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Stack,
|
||||||
|
IconButton,
|
||||||
|
Icon,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
Chip,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
InputAdornment,
|
||||||
|
MenuItem,
|
||||||
|
Menu,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardActions,
|
||||||
|
CardActionArea
|
||||||
|
} from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import client from "../../../core/client"; //Клиент БД
|
||||||
|
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||||
|
import { BUTTONS } from "../../../../app.text"; //Общие текстовые ресурсы
|
||||||
|
import { useUserProcDesc } from "./components_hooks"; //Общие хуки компонентов
|
||||||
|
import "../panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CHIP: (fullWidth = false, multiLine = false) => ({
|
||||||
|
...(multiLine ? { height: "auto" } : {}),
|
||||||
|
"& .MuiChip-label": {
|
||||||
|
...(multiLine
|
||||||
|
? {
|
||||||
|
display: "block",
|
||||||
|
whiteSpace: "normal"
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(fullWidth ? { width: "100%" } : {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
//Типы даных аргументов
|
||||||
|
const ARGUMENT_DATA_TYPE = {
|
||||||
|
STR: client.SERV_DATA_TYPE_STR,
|
||||||
|
NUMB: client.SERV_DATA_TYPE_NUMB,
|
||||||
|
DATE: client.SERV_DATA_TYPE_DATE
|
||||||
|
};
|
||||||
|
|
||||||
|
//Типы источников данных
|
||||||
|
const DATA_SOURCE_TYPE = {
|
||||||
|
USER_PROC: "USER_PROC",
|
||||||
|
QUERY: "QUERY"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Типы источников данных (наименования)
|
||||||
|
const DATA_SOURCE_TYPE_NAME = {
|
||||||
|
[DATA_SOURCE_TYPE.USER_PROC]: "Пользовательская процедура",
|
||||||
|
[DATA_SOURCE_TYPE.QUERY]: "Запрос"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Структура аргумента источника данных
|
||||||
|
const DATA_SOURCE_ARGUMENT_SHAPE = PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
caption: PropTypes.string.isRequired,
|
||||||
|
dataType: PropTypes.oneOf(Object.values(ARGUMENT_DATA_TYPE)),
|
||||||
|
req: PropTypes.bool.isRequired,
|
||||||
|
value: PropTypes.any,
|
||||||
|
valueSource: PropTypes.string
|
||||||
|
});
|
||||||
|
|
||||||
|
//Начальное состояние аргумента источника данных
|
||||||
|
const DATA_SOURCE_ARGUMENT_INITIAL = {
|
||||||
|
name: "",
|
||||||
|
caption: "",
|
||||||
|
dataType: "",
|
||||||
|
req: false,
|
||||||
|
value: "",
|
||||||
|
valueSource: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
//Структура источника данных
|
||||||
|
const DATA_SOURCE_SHAPE = PropTypes.shape({
|
||||||
|
type: PropTypes.oneOf([...Object.values(DATA_SOURCE_TYPE), ""]),
|
||||||
|
userProc: PropTypes.string,
|
||||||
|
stored: PropTypes.string,
|
||||||
|
respArg: PropTypes.string,
|
||||||
|
arguments: PropTypes.arrayOf(DATA_SOURCE_ARGUMENT_SHAPE)
|
||||||
|
});
|
||||||
|
|
||||||
|
//Начальное состояние истоника данных
|
||||||
|
const DATA_SOURCE_INITIAL = {
|
||||||
|
type: "",
|
||||||
|
userProc: "",
|
||||||
|
stored: "",
|
||||||
|
respArg: "",
|
||||||
|
arguments: []
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Контейнер редактора
|
||||||
|
const EditorBox = ({ title, children, onSave }) => {
|
||||||
|
//При нажатии на "Сохранить"
|
||||||
|
const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor);
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Box className={"component-editor__container"}>
|
||||||
|
<Divider>{title}</Divider>
|
||||||
|
<Stack direction={"column"} spacing={1}>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
<Stack direction={"row"} justifyContent={"right"} p={1}>
|
||||||
|
<IconButton onClick={() => handleSaveClick(false)} title={BUTTONS.APPLY}>
|
||||||
|
<Icon>done</Icon>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => handleSaveClick(true)} title={BUTTONS.SAVE}>
|
||||||
|
<Icon>done_all</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - контейнер редактора
|
||||||
|
EditorBox.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||||
|
onSave: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//Заголовок раздела редактора
|
||||||
|
const EditorSubHeader = ({ title }) => {
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Divider className={"component-editor__divider"}>
|
||||||
|
<Chip label={title} size={"small"} />
|
||||||
|
</Divider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - заголовок раздела редактора
|
||||||
|
EditorSubHeader.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//Диалог настройки
|
||||||
|
const ConfigDialog = ({ title, children, onOk, onCancel }) => {
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Dialog onClose={onCancel} open>
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogContent>{children}</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => onOk && onOk()}>{BUTTONS.OK}</Button>
|
||||||
|
<Button onClick={() => onCancel && onCancel()}>{BUTTONS.CANCEL}</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - диалог настройки
|
||||||
|
ConfigDialog.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||||
|
onOk: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//Диалог настройки источника данных
|
||||||
|
const ConfigDataSourceDialog = ({ dataSource = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => {
|
||||||
|
//Собственное состояние - параметры элемента формы
|
||||||
|
const [state, setState] = useState({ ...DATA_SOURCE_INITIAL, ...dataSource });
|
||||||
|
|
||||||
|
//Собственное состояние - флаги обновление данных
|
||||||
|
const [refresh, setRefresh] = useState({ userProcDesc: 0 });
|
||||||
|
|
||||||
|
//Собственное состояние - элемент привязки меню выбора источника
|
||||||
|
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState(null);
|
||||||
|
|
||||||
|
//Описание выбранной пользовательской процедуры
|
||||||
|
const [userProcDesc] = useUserProcDesc({ code: state.userProc, refresh: refresh.userProcDesc });
|
||||||
|
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//Установка значения/привязки аргумента
|
||||||
|
const setArgumentValueSource = (index, value, valueSource) =>
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
arguments: pv.arguments.map((argument, i) => ({ ...argument, ...(i == index ? { value, valueSource } : {}) }))
|
||||||
|
}));
|
||||||
|
|
||||||
|
//Открытие/сокрытие меню выбора источника
|
||||||
|
const toggleValueProvidersMenu = target => setValueProvidersMenuAnchorEl(target instanceof Element ? target : null);
|
||||||
|
|
||||||
|
//При нажатии на очистку наименования пользовательской процедуры
|
||||||
|
const handleUserProcClearClick = () => setState({ ...DATA_SOURCE_INITIAL });
|
||||||
|
|
||||||
|
//При нажатии на выбор пользовательской процедуры в качестве источника данных
|
||||||
|
const handleUserProcSelectClick = () => {
|
||||||
|
pOnlineShowDictionary({
|
||||||
|
unitCode: "UserProcedures",
|
||||||
|
showMethod: "main",
|
||||||
|
inputParameters: [{ name: "in_CODE", value: state.userProc }],
|
||||||
|
callBack: res => {
|
||||||
|
if (res.success) {
|
||||||
|
setState(pv => ({ ...pv, type: DATA_SOURCE_TYPE.USER_PROC, userProc: res.outParameters.out_CODE }));
|
||||||
|
setRefresh(pv => ({ ...pv, userProcDesc: pv.userProcDesc + 1 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//При закрытии дилога с сохранением
|
||||||
|
const handleOk = () => onOk && onOk({ ...state });
|
||||||
|
|
||||||
|
//При закртии диалога отменой
|
||||||
|
const handleCancel = () => onCancel && onCancel();
|
||||||
|
|
||||||
|
//При очистке значения/связывания аргумента
|
||||||
|
const handleArgumentClearClick = index => setArgumentValueSource(index, "", "");
|
||||||
|
|
||||||
|
//При отображении меню связывания аргумента с поставщиком данных
|
||||||
|
const handleArgumentLinkMenuClick = e => setValueProvidersMenuAnchorEl(e.currentTarget);
|
||||||
|
|
||||||
|
//При выборе элемента меню связывания аргумента с поставщиком данных
|
||||||
|
const handleArgumentLinkClick = valueSource => {
|
||||||
|
setArgumentValueSource(valueProvidersMenuAnchorEl.id, "", valueSource);
|
||||||
|
toggleValueProvidersMenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
//При вводе значения аргумента
|
||||||
|
const handleArgumentChange = (index, value) => setArgumentValueSource(index, value, "");
|
||||||
|
|
||||||
|
//При изменении описания пользовательской процедуры
|
||||||
|
useEffect(() => {
|
||||||
|
if (userProcDesc)
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
stored: userProcDesc?.stored?.name,
|
||||||
|
respArg: userProcDesc?.stored?.respArg,
|
||||||
|
arguments: (userProcDesc?.arguments || []).map(argument => ({ ...DATA_SOURCE_ARGUMENT_INITIAL, ...argument }))
|
||||||
|
}));
|
||||||
|
}, [userProcDesc]);
|
||||||
|
|
||||||
|
//Список значений
|
||||||
|
const values = Object.keys(valueProviders).reduce((res, key) => [...res, ...Object.keys(valueProviders[key])], []);
|
||||||
|
|
||||||
|
//Наличие значений
|
||||||
|
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={() => handleArgumentLinkClick(value)}>
|
||||||
|
{value}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<ConfigDialog title="Настройка источника данных" onOk={handleOk} onCancel={handleCancel}>
|
||||||
|
<Stack direction={"column"} spacing={1}>
|
||||||
|
{valueProvidersMenu}
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.userProc}
|
||||||
|
label={"Пользовательская процедура"}
|
||||||
|
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>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</ConfigDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Диалог настройки источника данных
|
||||||
|
ConfigDataSourceDialog.propTypes = {
|
||||||
|
dataSource: DATA_SOURCE_SHAPE,
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
onOk: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//Источник данных
|
||||||
|
const DataSource = ({ dataSource = null, valueProviders = {}, onChange = null } = {}) => {
|
||||||
|
//Собственное состояние - отображение диалога настройки
|
||||||
|
const [configDlg, setConfigDlg] = useState(false);
|
||||||
|
|
||||||
|
//Уведомление родителя о смене настроек источника данных
|
||||||
|
const notifyChange = settings => onChange && onChange(settings);
|
||||||
|
|
||||||
|
//При нажатии на настройку источника данных
|
||||||
|
const handleSetup = () => setConfigDlg(true);
|
||||||
|
|
||||||
|
//При нажатии на настройку источника данных
|
||||||
|
const handleSetupOk = dataSource => {
|
||||||
|
setConfigDlg(false);
|
||||||
|
notifyChange(dataSource);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на настройку источника данных
|
||||||
|
const handleSetupCancel = () => setConfigDlg(false);
|
||||||
|
|
||||||
|
//При удалении настроек источника данных
|
||||||
|
const handleDelete = () => notifyChange({ ...DATA_SOURCE_INITIAL });
|
||||||
|
|
||||||
|
//Расчет флага "настроенности"
|
||||||
|
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={STYLES.CHIP(true)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{configDlg && (
|
||||||
|
<ConfigDataSourceDialog dataSource={dataSource} valueProviders={valueProviders} onOk={handleSetupOk} onCancel={handleSetupCancel} />
|
||||||
|
)}
|
||||||
|
{configured && (
|
||||||
|
<Card variant={"outlined"}>
|
||||||
|
<CardActionArea onClick={handleSetup}>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant={"subtitle1"} noWrap={true}>
|
||||||
|
{dataSource.type === DATA_SOURCE_TYPE.USER_PROC ? dataSource.userProc : "Источник без наименования"}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant={"caption"} color={"text.secondary"} noWrap={true}>
|
||||||
|
{DATA_SOURCE_TYPE_NAME[dataSource.type] || "Неизвестный тип источника"}
|
||||||
|
</Typography>
|
||||||
|
<Stack direction={"column"} spacing={1} pt={2}>
|
||||||
|
{args}
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
</CardActionArea>
|
||||||
|
<CardActions>
|
||||||
|
<IconButton onClick={handleDelete}>
|
||||||
|
<Icon>delete</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{!configured && (
|
||||||
|
<Button startIcon={<Icon>build</Icon>} onClick={handleSetup}>
|
||||||
|
Настроить
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Источник данных
|
||||||
|
DataSource.propTypes = {
|
||||||
|
dataSource: DATA_SOURCE_SHAPE,
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
onChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { STYLES, ARGUMENT_DATA_TYPE, DATA_SOURCE_TYPE, DATA_SOURCE_SHAPE, DATA_SOURCE_INITIAL, EditorBox, EditorSubHeader, ConfigDialog, DataSource };
|
49
app/panels/panels_editor/components/form/common.js
Normal file
49
app/panels/panels_editor/components/form/common.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Форма (общие константы)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
//Структура элемента формы
|
||||||
|
export const ITEM_SHAPE = PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
caption: PropTypes.string.isRequired,
|
||||||
|
unitCode: PropTypes.string,
|
||||||
|
unitName: PropTypes.string,
|
||||||
|
showMethod: PropTypes.string,
|
||||||
|
showMethodName: PropTypes.string,
|
||||||
|
parameter: PropTypes.string,
|
||||||
|
inputParameter: PropTypes.string,
|
||||||
|
outputParameter: PropTypes.string
|
||||||
|
});
|
||||||
|
|
||||||
|
//Начальное состояние элемента формы
|
||||||
|
export const ITEM_INITIAL = {
|
||||||
|
name: "",
|
||||||
|
caption: "",
|
||||||
|
unitCode: "",
|
||||||
|
unitName: "",
|
||||||
|
showMethod: "",
|
||||||
|
showMethodName: "",
|
||||||
|
parameter: "",
|
||||||
|
inputParameter: "",
|
||||||
|
outputParameter: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние элементов формы
|
||||||
|
export const ITEMS_INITIAL = [];
|
||||||
|
|
||||||
|
//Ориентация элементов формы
|
||||||
|
export const ORIENTATION = {
|
||||||
|
H: "H",
|
||||||
|
V: "v"
|
||||||
|
};
|
306
app/panels/panels_editor/components/form/editor.js
Normal file
306
app/panels/panels_editor/components/form/editor.js
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Форма (редактор настроек)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//TODO: Контроль уникальности имени элемента формы
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useEffect, useState, useContext } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import {
|
||||||
|
TextField,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
FormControlLabel,
|
||||||
|
Switch,
|
||||||
|
Chip,
|
||||||
|
Stack,
|
||||||
|
InputAdornment,
|
||||||
|
IconButton
|
||||||
|
} from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения
|
||||||
|
import { STYLES as COMMON_STYLES, EditorBox, EditorSubHeader, ConfigDialog } from "../editors_common"; //Общие компоненты редакторов
|
||||||
|
import { ITEM_SHAPE, ITEM_INITIAL, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CHIP_ITEM: { ...COMMON_STYLES.CHIP(true, false) }
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Редактор элемента
|
||||||
|
const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => {
|
||||||
|
//Собственное состояние - параметры элемента формы
|
||||||
|
const [state, setState] = useState({ ...ITEM_INITIAL, ...item });
|
||||||
|
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//При закрытии редактора с сохранением
|
||||||
|
const handleOk = () => onOk && onOk({ ...state });
|
||||||
|
|
||||||
|
//При закрытии редактора с отменой
|
||||||
|
const handleCancel = () => onCancel && onCancel();
|
||||||
|
|
||||||
|
//При изменении параметра элемента
|
||||||
|
const handleChange = e => setState(pv => ({ ...pv, [e.target.id]: e.target.value }));
|
||||||
|
|
||||||
|
//При нажатии на очистку раздела
|
||||||
|
const handleClearUnitClick = () =>
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
unitCode: "",
|
||||||
|
unitName: "",
|
||||||
|
showMethod: "",
|
||||||
|
showMethodName: "",
|
||||||
|
parameter: "",
|
||||||
|
inputParameter: "",
|
||||||
|
outputParameter: ""
|
||||||
|
}));
|
||||||
|
|
||||||
|
//При нажатии на выбор раздела
|
||||||
|
const handleSelectUnitClick = () => {
|
||||||
|
pOnlineShowDictionary({
|
||||||
|
unitCode: "Units",
|
||||||
|
showMethod: "methods",
|
||||||
|
inputParameters: [
|
||||||
|
{ name: "pos_unit_name", value: state.unitName },
|
||||||
|
{ name: "pos_method_name", value: state.showMethodName }
|
||||||
|
],
|
||||||
|
callBack: res =>
|
||||||
|
res.success &&
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
unitCode: res.outParameters.unit_code,
|
||||||
|
unitName: res.outParameters.unit_name,
|
||||||
|
showMethod: res.outParameters.method_code,
|
||||||
|
showMethodName: res.outParameters.method_name,
|
||||||
|
parameter: "",
|
||||||
|
inputParameter: "",
|
||||||
|
outputParameter: ""
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на выбор параметра метода вызова
|
||||||
|
const handleSelectUnitParameterClick = () => {
|
||||||
|
state.unitCode &&
|
||||||
|
state.showMethod &&
|
||||||
|
pOnlineShowDictionary({
|
||||||
|
unitCode: "UnitParams",
|
||||||
|
showMethod: "main",
|
||||||
|
inputParameters: [
|
||||||
|
{ name: "in_UNITCODE", value: state.unitCode },
|
||||||
|
{ name: "in_PARENT_METHOD_CODE", value: state.showMethod },
|
||||||
|
{ name: "in_PARAMNAME", value: state.parameter }
|
||||||
|
],
|
||||||
|
callBack: res =>
|
||||||
|
res.success &&
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
parameter: res.outParameters.out_PARAMNAME,
|
||||||
|
inputParameter: res.outParameters.out_IN_CODE,
|
||||||
|
outputParameter: res.outParameters.out_OUT_CODE
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<ConfigDialog title={`${item ? "Изменение" : "Добавление"} элемента`} onOk={handleOk} onCancel={handleCancel}>
|
||||||
|
<Stack direction={"column"} spacing={1}>
|
||||||
|
<TextField type={"text"} variant={"standard"} value={state.name} label={"Имя"} id={"name"} onChange={handleChange} />
|
||||||
|
<TextField type={"text"} variant={"standard"} value={state.caption} label={"Приглашение"} id={"caption"} onChange={handleChange} />
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.unitName}
|
||||||
|
label={"Раздел"}
|
||||||
|
InputLabelProps={{ shrink: state.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>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.showMethodName}
|
||||||
|
label={"Метод вызова"}
|
||||||
|
InputLabelProps={{ shrink: state.showMethodName ? true : false }}
|
||||||
|
InputProps={{ readOnly: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.parameter}
|
||||||
|
label={"Параметр"}
|
||||||
|
InputLabelProps={{ shrink: state.parameter ? true : false }}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleSelectUnitParameterClick}>
|
||||||
|
<Icon>list</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</ConfigDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - редактор элемента
|
||||||
|
ItemEditor.propTypes = {
|
||||||
|
item: ITEM_SHAPE,
|
||||||
|
onOk: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Форма (редактор настроек)
|
||||||
|
const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = false, items = ITEMS_INITIAL, onSettingsChange = null } = {}) => {
|
||||||
|
//Собственное состояние - текущие настройки
|
||||||
|
const [settings, setSettings] = useState(null);
|
||||||
|
|
||||||
|
//Собственное состояние - предоставляемые в панель значения
|
||||||
|
const [providedValues, setProvidedValues] = useState([]);
|
||||||
|
|
||||||
|
//Собственное состояние - редактор элементов формы
|
||||||
|
const [itemEditor, setItemEditor] = useState({ display: false, index: null });
|
||||||
|
|
||||||
|
//При изменении значения настройки
|
||||||
|
const handleChange = e => setSettings({ ...settings, [e.target.name]: e.target.type === "checkbox" ? e.target.checked : e.target.value });
|
||||||
|
|
||||||
|
//При добавлении нового элемента
|
||||||
|
const handleItemAdd = () => setItemEditor({ display: true, index: null });
|
||||||
|
|
||||||
|
//При нажатии на элемент
|
||||||
|
const handleItemClick = i => setItemEditor({ display: true, index: i });
|
||||||
|
|
||||||
|
//При удалении элемента
|
||||||
|
const handleItemDelete = i => {
|
||||||
|
const items = [...settings.items];
|
||||||
|
items.splice(i, 1);
|
||||||
|
setSettings(pv => ({ ...pv, items }));
|
||||||
|
};
|
||||||
|
|
||||||
|
//При сохранении изменений элемента
|
||||||
|
const handleItemSave = item => {
|
||||||
|
const items = [...settings.items];
|
||||||
|
itemEditor.index == null ? items.push({ ...item }) : (items[itemEditor.index] = { ...item });
|
||||||
|
setSettings(pv => ({ ...pv, items }));
|
||||||
|
setItemEditor({ display: false, index: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
//При отмене сохранения изменений элемента
|
||||||
|
const handleItemCancel = () => setItemEditor({ display: false, index: null });
|
||||||
|
|
||||||
|
//При сохранении настроек
|
||||||
|
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, providedValues, 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 && (
|
||||||
|
<EditorBox title={"Параметры формы"} onSave={handleSave}>
|
||||||
|
{itemEditor.display && (
|
||||||
|
<ItemEditor
|
||||||
|
item={itemEditor.index !== null ? { ...settings.items[itemEditor.index] } : null}
|
||||||
|
onCancel={handleItemCancel}
|
||||||
|
onOk={handleItemSave}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<EditorSubHeader title={"Общие"} />
|
||||||
|
<TextField type={"text"} variant={"standard"} value={settings.title} label={"Заголовок"} name={"title"} onChange={handleChange} />
|
||||||
|
<FormControl variant={"standard"}>
|
||||||
|
<InputLabel id={"orientation-label"}>Ориентация</InputLabel>
|
||||||
|
<Select
|
||||||
|
name={"orientation"}
|
||||||
|
value={settings.orientation}
|
||||||
|
labelId={"orientation-label"}
|
||||||
|
label={"Ориентация"}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<MenuItem value={ORIENTATION.V}>Вертикально</MenuItem>
|
||||||
|
<MenuItem value={ORIENTATION.H}>Горизонтально</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch name={"autoApply"} checked={settings.autoApply} onChange={handleChange} />}
|
||||||
|
label={"Автоподтверждение"}
|
||||||
|
/>
|
||||||
|
<EditorSubHeader title={"Элементы"} />
|
||||||
|
{Array.isArray(settings?.items) &&
|
||||||
|
settings.items.length > 0 &&
|
||||||
|
settings.items.map((item, i) => (
|
||||||
|
<Chip
|
||||||
|
key={i}
|
||||||
|
label={item.caption}
|
||||||
|
variant={"outlined"}
|
||||||
|
onClick={() => handleItemClick(i)}
|
||||||
|
onDelete={() => handleItemDelete(i)}
|
||||||
|
sx={STYLES.CHIP_ITEM}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Button startIcon={<Icon>add</Icon>} onClick={handleItemAdd}>
|
||||||
|
Добавить элемент
|
||||||
|
</Button>
|
||||||
|
</EditorBox>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Форма (редактор настроек)
|
||||||
|
FormEditor.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string,
|
||||||
|
orientation: PropTypes.oneOf(Object.values(ORIENTATION)),
|
||||||
|
autoApply: PropTypes.bool,
|
||||||
|
items: PropTypes.arrayOf(ITEM_SHAPE),
|
||||||
|
onSettingsChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export default FormEditor;
|
168
app/panels/panels_editor/components/form/view.js
Normal file
168
app/panels/panels_editor/components/form/view.js
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Форма (представление)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useEffect, useState, useContext } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Paper, Stack, Typography, Icon, TextField, IconButton, InputAdornment } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения
|
||||||
|
import { COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений
|
||||||
|
import { ITEM_SHAPE, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
||||||
|
import "../../panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Иконка компонента
|
||||||
|
const COMPONENT_ICON = "fact_check";
|
||||||
|
|
||||||
|
//Наименование компонента
|
||||||
|
const COMPONENT_NAME = "Форма";
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Элемент формы
|
||||||
|
const FormItem = ({ item = null, fullWidth = false, value = "", onChange = null } = {}) => {
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//При изменении значения элемента
|
||||||
|
const handleChange = e => onChange && onChange(e.target.id, e.target.value);
|
||||||
|
|
||||||
|
//При очистке значения элемента
|
||||||
|
const handleClear = () => onChange(item.name, "");
|
||||||
|
|
||||||
|
//При выборе значения из словаря
|
||||||
|
const handleDictionary = () =>
|
||||||
|
item.unitCode &&
|
||||||
|
item.showMethod &&
|
||||||
|
pOnlineShowDictionary({
|
||||||
|
unitCode: item.unitCode,
|
||||||
|
showMethod: item.showMethod,
|
||||||
|
inputParameters: [{ name: item.inputParameter, value }],
|
||||||
|
callBack: res => res.success && onChange && onChange(item.name, res.outParameters[item.outputParameter])
|
||||||
|
});
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
item && (
|
||||||
|
<TextField
|
||||||
|
fullWidth={fullWidth}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={value}
|
||||||
|
label={item.caption}
|
||||||
|
id={item.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
{...(item.unitCode && {
|
||||||
|
InputLabelProps: { shrink: true },
|
||||||
|
InputProps: {
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleClear}>
|
||||||
|
<Icon>clear</Icon>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={handleDictionary}>
|
||||||
|
<Icon>list</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - элемент формы
|
||||||
|
FormItem.propTypes = {
|
||||||
|
item: ITEM_SHAPE,
|
||||||
|
fullWidth: PropTypes.bool,
|
||||||
|
value: PropTypes.any,
|
||||||
|
onChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Форма (представление)
|
||||||
|
const Form = ({ title = null, orientation = ORIENTATION.V, autoApply = false, items = ITEMS_INITIAL, values = {}, onValuesChange = null } = {}) => {
|
||||||
|
//Собственное состояние - значения элементов
|
||||||
|
const [selfValues, setSelfValues] = useState({});
|
||||||
|
|
||||||
|
//При изменении состава элементов или значений
|
||||||
|
useEffect(() => setSelfValues(items.reduce((sV, item) => ({ ...sV, [item.name]: values[item.name] }), {})), [items, values]);
|
||||||
|
|
||||||
|
//При изменении значения элемента формы
|
||||||
|
const handleItemChange = (name, value) => {
|
||||||
|
setSelfValues(pv => ({ ...pv, [name]: value }));
|
||||||
|
autoApply && onValuesChange && onValuesChange({ ...selfValues, [name]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
//При подтверждении изменений формы
|
||||||
|
const handleOkClick = () => onValuesChange && onValuesChange({ ...selfValues });
|
||||||
|
|
||||||
|
//Флаг настроенности формы
|
||||||
|
const haveConfing = items && Array.isArray(items) && items.length > 0;
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Paper className={`component-view__container ${!haveConfing && "component-view__container__empty"}`} elevation={6}>
|
||||||
|
{haveConfing ? (
|
||||||
|
<Stack direction={"column"}>
|
||||||
|
<Stack direction={"row"} justifyContent={"space-between"} alignItems={"center"}>
|
||||||
|
{title && (
|
||||||
|
<Typography align={"left"} color={"text.primary"} variant={"subtitle2"} noWrap={true}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{!autoApply && (
|
||||||
|
<IconButton onClick={handleOkClick}>
|
||||||
|
<Icon>done</Icon>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
<Stack direction={orientation == ORIENTATION.V ? "column" : "row"} spacing={1} pt={1} pb={1}>
|
||||||
|
{items.map((item, i) => (
|
||||||
|
<FormItem
|
||||||
|
key={i}
|
||||||
|
item={item}
|
||||||
|
value={selfValues?.[item.name] || ""}
|
||||||
|
onChange={handleItemChange}
|
||||||
|
fullWidth={orientation == ORIENTATION.V}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<ComponentInlineMessage icon={COMPONENT_ICON} name={COMPONENT_NAME} message={COMPONENT_MESSAGES.NO_SETTINGS} />
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Форма (представление)
|
||||||
|
Form.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string,
|
||||||
|
orientation: PropTypes.oneOf(Object.values(ORIENTATION)),
|
||||||
|
autoApply: PropTypes.bool,
|
||||||
|
items: PropTypes.arrayOf(ITEM_SHAPE),
|
||||||
|
values: PropTypes.object,
|
||||||
|
onValuesChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export default Form;
|
56
app/panels/panels_editor/components/indicator/editor.js
Normal file
56
app/panels/panels_editor/components/indicator/editor.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Индикатор (редактор настроек)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов
|
||||||
|
import "../../panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Индикатор (редактор настроек)
|
||||||
|
const IndicatorEditor = ({ id, dataSource = null, valueProviders = {}, 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 handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<EditorBox title={"Параметры индикатора"} onSave={handleSave}>
|
||||||
|
<EditorSubHeader title={"Источник данных"} />
|
||||||
|
<DataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||||
|
</EditorBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Индикатор (редактор настроек)
|
||||||
|
IndicatorEditor.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
dataSource: DATA_SOURCE_SHAPE,
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
onSettingsChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export default IndicatorEditor;
|
84
app/panels/panels_editor/components/indicator/view.js
Normal file
84
app/panels/panels_editor/components/indicator/view.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Индикатор (представление)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8PIndicator } from "../../../../components/p8p_indicator"; //Компонент индикатора
|
||||||
|
import { useComponentDataSource } from "../components_hooks"; //Хуки для данных
|
||||||
|
import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов
|
||||||
|
import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений
|
||||||
|
import "../../panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Иконка компонента
|
||||||
|
const COMPONENT_ICON = "speed";
|
||||||
|
|
||||||
|
//Наименование компонента
|
||||||
|
const COMPONENT_NAME = "Индикатор";
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: { height: "100%" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Индикатор (представление)
|
||||||
|
const Indicator = ({ dataSource = null, values = {} } = {}) => {
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, error] = useComponentDataSource({ dataSource, values });
|
||||||
|
|
||||||
|
//Флаг настроенности индикатора
|
||||||
|
const haveConfing = dataSource?.stored ? true : false;
|
||||||
|
|
||||||
|
//Флаг наличия данных
|
||||||
|
const haveData = data?.init === true && !error ? true : false;
|
||||||
|
|
||||||
|
//Данные индикатора
|
||||||
|
const indicator = data?.XINDICATOR || {};
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
{...(haveConfing && haveData
|
||||||
|
? { sx: { ...STYLES.CONTAINER } }
|
||||||
|
: { className: "component-view__container component-view__container__empty" })}
|
||||||
|
elevation={6}
|
||||||
|
>
|
||||||
|
{haveConfing && haveData ? (
|
||||||
|
<P8PIndicator {...indicator} elevation={0} />
|
||||||
|
) : (
|
||||||
|
<ComponentInlineMessage
|
||||||
|
icon={COMPONENT_ICON}
|
||||||
|
name={COMPONENT_NAME}
|
||||||
|
message={!haveConfing ? COMPONENT_MESSAGES.NO_SETTINGS : error ? error : COMPONENT_MESSAGES.NO_DATA_FOUND}
|
||||||
|
type={error ? COMPONENT_MESSAGE_TYPE.ERROR : COMPONENT_MESSAGE_TYPE.COMMON}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Индикатор (представление)
|
||||||
|
Indicator.propTypes = {
|
||||||
|
dataSource: DATA_SOURCE_SHAPE,
|
||||||
|
values: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export default Indicator;
|
56
app/panels/panels_editor/components/table/editor.js
Normal file
56
app/panels/panels_editor/components/table/editor.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Таблица (редактор настроек)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { DATA_SOURCE_SHAPE, DataSource, EditorBox, EditorSubHeader } from "../editors_common"; //Общие компоненты редакторов
|
||||||
|
import "../../panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Таблица (редактор настроек)
|
||||||
|
const TableEditor = ({ id, dataSource = null, valueProviders = {}, 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 handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<EditorBox title={"Параметры таблицы"} onSave={handleSave}>
|
||||||
|
<EditorSubHeader title={"Источник данных"} />
|
||||||
|
<DataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||||
|
</EditorBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Таблица (редактор настроек)
|
||||||
|
TableEditor.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
dataSource: DATA_SOURCE_SHAPE,
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
onSettingsChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export default TableEditor;
|
96
app/panels/panels_editor/components/table/view.js
Normal file
96
app/panels/panels_editor/components/table/view.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Таблица (представление)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Paper } 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 { useComponentDataSource } from "../components_hooks"; //Хуки для данных
|
||||||
|
import { DATA_SOURCE_SHAPE } from "../editors_common"; //Общие объекты компонентов
|
||||||
|
import { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage } from "../views_common"; //Общие компоненты представлений
|
||||||
|
import "../../panels_editor.css"; //Стили редактора
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Иконка компонента
|
||||||
|
const COMPONENT_ICON = "table_view";
|
||||||
|
|
||||||
|
//Наименование компонента
|
||||||
|
const COMPONENT_NAME = "Таблица";
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: { display: "flex", height: "100%", overflow: "hidden" },
|
||||||
|
DATA_GRID: { width: "100%" },
|
||||||
|
DATA_GRID_CONTAINER: {
|
||||||
|
height: `calc(100%)`,
|
||||||
|
...APP_STYLES.SCROLL
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Таблица (представление)
|
||||||
|
const Table = ({ dataSource = null, values = {} } = {}) => {
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, error] = useComponentDataSource({ dataSource, values });
|
||||||
|
|
||||||
|
//Флаг настроенности таблицы
|
||||||
|
const haveConfing = dataSource?.stored ? true : false;
|
||||||
|
|
||||||
|
//Флаг наличия данных
|
||||||
|
const haveData = data?.init === true && !error ? true : false;
|
||||||
|
|
||||||
|
//Данные таблицы
|
||||||
|
const dataGrid = data?.XDATA_GRID || {};
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
{...(haveConfing && haveData
|
||||||
|
? { sx: { ...STYLES.CONTAINER } }
|
||||||
|
: { className: "component-view__container component-view__container__empty" })}
|
||||||
|
elevation={6}
|
||||||
|
>
|
||||||
|
{haveConfing && haveData ? (
|
||||||
|
<P8PDataGrid
|
||||||
|
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||||
|
{...dataGrid}
|
||||||
|
style={STYLES.DATA_GRID}
|
||||||
|
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ComponentInlineMessage
|
||||||
|
icon={COMPONENT_ICON}
|
||||||
|
name={COMPONENT_NAME}
|
||||||
|
message={!haveConfing ? COMPONENT_MESSAGES.NO_SETTINGS : error ? error : COMPONENT_MESSAGES.NO_DATA_FOUND}
|
||||||
|
type={error ? COMPONENT_MESSAGE_TYPE.ERROR : COMPONENT_MESSAGE_TYPE.COMMON}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Таблица (представление)
|
||||||
|
Table.propTypes = {
|
||||||
|
dataSource: DATA_SOURCE_SHAPE,
|
||||||
|
values: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export default Table;
|
67
app/panels/panels_editor/components/views_common.js
Normal file
67
app/panels/panels_editor/components/views_common.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Общие компоненты представлений элементов панели
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Stack, Icon, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { TEXTS } from "../../../../app.text"; //Общие текстовые ресурсы
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Типы сообщений
|
||||||
|
const COMPONENT_MESSAGE_TYPE = {
|
||||||
|
COMMON: "COMMON",
|
||||||
|
ERROR: "ERROR"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Типовые сообщения
|
||||||
|
const COMPONENT_MESSAGES = {
|
||||||
|
NO_DATA_FOUND: TEXTS.NO_DATA_FOUND,
|
||||||
|
NO_SETTINGS: "Настройте компонент"
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Информационное сообщение внутри компонента
|
||||||
|
const ComponentInlineMessage = ({ icon, name, message, type = COMPONENT_MESSAGE_TYPE.COMMON }) => {
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Stack direction={"column"}>
|
||||||
|
<Stack direction={"row"} justifyContent={"center"} alignItems={"center"}>
|
||||||
|
{icon && <Icon color={"disabled"}>{icon}</Icon>}
|
||||||
|
{name && (
|
||||||
|
<Typography align={"center"} color={"text.secondary"} variant={"button"}>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
<Typography align={"center"} color={type != COMPONENT_MESSAGE_TYPE.ERROR ? "text.secondary" : "error.dark"} variant={"caption"}>
|
||||||
|
{message}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Информационное сообщение внутри компонента
|
||||||
|
ComponentInlineMessage.propTypes = {
|
||||||
|
icon: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
message: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.oneOf(Object.values(COMPONENT_MESSAGE_TYPE))
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { COMPONENT_MESSAGE_TYPE, COMPONENT_MESSAGES, ComponentInlineMessage };
|
16
app/panels/panels_editor/index.js
Normal file
16
app/panels/panels_editor/index.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Редактор панелей: точка входа
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import { PanelsEditor } from "./panels_editor"; //Корневая панель редактора
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export const RootClass = PanelsEditor;
|
87
app/panels/panels_editor/layout_item.js
Normal file
87
app/panels/panels_editor/layout_item.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Элемент макета
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import "./panels_editor.css"; //Кастомные стили
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: selected => ({ zIndex: 1100, ...(selected ? { border: "2px dotted green" } : {}) }),
|
||||||
|
STACK_TOOLS: { position: "absolute", zIndex: 1200, height: "100%", backgroundColor: "#c0c0c07f" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Элемент макета
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
|
const LayoutItem = React.forwardRef(
|
||||||
|
(
|
||||||
|
{ style, className, onMouseDown, onMouseUp, onTouchEnd, children, onSettingsClick, onDeleteClick, item, editMode = false, selected = false },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
//При нажатии на настройки
|
||||||
|
const handleSettingsClick = () => onSettingsClick && onSettingsClick(item.i);
|
||||||
|
|
||||||
|
//При нажатии на удаление
|
||||||
|
const handleDeleteClick = () => onDeleteClick && onDeleteClick(item.i);
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ ...style, ...STYLES.CONTAINER(selected) }}
|
||||||
|
className={`${className} layout-item__container`}
|
||||||
|
ref={ref}
|
||||||
|
onMouseDown={onMouseDown}
|
||||||
|
onMouseUp={onMouseUp}
|
||||||
|
onTouchEnd={onTouchEnd}
|
||||||
|
>
|
||||||
|
{editMode && (
|
||||||
|
<Stack direction={"column"} sx={STYLES.STACK_TOOLS}>
|
||||||
|
<IconButton onClick={handleSettingsClick}>
|
||||||
|
<Icon>settings</Icon>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={handleDeleteClick}>
|
||||||
|
<Icon>delete</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//Контроль свойств компонента - элемент макета
|
||||||
|
LayoutItem.propTypes = {
|
||||||
|
style: PropTypes.object,
|
||||||
|
className: PropTypes.string,
|
||||||
|
onMouseDown: PropTypes.func,
|
||||||
|
onMouseUp: PropTypes.func,
|
||||||
|
onTouchEnd: PropTypes.func,
|
||||||
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||||
|
onSettingsClick: PropTypes.func,
|
||||||
|
onDeleteClick: PropTypes.func,
|
||||||
|
item: PropTypes.object.isRequired,
|
||||||
|
editMode: PropTypes.bool,
|
||||||
|
selected: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { LayoutItem };
|
40
app/panels/panels_editor/panels_editor.css
Normal file
40
app/panels/panels_editor/panels_editor.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
:root {
|
||||||
|
--border-color: #dee2e6;
|
||||||
|
--layout-bg: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
background-color: var(--layout-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-item__container {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-editor__wrap {
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-editor__container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-editor__divider {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-view__wrap {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-view__container {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-view__container__empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
249
app/panels/panels_editor/panels_editor.js
Normal file
249
app/panels/panels_editor/panels_editor.js
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Корневой компонент
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useEffect, useState, useContext } from "react"; //Классы React
|
||||||
|
import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
|
||||||
|
import { Box, Grid, Stack, Menu, MenuItem, IconButton, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
|
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
|
||||||
|
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"; //Стили для адаптивного макета
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
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]: []
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Обёрдка для динамического макета
|
||||||
|
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 { 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 handleComponentDeleteClick = id => deleteComponent(id);
|
||||||
|
|
||||||
|
//При подключении к странице
|
||||||
|
useEffect(() => {
|
||||||
|
addComponent(COMPONETNS[0]);
|
||||||
|
addComponent(COMPONETNS[3]);
|
||||||
|
addComponent(COMPONETNS[4]);
|
||||||
|
//addComponent(COMPONETNS[1]);
|
||||||
|
//addComponent(COMPONETNS[2]);
|
||||||
|
// 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 = (
|
||||||
|
<Stack direction={"row"} p={1}>
|
||||||
|
<IconButton onClick={toggleEditMode} title={"Запустить"}>
|
||||||
|
<Icon>play_arrow</Icon>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={handleAddClick} title={"Добавить элемент"}>
|
||||||
|
<Icon>add</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { PanelsEditor };
|
156
app/panels/samples/indicator.js
Normal file
156
app/panels/samples/indicator.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Примеры для разработчиков
|
||||||
|
Пример: Индикатор "P8PIndicator"
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useContext } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Typography, Stack, Divider } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||||
|
import { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator } from "../../components/p8p_indicator"; //Индикатор
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: { textAlign: "center", paddingTop: "20px" },
|
||||||
|
TITLE: { paddingBottom: "15px" },
|
||||||
|
DIVIDER: { margin: "15px" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Пример: Индикатор "P8PIndicator"
|
||||||
|
const Indicator = ({ title }) => {
|
||||||
|
//Подключение к контексту сообщений
|
||||||
|
const { showMsgInfo } = useContext(MessagingСtx);
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return (
|
||||||
|
<div style={STYLES.CONTAINER}>
|
||||||
|
<Typography sx={STYLES.TITLE} variant={"h6"}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Divider>Иконка</Divider>
|
||||||
|
<Stack direction={"row"} spacing={2} p={5}>
|
||||||
|
{/* Индикатор (без иконки) */}
|
||||||
|
<P8PIndicator caption={"Без иконки"} value={10} />
|
||||||
|
{/* Индикатор (с иконкой 1) */}
|
||||||
|
<P8PIndicator caption={"С иконкой - Back Hand"} value={20} icon={"back_hand"} />
|
||||||
|
{/* Индикатор (с иконкой 2) */}
|
||||||
|
<P8PIndicator caption={"С иконкой - Scoreboard"} value={30} icon={"scoreboard"} />
|
||||||
|
</Stack>
|
||||||
|
<Divider>Состояние</Divider>
|
||||||
|
<Stack direction={"row"} spacing={2} p={5}>
|
||||||
|
{/* Индикатор (нейтральный) */}
|
||||||
|
<P8PIndicator caption={"Нейтральное состояние"} value={10} icon={"sentiment_neutral"} />
|
||||||
|
{/* Индикатор (позитивный) */}
|
||||||
|
<P8PIndicator caption={"Позитивное состояние"} value={20} state={P8P_INDICATOR_STATE.OK} icon={"check_circle"} />
|
||||||
|
{/* Индикатор (пограничный) */}
|
||||||
|
<P8PIndicator caption={"Пограничное состояние"} value={30} state={P8P_INDICATOR_STATE.WARN} icon={"warning"} />
|
||||||
|
{/* Индикатор (негативный) */}
|
||||||
|
<P8PIndicator caption={"Негативное состояния"} value={40} state={P8P_INDICATOR_STATE.ERR} icon={"dangerous"} />
|
||||||
|
</Stack>
|
||||||
|
<Divider>Скругление</Divider>
|
||||||
|
<Stack direction={"row"} spacing={2} p={5}>
|
||||||
|
{/* Индикатор (скругленный) */}
|
||||||
|
<P8PIndicator caption={"Скругленный"} />
|
||||||
|
{/* Индикатор (квадратный) */}
|
||||||
|
<P8PIndicator caption={"Квадрадтный"} square={true} />
|
||||||
|
</Stack>
|
||||||
|
<Divider>Парение</Divider>
|
||||||
|
<Stack direction={"row"} spacing={2} p={5}>
|
||||||
|
{/* Индикатор (парение - 0) */}
|
||||||
|
<P8PIndicator caption={"Парение"} value={0} state={P8P_INDICATOR_STATE.OK} elevation={0} />
|
||||||
|
{/* Индикатор (парение - 3) */}
|
||||||
|
<P8PIndicator caption={"Парение (по умолчанию)"} value={3} state={P8P_INDICATOR_STATE.WARN} elevation={3} />
|
||||||
|
{/* Индикатор (парение - 6) */}
|
||||||
|
<P8PIndicator caption={"Парение"} value={6} state={P8P_INDICATOR_STATE.OK} elevation={6} />
|
||||||
|
{/* Индикатор (парение - 12) */}
|
||||||
|
<P8PIndicator caption={"Парение"} value={12} state={P8P_INDICATOR_STATE.OK} elevation={12} />
|
||||||
|
{/* Индикатор (парение - 18) */}
|
||||||
|
<P8PIndicator caption={"Парение"} value={18} state={P8P_INDICATOR_STATE.OK} elevation={18} />
|
||||||
|
</Stack>
|
||||||
|
<Divider>Исполнение</Divider>
|
||||||
|
<Stack direction={"row"} spacing={2} p={5}>
|
||||||
|
{/* Индикатор (парение) */}
|
||||||
|
<P8PIndicator caption={"Парящий (по умолчанию)"} value={123} />
|
||||||
|
{/* Индикатор (рамка) */}
|
||||||
|
<P8PIndicator caption={"Рамка"} value={321} variant={P8P_INDICATOR_VARIANT.OUTLINED} />
|
||||||
|
</Stack>
|
||||||
|
<Divider>Подсказка</Divider>
|
||||||
|
<Stack direction={"row"} spacing={2} p={5}>
|
||||||
|
{/* Индикатор (подсказка без форматирования) */}
|
||||||
|
<P8PIndicator
|
||||||
|
caption={"Подсказка (без форматирования)"}
|
||||||
|
value={42}
|
||||||
|
icon={"desktop_windows"}
|
||||||
|
hint={"Ответ на главный вопрос жизни, вселенной и всего такого..."}
|
||||||
|
/>
|
||||||
|
{/* Индикатор (подсказка с форматирование) */}
|
||||||
|
<P8PIndicator
|
||||||
|
caption={"Подсказка (с форматированием)"}
|
||||||
|
value={3.14}
|
||||||
|
icon={"radio_button_unchecked"}
|
||||||
|
hint={`Математическая <b>постоянная</b>, равная <b style='color:red'>отношению</b> <b style='color:green'>длины окружности</b>
|
||||||
|
к её <b style='color:blue'>диаметру</b>:
|
||||||
|
<p style='text-align: center'>π = <span style='color:green'>L</span>/<span style='color:blue'>d</span></p>`}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Divider>Активность</Divider>
|
||||||
|
<Stack direction={"row"} spacing={2} p={5}>
|
||||||
|
{[P8P_INDICATOR_STATE.UNDEFINED, P8P_INDICATOR_STATE.OK, P8P_INDICATOR_STATE.WARN, P8P_INDICATOR_STATE.ERR].map(
|
||||||
|
(indicatorState, i) => (
|
||||||
|
<P8PIndicator
|
||||||
|
key={i}
|
||||||
|
caption={`Нажми на меня #${i + 1}`}
|
||||||
|
value={i + 1}
|
||||||
|
state={indicatorState}
|
||||||
|
icon={"chat"}
|
||||||
|
onClick={() => showMsgInfo(`Нажатие на индикатор #${i + 1}`)}
|
||||||
|
hint={`Подсказка индикатора #${i + 1}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
<Divider>Пользовательские цвета</Divider>
|
||||||
|
<Stack direction={"row"} spacing={2} p={5}>
|
||||||
|
{[
|
||||||
|
["yellow", "black"],
|
||||||
|
["darkred", "yellow"],
|
||||||
|
["orange", "darkblue"],
|
||||||
|
["magenta", "darkmagenta"]
|
||||||
|
].map((userColor, i) => (
|
||||||
|
<P8PIndicator
|
||||||
|
key={i}
|
||||||
|
caption={`Текст: ${userColor[0]}, Заливка: ${userColor[1]}`}
|
||||||
|
value={i + 1}
|
||||||
|
state={P8P_INDICATOR_STATE.WARN}
|
||||||
|
icon={"palette"}
|
||||||
|
color={userColor[0]}
|
||||||
|
backgroundColor={userColor[1]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Пример: Индикатор "P8PIndicator"
|
||||||
|
Indicator.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { Indicator };
|
@ -19,6 +19,7 @@ import { Chart } from "./chart"; //Пример: Графики "P8PChart"
|
|||||||
import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt"
|
import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt"
|
||||||
import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG"
|
import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG"
|
||||||
import { Cyclogram } from "./cyclogram"; //Пример: Циклограмма "P8PCyclogram"
|
import { Cyclogram } from "./cyclogram"; //Пример: Циклограмма "P8PCyclogram"
|
||||||
|
import { Indicator } from "./indicator"; //Пример: Индикатор "P8PIndicator"
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -34,7 +35,8 @@ const MODES = {
|
|||||||
CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart },
|
CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart },
|
||||||
GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt },
|
GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt },
|
||||||
SVG: { name: "SVG", caption: 'Интерактивные изображения "P8PSVG"', component: Svg },
|
SVG: { name: "SVG", caption: 'Интерактивные изображения "P8PSVG"', component: Svg },
|
||||||
CYCLOGRAM: { name: "CYCLOGRAM", caption: 'Циклограмма "P8PCyclogram"', component: Cyclogram }
|
CYCLOGRAM: { name: "CYCLOGRAM", caption: 'Циклограмма "P8PCyclogram"', component: Cyclogram },
|
||||||
|
INDICATOR: { name: "INDICATOR", caption: 'Индикатор "P8PIndicator"', component: Indicator }
|
||||||
};
|
};
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
|
127
db/PKG_P8PANELS_EDITOR.pck
Normal file
127
db/PKG_P8PANELS_EDITOR.pck
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
create or replace package PKG_P8PANELS_EDITOR as
|
||||||
|
|
||||||
|
/* Список аргументов пользовательской процедуры */
|
||||||
|
procedure USERPROCS_DESC
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод пользовательской процедуры
|
||||||
|
COUT out clob -- Сериализованный список аргументов
|
||||||
|
);
|
||||||
|
|
||||||
|
end PKG_P8PANELS_EDITOR;
|
||||||
|
/
|
||||||
|
create or replace package body PKG_P8PANELS_EDITOR as
|
||||||
|
|
||||||
|
/* Описание пользовательской процедуры */
|
||||||
|
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;
|
||||||
|
|
||||||
|
end PKG_P8PANELS_EDITOR;
|
||||||
|
/
|
@ -9,14 +9,26 @@ create or replace package PKG_P8PANELS_VISUAL as
|
|||||||
SORDER_DIRECTION_ASC constant PKG_STD.TSTRING := 'ASC'; -- По возрастанию
|
SORDER_DIRECTION_ASC constant PKG_STD.TSTRING := 'ASC'; -- По возрастанию
|
||||||
SORDER_DIRECTION_DESC constant PKG_STD.TSTRING := 'DESC'; -- По убыванию
|
SORDER_DIRECTION_DESC constant PKG_STD.TSTRING := 'DESC'; -- По убыванию
|
||||||
|
|
||||||
/* Константы - масштаб диаграммы Ганта */
|
/* Константы - диаграмма Ганта - масштаб */
|
||||||
NGANTT_ZOOM_QUARTER_DAY constant PKG_STD.TNUMBER := 0; -- Четверть дня
|
NGANTT_ZOOM_QUARTER_DAY constant PKG_STD.TNUMBER := 0; -- Четверть дня
|
||||||
NGANTT_ZOOM_HALF_DAY constant PKG_STD.TNUMBER := 1; -- Пол дня
|
NGANTT_ZOOM_HALF_DAY constant PKG_STD.TNUMBER := 1; -- Пол дня
|
||||||
NGANTT_ZOOM_DAY constant PKG_STD.TNUMBER := 2; -- День
|
NGANTT_ZOOM_DAY constant PKG_STD.TNUMBER := 2; -- День
|
||||||
NGANTT_ZOOM_WEEK constant PKG_STD.TNUMBER := 3; -- Неделя
|
NGANTT_ZOOM_WEEK constant PKG_STD.TNUMBER := 3; -- Неделя
|
||||||
NGANTT_ZOOM_MONTH constant PKG_STD.TNUMBER := 4; -- Месяц
|
NGANTT_ZOOM_MONTH constant PKG_STD.TNUMBER := 4; -- Месяц
|
||||||
|
|
||||||
/* Константы - масштаб циклограммы */
|
/* Константы - график - тип */
|
||||||
|
SCHART_TYPE_BAR constant PKG_STD.TSTRING := 'bar'; -- Столбчатая
|
||||||
|
SCHART_TYPE_LINE constant PKG_STD.TSTRING := 'line'; -- Линейная
|
||||||
|
SCHART_TYPE_PIE constant PKG_STD.TSTRING := 'pie'; -- Круговая
|
||||||
|
SCHART_TYPE_DOUGHNUT constant PKG_STD.TSTRING := 'doughnut'; -- Кольцевая
|
||||||
|
|
||||||
|
/* Константы - график - расположение легенды */
|
||||||
|
SCHART_LGND_POS_LEFT constant PKG_STD.TSTRING := 'left'; -- Слева
|
||||||
|
SCHART_LGND_POS_RIGHT constant PKG_STD.TSTRING := 'right'; -- Справа
|
||||||
|
SCHART_LGND_POS_TOP constant PKG_STD.TSTRING := 'top'; -- Наверху
|
||||||
|
SCHART_LGND_POS_BOTTOM constant PKG_STD.TSTRING := 'bottom'; -- Внизу
|
||||||
|
|
||||||
|
/* Константы - циклограмма - масштаб */
|
||||||
NCYCLOGRAM_ZOOM_MIN constant PKG_STD.TLNUMBER := 0.2; -- Минимальный (0.2 от исходного)
|
NCYCLOGRAM_ZOOM_MIN constant PKG_STD.TLNUMBER := 0.2; -- Минимальный (0.2 от исходного)
|
||||||
NCYCLOGRAM_ZOOM_TINY constant PKG_STD.TLNUMBER := 0.4; -- Мелкий (0.4 от исходного)
|
NCYCLOGRAM_ZOOM_TINY constant PKG_STD.TLNUMBER := 0.4; -- Мелкий (0.4 от исходного)
|
||||||
NCYCLOGRAM_ZOOM_SMALL constant PKG_STD.TLNUMBER := 0.7; -- Уменьшенный (0.7 от исходного)
|
NCYCLOGRAM_ZOOM_SMALL constant PKG_STD.TLNUMBER := 0.7; -- Уменьшенный (0.7 от исходного)
|
||||||
@ -25,23 +37,21 @@ create or replace package PKG_P8PANELS_VISUAL as
|
|||||||
NCYCLOGRAM_ZOOM_HUGE constant PKG_STD.TLNUMBER := 2; -- Большой (2 от исходного)
|
NCYCLOGRAM_ZOOM_HUGE constant PKG_STD.TLNUMBER := 2; -- Большой (2 от исходного)
|
||||||
NCYCLOGRAM_ZOOM_MAX constant PKG_STD.TLNUMBER := 2.5; -- Максимальный (2.5 от исходного)
|
NCYCLOGRAM_ZOOM_MAX constant PKG_STD.TLNUMBER := 2.5; -- Максимальный (2.5 от исходного)
|
||||||
|
|
||||||
/* Константы - тип графика */
|
/* Константы - циклограмма - оформление */
|
||||||
SCHART_TYPE_BAR constant PKG_STD.TSTRING := 'bar'; -- Столбчатая
|
|
||||||
SCHART_TYPE_LINE constant PKG_STD.TSTRING := 'line'; -- Линейная
|
|
||||||
SCHART_TYPE_PIE constant PKG_STD.TSTRING := 'pie'; -- Круговая
|
|
||||||
SCHART_TYPE_DOUGHNUT constant PKG_STD.TSTRING := 'doughnut'; -- Кольцевая
|
|
||||||
|
|
||||||
/* Константы - расположение легенды графика */
|
|
||||||
SCHART_LGND_POS_LEFT constant PKG_STD.TSTRING := 'left'; -- Слева
|
|
||||||
SCHART_LGND_POS_RIGHT constant PKG_STD.TSTRING := 'right'; -- Справа
|
|
||||||
SCHART_LGND_POS_TOP constant PKG_STD.TSTRING := 'top'; -- Наверху
|
|
||||||
SCHART_LGND_POS_BOTTOM constant PKG_STD.TSTRING := 'bottom'; -- Внизу
|
|
||||||
|
|
||||||
/* Константы - циклограмма */
|
|
||||||
NCYCLOGRAM_GROUP_DEF_WIDTH constant PKG_STD.TNUMBER := 100; -- Высота заголовка группы (по умолчанию)
|
NCYCLOGRAM_GROUP_DEF_WIDTH constant PKG_STD.TNUMBER := 100; -- Высота заголовка группы (по умолчанию)
|
||||||
NCYCLOGRAM_GROUP_DEF_HEIGHT constant PKG_STD.TNUMBER := 42; -- Ширина заголовка группы (по умолчанию)
|
NCYCLOGRAM_GROUP_DEF_HEIGHT constant PKG_STD.TNUMBER := 42; -- Ширина заголовка группы (по умолчанию)
|
||||||
NCYCLOGRAM_LINE_HEIGHT constant PKG_STD.TNUMBER := 20; -- Высота строк циклограммы
|
NCYCLOGRAM_LINE_HEIGHT constant PKG_STD.TNUMBER := 20; -- Высота строк циклограммы
|
||||||
|
|
||||||
|
/* Константы - индикатор - состояния */
|
||||||
|
SINDICATOR_STATE_UNDEFINED constant PKG_STD.TSTRING := 'UNDEFINED'; -- Неопределено
|
||||||
|
SINDICATOR_STATE_OK constant PKG_STD.TSTRING := 'OK'; -- Позитивное
|
||||||
|
SINDICATOR_STATE_ERR constant PKG_STD.TSTRING := 'ERR'; -- Негативное
|
||||||
|
SINDICATOR_STATE_WARN constant PKG_STD.TSTRING := 'WARN'; -- Пограничное
|
||||||
|
|
||||||
|
/* Константы - индикатор - варианты исполнения */
|
||||||
|
SINDICATOR_VARIANT_ELEVATION constant PKG_STD.TSTRING := 'elevation'; -- Парящий
|
||||||
|
SINDICATOR_VARIANT_OUTLINED constant PKG_STD.TSTRING := 'outlined'; -- Плоский с рамкой
|
||||||
|
|
||||||
/* Типы данных - значение колонки таблицы данных */
|
/* Типы данных - значение колонки таблицы данных */
|
||||||
type TDG_COL_VAL is record
|
type TDG_COL_VAL is record
|
||||||
(
|
(
|
||||||
@ -333,6 +343,21 @@ create or replace package PKG_P8PANELS_VISUAL as
|
|||||||
RTASK_ATTRS TCYCLOGRAM_TASK_ATTRS -- Описание атрибутов карточки задачи
|
RTASK_ATTRS TCYCLOGRAM_TASK_ATTRS -- Описание атрибутов карточки задачи
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* Типы данных - индикатор */
|
||||||
|
type TINDICATOR is record
|
||||||
|
(
|
||||||
|
SCAPTION PKG_STD.TSTRING, -- Подпись
|
||||||
|
SVALUE PKG_STD.TSTRING, -- Значение
|
||||||
|
SICON PKG_STD.TSTRING := null, -- Иконка (код шрифта "Google Material Icons" - https://fonts.google.com/icons?icon.set=Material+Icons)
|
||||||
|
SSTATE PKG_STD.TSTRING := SINDICATOR_STATE_UNDEFINED, -- Состояние (см. константы SINDICATOR_STATE_*)
|
||||||
|
BSQUARE boolean := false, -- Скруглять углы
|
||||||
|
NELEVATION PKG_STD.TNUMBER := 3, -- Высота парения (от 0 до 24, игнорируется для SINDICATOR_VARIANT_OUTLINED)
|
||||||
|
SVARIANT PKG_STD.TSTRING := SINDICATOR_VARIANT_ELEVATION, -- Вариант исполнения (см. константы SINDICATOR_VARIANT_*)
|
||||||
|
SHINT PKG_STD.TSTRING := null, -- Подсказка
|
||||||
|
SBACKGROUND_COLOR PKG_STD.TSTRING := null, -- Цвет заливки (HTML-код, null - использовать цвет по умолчанию для состояния)
|
||||||
|
SCOLOR PKG_STD.TSTRING := null -- Цвет шрифта и иконки (HTML-код, null - использовать цвет по умолчанию для состояния)
|
||||||
|
);
|
||||||
|
|
||||||
/* Расчет диапаона выдаваемых записей */
|
/* Расчет диапаона выдаваемых записей */
|
||||||
procedure UTL_ROWS_LIMITS_CALC
|
procedure UTL_ROWS_LIMITS_CALC
|
||||||
(
|
(
|
||||||
@ -750,6 +775,27 @@ create or replace package PKG_P8PANELS_VISUAL as
|
|||||||
NINCLUDE_DEF in number := 1 -- Включить описание колонок (0 - нет, 1 - да)
|
NINCLUDE_DEF in number := 1 -- Включить описание колонок (0 - нет, 1 - да)
|
||||||
) return clob; -- XML-описание
|
) return clob; -- XML-описание
|
||||||
|
|
||||||
|
/* Формирование индикатора */
|
||||||
|
function TINDICATOR_MAKE
|
||||||
|
(
|
||||||
|
SCAPTION in varchar2, -- Подпись
|
||||||
|
SVALUE in varchar2, -- Значение
|
||||||
|
SICON in varchar2 := null, -- Иконка (код шрифта "Google Material Icons" - https://fonts.google.com/icons?icon.set=Material+Icons)
|
||||||
|
SSTATE in varchar2 := SINDICATOR_STATE_UNDEFINED, -- Состояние (см. константы SINDICATOR_STATE_*)
|
||||||
|
BSQUARE in boolean := false, -- Скруглять углы
|
||||||
|
NELEVATION in number := 3, -- Высота парения (от 0 до 24, игнорируется для SINDICATOR_VARIANT_OUTLINED)
|
||||||
|
SVARIANT in varchar2 := SINDICATOR_VARIANT_ELEVATION, -- Вариант исполнения (см. константы SINDICATOR_VARIANT_*)
|
||||||
|
SHINT in varchar2 := null, -- Подсказка
|
||||||
|
SBACKGROUND_COLOR in varchar2 := null, -- Цвет заливки (HTML-код, null - использовать цвет по умолчанию для состояния)
|
||||||
|
SCOLOR in varchar2 := null -- Цвет шрифта и иконки (HTML-код, null - использовать цвет по умолчанию для состояния)
|
||||||
|
) return TINDICATOR; -- Результат работы
|
||||||
|
|
||||||
|
/* Сериализация индикатора */
|
||||||
|
function TINDICATOR_TO_XML
|
||||||
|
(
|
||||||
|
RINDICATOR in TINDICATOR -- Описание индикатора
|
||||||
|
) return clob; -- XML-описание
|
||||||
|
|
||||||
end PKG_P8PANELS_VISUAL;
|
end PKG_P8PANELS_VISUAL;
|
||||||
/
|
/
|
||||||
create or replace package body PKG_P8PANELS_VISUAL as
|
create or replace package body PKG_P8PANELS_VISUAL as
|
||||||
@ -769,6 +815,7 @@ create or replace package body PKG_P8PANELS_VISUAL as
|
|||||||
SRESP_TAG_XDATA_GRID constant PKG_STD.TSTRING := 'XDATA_GRID'; -- Тэг для описания таблицы данных
|
SRESP_TAG_XDATA_GRID constant PKG_STD.TSTRING := 'XDATA_GRID'; -- Тэг для описания таблицы данных
|
||||||
SRESP_TAG_XCYCLOGRAM constant PKG_STD.TSTRING := 'XCYCLOGRAM'; -- Тэг для описания циклограммы
|
SRESP_TAG_XCYCLOGRAM constant PKG_STD.TSTRING := 'XCYCLOGRAM'; -- Тэг для описания циклограммы
|
||||||
SRESP_TAG_XGANTT constant PKG_STD.TSTRING := 'XGANTT'; -- Тэг для описания диаграммы Ганта
|
SRESP_TAG_XGANTT constant PKG_STD.TSTRING := 'XGANTT'; -- Тэг для описания диаграммы Ганта
|
||||||
|
SRESP_TAG_XINDICATOR constant PKG_STD.TSTRING := 'XINDICATOR'; -- Тэг для описания индикатора
|
||||||
|
|
||||||
/* Константы - атрибуты ответов (универсальные) */
|
/* Константы - атрибуты ответов (универсальные) */
|
||||||
SRESP_ATTR_NAME constant PKG_STD.TSTRING := 'name'; -- Атрибут для наименования
|
SRESP_ATTR_NAME constant PKG_STD.TSTRING := 'name'; -- Атрибут для наименования
|
||||||
@ -801,6 +848,14 @@ create or replace package body PKG_P8PANELS_VISUAL as
|
|||||||
SRESP_ATTR_ROWS constant PKG_STD.TSTRING := 'rows'; -- Атрибут для строк данных
|
SRESP_ATTR_ROWS constant PKG_STD.TSTRING := 'rows'; -- Атрибут для строк данных
|
||||||
SRESP_ATTR_COLUMNS_DEF constant PKG_STD.TSTRING := 'columnsDef'; -- Атрибут для описания колонок
|
SRESP_ATTR_COLUMNS_DEF constant PKG_STD.TSTRING := 'columnsDef'; -- Атрибут для описания колонок
|
||||||
SRESP_ATTR_GROUPS constant PKG_STD.TSTRING := 'groups'; -- Атрибут для описания групп
|
SRESP_ATTR_GROUPS constant PKG_STD.TSTRING := 'groups'; -- Атрибут для описания групп
|
||||||
|
SRESP_ATTR_VALUE constant PKG_STD.TSTRING := 'value'; -- Атрибут для значения
|
||||||
|
SRESP_ATTR_ICON constant PKG_STD.TSTRING := 'icon'; -- Атрибут для иконки
|
||||||
|
SRESP_ATTR_STATE constant PKG_STD.TSTRING := 'state'; -- Атрибут для состояния
|
||||||
|
SRESP_ATTR_SQUARE constant PKG_STD.TSTRING := 'square'; -- Атрибут для флага скругления
|
||||||
|
SRESP_ATTR_ELEVATION constant PKG_STD.TSTRING := 'elevation'; -- Атрибут для высоты парения
|
||||||
|
SRESP_ATTR_VARIANT constant PKG_STD.TSTRING := 'variant'; -- Атрибут для варианта исполнения
|
||||||
|
SRESP_ATTR_BG_COLOR constant PKG_STD.TSTRING := 'backgroundColor'; -- Атрибут для цвета заливки
|
||||||
|
SRESP_ATTR_COLOR constant PKG_STD.TSTRING := 'color'; -- Атрибут для цвета
|
||||||
|
|
||||||
/* Константы - атрибуты ответов (таблица данных) */
|
/* Константы - атрибуты ответов (таблица данных) */
|
||||||
SRESP_ATTR_DT_ORDER constant PKG_STD.TSTRING := 'order'; -- Атрибут для флага сортировки
|
SRESP_ATTR_DT_ORDER constant PKG_STD.TSTRING := 'order'; -- Атрибут для флага сортировки
|
||||||
@ -3069,5 +3124,93 @@ create or replace package body PKG_P8PANELS_VISUAL as
|
|||||||
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
||||||
end TCYCLOGRAM_TO_XML;
|
end TCYCLOGRAM_TO_XML;
|
||||||
|
|
||||||
|
/* Формирование индикатора */
|
||||||
|
function TINDICATOR_MAKE
|
||||||
|
(
|
||||||
|
SCAPTION in varchar2, -- Подпись
|
||||||
|
SVALUE in varchar2, -- Значение
|
||||||
|
SICON in varchar2 := null, -- Иконка (код шрифта "Google Material Icons" - https://fonts.google.com/icons?icon.set=Material+Icons)
|
||||||
|
SSTATE in varchar2 := SINDICATOR_STATE_UNDEFINED, -- Состояние (см. константы SINDICATOR_STATE_*)
|
||||||
|
BSQUARE in boolean := false, -- Скруглять углы
|
||||||
|
NELEVATION in number := 3, -- Высота парения (от 0 до 24, игнорируется для SINDICATOR_VARIANT_OUTLINED)
|
||||||
|
SVARIANT in varchar2 := SINDICATOR_VARIANT_ELEVATION, -- Вариант исполнения (см. константы SINDICATOR_VARIANT_*)
|
||||||
|
SHINT in varchar2 := null, -- Подсказка
|
||||||
|
SBACKGROUND_COLOR in varchar2 := null, -- Цвет заливки (HTML-код, null - использовать цвет по умолчанию для состояния)
|
||||||
|
SCOLOR in varchar2 := null -- Цвет шрифта и иконки (HTML-код, null - использовать цвет по умолчанию для состояния)
|
||||||
|
) return TINDICATOR -- Результат работы
|
||||||
|
is
|
||||||
|
RRES TINDICATOR; -- Буфер для результата
|
||||||
|
begin
|
||||||
|
/* Проверим параметры */
|
||||||
|
if ((SSTATE is not null) and
|
||||||
|
(SSTATE not in (SINDICATOR_STATE_UNDEFINED, SINDICATOR_STATE_OK, SINDICATOR_STATE_ERR, SINDICATOR_STATE_WARN))) then
|
||||||
|
P_EXCEPTION(0, 'Некорректно указано значение "Состояние".');
|
||||||
|
end if;
|
||||||
|
if ((NELEVATION is not null) and ((NELEVATION <> TRUNC(NELEVATION)) or (NELEVATION < 0) or (NELEVATION > 24))) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Некорректно указано значение "Высота парения" (ожидается целое число от 0 до 24).');
|
||||||
|
end if;
|
||||||
|
if ((SVARIANT is not null) and (SVARIANT not in (SINDICATOR_VARIANT_ELEVATION, SINDICATOR_VARIANT_OUTLINED))) then
|
||||||
|
P_EXCEPTION(0, 'Некорректно указано значение "Вариант исполнения".');
|
||||||
|
end if;
|
||||||
|
/* Формируем объект */
|
||||||
|
RRES.SCAPTION := SCAPTION;
|
||||||
|
RRES.SVALUE := SVALUE;
|
||||||
|
RRES.SICON := SICON;
|
||||||
|
RRES.SSTATE := COALESCE(SSTATE, SINDICATOR_STATE_UNDEFINED);
|
||||||
|
RRES.BSQUARE := BSQUARE;
|
||||||
|
RRES.NELEVATION := COALESCE(NELEVATION, 3);
|
||||||
|
RRES.SVARIANT := COALESCE(SVARIANT, SINDICATOR_VARIANT_ELEVATION);
|
||||||
|
RRES.SHINT := SHINT;
|
||||||
|
RRES.SBACKGROUND_COLOR := SBACKGROUND_COLOR;
|
||||||
|
RRES.SCOLOR := SCOLOR;
|
||||||
|
/* Возвращаем результат */
|
||||||
|
return RRES;
|
||||||
|
end TINDICATOR_MAKE;
|
||||||
|
|
||||||
|
/* Сериализация индикатора */
|
||||||
|
function TINDICATOR_TO_XML
|
||||||
|
(
|
||||||
|
RINDICATOR in TINDICATOR -- Описание индикатора
|
||||||
|
) return clob -- XML-описание
|
||||||
|
is
|
||||||
|
CRES clob; -- Буфер для результата
|
||||||
|
begin
|
||||||
|
/* Начинаем формирование XML */
|
||||||
|
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
|
||||||
|
/* Открываем корень */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => SRESP_TAG_XDATA);
|
||||||
|
/* Открываем индикатор */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => SRESP_TAG_XINDICATOR);
|
||||||
|
/* Формируем атрибуты */
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_CAPTION, SVALUE => RINDICATOR.SCAPTION);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_VALUE, SVALUE => RINDICATOR.SVALUE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_ICON, SVALUE => RINDICATOR.SICON);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_STATE, SVALUE => RINDICATOR.SSTATE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_SQUARE, BVALUE => RINDICATOR.BSQUARE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_ELEVATION, NVALUE => RINDICATOR.NELEVATION);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_VARIANT, SVALUE => RINDICATOR.SVARIANT);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_HINT, SVALUE => RINDICATOR.SHINT);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_BG_COLOR, SVALUE => RINDICATOR.SBACKGROUND_COLOR);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SRESP_ATTR_COLOR, SVALUE => RINDICATOR.SCOLOR);
|
||||||
|
/* Закрываем индикатор */
|
||||||
|
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 TINDICATOR_TO_XML;
|
||||||
|
|
||||||
end PKG_P8PANELS_VISUAL;
|
end PKG_P8PANELS_VISUAL;
|
||||||
/
|
/
|
||||||
|
797
dist/p8-panels.js
vendored
797
dist/p8-panels.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
BIN
docs/img/74.png
Normal file
BIN
docs/img/74.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
274
package-lock.json
generated
274
package-lock.json
generated
@ -17,6 +17,7 @@
|
|||||||
"@mui/x-tree-view": "^7.11.0",
|
"@mui/x-tree-view": "^7.11.0",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"chart.js": "^4.4.0",
|
"chart.js": "^4.4.0",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"eslint": "^8.46.0",
|
"eslint": "^8.46.0",
|
||||||
"eslint-plugin-react": "^7.33.1",
|
"eslint-plugin-react": "^7.33.1",
|
||||||
@ -27,7 +28,9 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-grid-layout": "^1.5.1",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
"webpack": "^5.88.2",
|
"webpack": "^5.88.2",
|
||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
}
|
||||||
@ -1872,6 +1875,62 @@
|
|||||||
"tiny-invariant": "^1.0.6"
|
"tiny-invariant": "^1.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-loader": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==",
|
||||||
|
"dependencies": {
|
||||||
|
"icss-utils": "^5.1.0",
|
||||||
|
"postcss": "^8.4.33",
|
||||||
|
"postcss-modules-extract-imports": "^3.1.0",
|
||||||
|
"postcss-modules-local-by-default": "^4.0.5",
|
||||||
|
"postcss-modules-scope": "^3.2.0",
|
||||||
|
"postcss-modules-values": "^4.0.0",
|
||||||
|
"postcss-value-parser": "^4.2.0",
|
||||||
|
"semver": "^7.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@rspack/core": "0.x || 1.x",
|
||||||
|
"webpack": "^5.27.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@rspack/core": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"webpack": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-loader/node_modules/semver": {
|
||||||
|
"version": "7.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||||
|
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cssesc": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
|
"bin": {
|
||||||
|
"cssesc": "bin/cssesc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
@ -2523,6 +2582,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-equals": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg=="
|
||||||
|
},
|
||||||
"node_modules/fast-json-stable-stringify": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||||
@ -2945,6 +3009,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/icss-utils": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >= 14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||||
@ -3694,6 +3769,23 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/nanoid": {
|
||||||
|
"version": "3.3.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
|
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||||
@ -4027,6 +4119,105 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss": {
|
||||||
|
"version": "8.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||||
|
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": "^3.3.8",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"source-map-js": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-modules-extract-imports": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >= 14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-modules-local-by-default": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==",
|
||||||
|
"dependencies": {
|
||||||
|
"icss-utils": "^5.0.0",
|
||||||
|
"postcss-selector-parser": "^7.0.0",
|
||||||
|
"postcss-value-parser": "^4.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >= 14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-modules-scope": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==",
|
||||||
|
"dependencies": {
|
||||||
|
"postcss-selector-parser": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >= 14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-modules-values": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"icss-utils": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >= 14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-selector-parser": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
|
||||||
|
"dependencies": {
|
||||||
|
"cssesc": "^3.0.0",
|
||||||
|
"util-deprecate": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@ -4148,6 +4339,44 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-draggable": {
|
||||||
|
"version": "4.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
|
||||||
|
"integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^1.1.1",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.3.0",
|
||||||
|
"react-dom": ">= 16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-draggable/node_modules/clsx": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-grid-layout": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-4Fr+kKMk0+m1HL/BWfHxi/lRuaOmDNNKQDcu7m12+NEYcen20wIuZFo789u3qWCyvUsNUxCiyf0eKq4WiJSNYw==",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"fast-equals": "^4.0.3",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-draggable": "^4.4.5",
|
||||||
|
"react-resizable": "^3.0.5",
|
||||||
|
"resize-observer-polyfill": "^1.5.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.3.0",
|
||||||
|
"react-dom": ">= 16.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
@ -4182,6 +4411,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-resizable": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "15.x",
|
||||||
|
"react-draggable": "^4.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "6.25.1",
|
"version": "6.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz",
|
||||||
@ -4296,6 +4537,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/resize-observer-polyfill": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
@ -4595,6 +4841,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map-js": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-support": {
|
"node_modules/source-map-support": {
|
||||||
"version": "0.5.21",
|
"version": "0.5.21",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
@ -4736,6 +4990,21 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/style-loader": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"webpack": "^5.27.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/stylis": {
|
"node_modules/stylis": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||||
@ -4994,6 +5263,11 @@
|
|||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"@mui/x-tree-view": "^7.11.0",
|
"@mui/x-tree-view": "^7.11.0",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"chart.js": "^4.4.0",
|
"chart.js": "^4.4.0",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"eslint": "^8.46.0",
|
"eslint": "^8.46.0",
|
||||||
"eslint-plugin-react": "^7.33.1",
|
"eslint-plugin-react": "^7.33.1",
|
||||||
@ -37,7 +38,9 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-grid-layout": "^1.5.1",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
"webpack": "^5.88.2",
|
"webpack": "^5.88.2",
|
||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,8 @@ module.exports = {
|
|||||||
options: {
|
options: {
|
||||||
name: "[path][name].[hash].[ext]"
|
name: "[path][name].[hash].[ext]"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{ test: /\.css$/, use: ["style-loader", "css-loader"] }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user