Compare commits

..

2 Commits
main ... main

76 changed files with 4401 additions and 13965 deletions

158
README.md
View File

@ -104,9 +104,6 @@
git clone https://git.citpb.ru/CITKParus/P8-Panels.git
```
> **Внимание:** если при клонировании репозитория возникает ошибка "Server certificate verification failed" - используйте ключ `http.sslVerify=false`:\
> `git clone https://git.citpb.ru/CITKParus/P8-Panels.git -c http.sslVerify=false`
6. Проведите компиляцию хранимых объектов БД из каталога "db" клонированного репозитория (компиляцию проводить под пользователем-владельцем схемы серверной части Системы, с последующей перекомпиляцией зависимых инвалидных объектов), затем исполните скрипт "grants.sql", размещённый в этом же каталоге.
7. Перезапустите сервер приложений "ПАРУС 8 Онлайн"
@ -340,59 +337,6 @@ const MyPanel = () => {
8. Выдайте права но новое действие в "Администраторе", при необходимости - начните новый сеанс в "ПАРУС 8 Онлайн" с очисткой системного кэша.
#### Настройка КОР-действия для вызова панели "Выдача сменного задания на участок" из раздела "Сменные задания"
Входящая в состав поставки фреймворка панель "Выдача сменного задания на участок" доступна для вызова из раздела "Сменные задания" (приложение "Планирование и учёт в дискретном производстве", главное меню > "Документы" > "Сменные задания").
Для настройки этой возможности:
1. Откройте раздел "Классы" приложения "Конструктор отраслевых расширений" (главное меню > "Учёт" > "Классы")
2. В дереве классов выберите "Сменные задания", а в списке классов - класс с кодом "CostJobs"
3. В спецификации "Методы", выбранного класса, зарегистрируйте новый метод со следующими атрибутами:
- `Мнемокод` - P8PANELS_OPEN
- `Наименование` - P8PANELS_OPEN
- `Тип метода` - Встроенный
- `Доступность` - Клиентский
4. Для добавленного метода `P8PANELS_OPEN` в спецификации "Параметры" зарегистрируйте следующий набор параметров:
| Имя | Наименование | Тип | Домен | Обязательный | Тип привязки | Контекст | Параметр действия |
| -------- | ------------------- | ------- | ------- | ------------ | ----------------- | -------------------- | ----------------- |
| NRN | Рег. номер записи | Входной | TRN | Нет | Контекст | Идентификатор записи | |
| SPANEL | Наименование панели | Входной | TSTRING | Да | Параметр действия | | SPANEL |
| SCAPTION | Заголовок вкладки | Входной | TSTRING | Нет | Параметр действия | | SCAPTION |
5. В спецификации "Действия", выбранного класса, зарегистрируйте новое действие со следующими атрибутами:
- `Тип` - Нестандартное
- `Код` - FCJOBS_OPEN_JOBS_MANAGE_MP
- `Наименование` - Открытие панели "Выдача сменного задания на участок"
- `Технология производства` - Конструктор
- `Реализующий метод` - P8PANELS_OPEN
- `Обработка записей` - Для одной текущей записи
- `Завершение транзакции` - После каждого вызова действия
- `Обновление выборки` - Не обновлять
6. Для добавленного действия `FCJOBS_OPEN_JOBS_MANAGE_MP` в спецификации "Параметры" зарегистрируйте следующий набор параметров:
| Имя | Домен | Тип привязки | Значение |
| -------- | ------- | ------------ | ---------------------------------- |
| SPANEL | TSTRING | Значение | MechRecCostJobsManageMP |
| SCAPTION | TSTRING | Значение | Выдача сменного задания на участок |
7. Откройте редактор формы представления данных класса "CostJobs" ("Сменные задания").
Для этого отметьте в списке классов запись с кодом "CostJobs", перейдите на закладку "Методы вызова", укажите метод вызова "main", в его контекстном меню укажите "Формы", в появившемся списке форм выполните действие "Редактор" для формы с наименованием "Форма просмотра".
В открывшемся редакторе формы перейдите в режим редактирования всплывающего меню заголовка (закладка "Таблицы", таблица "CostJobs", затем кнопка "Редактор источника", установить фокус на форме представления данных щелчком мыши, затем пункт "Всплывающее меню" в "Инспекторе объектов"). Найдите в меню пункт, созданный Системой для действия, зарегистрированного на шаге 5 (как правило имеет метку, совпадающую с наименованием действия). Расположите (перетаскиванием) этот пункт меню сразу после пункта "Отработать исполнение по штрих-кодам". Укажите для этого пункта следующие параметры в "Инспекторе объектов":
- `Заголовок` - Выдать сменное задание на участок…
Закройте окна редакторов с сохранением изменений.
8. Выдайте права но новое действие в "Администраторе", при необходимости - начните новый сеанс в "ПАРУС 8 Онлайн" с очисткой системного кэша.
#### Настройка КОР-действия для вызова панели "Производственная программа" из раздела "Планы и отчеты производства изделий"
Входящая в состав поставки фреймворка панель "Производственная программа" доступна для вызова из спецификации "Выпуск" раздела "Планы и отчеты производства изделий" (приложение "Планирование и учёт в дискретном производстве", главное меню > "Документы" > "Планы и отчеты производства изделий").
@ -516,7 +460,7 @@ c:\inetpub\p8web20\WebClient\Modules\P8-Panels>npm run build
- `isRespErr` - функция, проверка результата исполнения серверного объекта на наличие ошибок
- `getRespErrMessage` - функция, получение ошибки исполнения серверного объекта
- `getRespPayload` - функция, получение выходных значений, полученных после успешного исполнения
- `executeStored` - функция, асинхронное исполнение хранимой процедуры/функции БД Системы
- `executeStored` -функция, асинхронное исполнение хранимой процедуры/функции БД Системы
- `getConfig` - функция, асинхронное считывание параметров конфигурации, определённых в "p8panels.config" (возвращает их JSON-представление)
При формировании ответов, функции, получающие данные с сервера, возвращают типовые значения:
@ -584,8 +528,7 @@ c:\inetpub\p8web20\WebClient\Modules\P8-Panels>npm run build
throwError = true,
showErrorMessage = true,
fullResponse = false,
spreadOutArguments = true,
signal = null
spreadOutArguments = true
}
```
@ -598,8 +541,7 @@ c:\inetpub\p8web20\WebClient\Modules\P8-Panels>npm run build
`throwError` - необязательный, логический, признак генерации исключения, если `false` - возвращает ошибку в типовом формате\
`showErrorMessage` - необязательный, логический, признак отображения типового клиентского сообщение об ошибке, в случае её возникновения (только если `throwError = true`)\
`fullResponse` - необязательный, логический, признак возврата полного типового ответа сервера, если `false` - возвращается только содержимое `XPAYLOAD`\
`spreadOutArguments` - необязательный, логический, признак "разделения" значений выходных параметров исполняемого обхекта (игнорируется при наличии `respArg`), если `true` - `XPAYLOAD` будет содержать ответ в виде `{"ВЫХОДНОЙ_ПАРАМЕТР1": "ЗНАЧЕНИЕ", "ВЫХОДНОЙ_ПАРАМЕТР2": "ЗНАЧЕНИЕ", ...}`, если `false` - `XPAYLOAD` будет содержать ответ в виде `{XOUT_ARGUMENTS: [{SNAME: "ВЫХОДНОЙ_ПАРАМЕТР1", VALUE: "ЗНАЧЕНИЕ"}, {SNAME: "ВЫХОДНОЙ_ПАРАМЕТР2", VALUE: "ЗНАЧЕНИЕ"}, ...]}`\
`signal` - необязательный, объект, экземпляр `AbortSignal` (например, `AbortController.signal`) для управления прерыванием выполнения запроса
`spreadOutArguments` - необязательный, логический, признак "разделения" значений выходных параметров исполняемого обхекта (игнорируется при наличии `respArg`), если `true` - `XPAYLOAD` будет содержать ответ в виде `{"ВЫХОДНОЙ_ПАРАМЕТР1": "ЗНАЧЕНИЕ", "ВЫХОДНОЙ_ПАРАМЕТР2": "ЗНАЧЕНИЕ", ...}`, если `false` - `XPAYLOAD` будет содержать ответ в виде `{XOUT_ARGUMENTS: [{SNAME: "ВЫХОДНОЙ_ПАРАМЕТР1", VALUE: "ЗНАЧЕНИЕ"}, {SNAME: "ВЫХОДНОЙ_ПАРАМЕТР2", VALUE: "ЗНАЧЕНИЕ"}, ...]}`
**Результат:** объект с данными, размещёнными в `XPAYLOAD` ответа сервера (если `fullResponse = false`) или полный типовой ответ (описан выше).
@ -1093,7 +1035,7 @@ const Mui = ({ title }) => {
![Примеры модальных сообщений](docs/img/63.png)
###### `undefined showMsg(type, text, msgOnOk = null, msgOnCancel = null, fullErrorText = null)`
###### `undefined showMsg(type, text, msgOnOk = null, msgOnCancel = null)`
Отображает модальное окно сообщения заданного типа.
@ -1101,21 +1043,16 @@ const Mui = ({ title }) => {
`type` - обязательный, строка, тип отображаемого сообщения, `information|warning|error` (см. константу `MSG_TYPE` в "app/context/messaging_reducer" и константу `P8P_APP_MESSAGE_VARIANT` в "app/components/p8p_app_message")\
`text` - обязательный, строка, текст отображаемого сообщения\
`msgOnOk` - необязательный, функция, будет вызвана при нажатии на "ОК"/"ЗАКРЫТЬ" в сообщении\
`msgOnCancel` - необязательный, функция, будет вызвана при нажатии на "ОТМЕНА" в сообщении (только для сообщений типа `warning`)\
`fullErrorText` - необязательный, строка, полный текст ошибки, используется только при `type="error"`. Если параметр указан, то в окно ошибки выводится кнопка "Подробнее", по нажатию на которую будет отображаться текст, указанный в данном параметре
`msgOnOk` - необязательный, функция, будет вызвана при нажатии на "ОК"/"ЗАКРЫТЬ" в сообщении
`msgOnCancel` - необязательный, функция, будет вызвана при нажатии на "ОТМЕНА" в сообщении (только для сообщений типа `warning`)
**Результат:** функция не возвращает значимого результата
###### `undefined showMsgErr(text, msgOnOk = null, fullErrorText = null)`
###### `undefined showMsgErr(text, msgOnOk = null)`
Декоратор для `showMsg`, отображает модальное окно сообщения типа "Ошибка" (`type="error"`).
**Входные параметры:**
`text` - обязательный, строка, текст отображаемого сообщения\
`msgOnOk` - необязательный, функция, будет вызвана при нажатии на "ЗАКРЫТЬ" в сообщении\
`fullErrorText` - необязательный, строка, полный текст ошибки. Если параметр указан, то в окно ошибки выводится кнопка "Подробнее", по нажатию на которую будет отображаться текст, указанный в данном параметре
**Входные параметры:** аналогично `showMsg`
**Результат:** аналогично `showMsg`
@ -1203,20 +1140,6 @@ const Messages = ({ title }) => {
Ошибка
</Button>
<Divider sx={STYLES.DIVIDER} />
{/* Сообщение об ошибке (диалог с подробностями) */}
<Button
variant="contained"
onClick={() =>
showMsgErr(
"Что-то пошло не так :( ...но мы точно знаем что ;)",
null,
"Здесь подробная информация об ошибке (стек вызова СУБД, например)"
)
}
>
Ошибка с подробностями
</Button>
<Divider sx={STYLES.DIVIDER} />
{/* Предупреждение (диалог) */}
<Button
variant="contained"
@ -1267,7 +1190,7 @@ const Messages = ({ title }) => {
![Пример индикатора процесса](docs/img/65.png)
###### `undefined showLoader(message)`
###### `undefined showMsg(message)`
Отображает модальный индикатор процесса с указанным сообщением.
@ -1334,14 +1257,14 @@ const Loader = ({ title }) => {
- состоят из значительного числа интерфейсных примитивов
- имеют специальный API на стороне сервера БД Системы для управления их содержимым
Необходимо понимать, что с одной стороны, наличие серверного API в БД значительно упрощает взаимодействие с компонентом, с другой стороны - ограничивает возможности его применения только теми прикладными задачами и функциональными возможностями, которые заложены в него. При этом "примитивы" HTML и MUI, хоть и сложнее в применении, но позволяют "собирать" практически любые интерфейсные решения на вкус разработчика.
Необходимо понимать, что с одной стороны, наличие серверного API в БД значительно упрощает взаимодействие с компонентом, с другой стороны - ограничивает возможности его примерения только теми прикладными задачами и функциональными возможностями, которые заложены в него. При этом "примитивы" HTML и MUI, хоть и сложнее в применении, но позволяют "собирать" практически любые интерфейсные решения на вкус разработчика.
##### Таблица данных "P8PDataGrid"
Предназначена для формирования табличных представлений данных с поддержкой:
- постраничного вывода данных
- сортировки и отбора данных по колонкам на стороне сервера БД
- сортировки и отбора данных по колонкам на строне сервера БД
- сложных заголовков с возможностью отображения/сокрытия уровней
- разворачивающихся строк (accordion)
- группировки строк с возможностью отображения/сокрытия содержимого группы
@ -1368,7 +1291,6 @@ 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: <ШИРИНА_КОЛОНКИ>}`\
`filtersInitial` - необязательныей, массив, начальное состояние фильтров таблицы, содержит объекты вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, from: <НАЧАЛО_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>, to: <ОКОНЧАНИЕ_ДИАПАЗОНА_ЗНАЧЕНИЙ_ФИЛЬТРА>}`\
`groups` - необязательный, массив групп данных, содержит объекты вида `{name: <ИМЯ_ГРУППЫ>, caption: <ЗАГОЛОВОКРУППЫ>, expandable: <ПРИЗНАК_РАЗВОРАЧИВАЕМОСТИ_ГРУППЫ - true|false>, expanded: <ПРИЗНАК_РАЗВЕРНУТОСТИ_ГРУППЫ - true|false>}`\
@ -1978,7 +1900,7 @@ const Chart = ({ title }) => {
};
```
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/chart.js" данного репозитория соответственно.
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/data_grid.js" данного репозитория соответственно.
##### Диаграмма ганта "P8PGantt"
@ -2031,7 +1953,6 @@ const MyPanel = () => {
`onTaskProgressChange` - необязательный, функция, если указана - будет вызвана при изменении прогресса исполнения элемента диаграммы, сигнатура функции `f({task, progress})`, результат функции не интерпретируется. В функцию будет передан объект в поле `task`, которого, будет содержаться описание изменённой задачи (элемент массива `tasks`, см. выше описание полей), в поле `progress` - новое значение прогресса исполнения задачи.\
`taskAttributeRenderer` - необязательный, функция, если указана - будет вызвана при отображении диалога редактора здачи, результат функции будет применён для отображения области дополнительных атрибутов задачи в диалоге редактора, если не указана - дополнительные атрибуты будут отображены с форматированием по умолчанию. Сигнатура функции - `f({task, attribute})`, в функцию будет передан объект в поле `task`, которого, будет содержаться описание задачи для которой отображается редактор (элемент массива `tasks`, см. выше описание полей), в поле `attribute` - описание дополнительного атрибута формируемого в диалоге редактора (элемент массива `taskAttributes`, см. выше описание полей). Должна возвращать значение или React-компонент.\
`taskDialogRenderer` - необязательный, функция, если указана - будет вызвана до отображения диалога редактора задачи. Результат функции будет показан в качестве содержимого диалога редактора, вместо типовой формы. Сигнатура функции - `f({task, taskAttributes, taskColors, close})`, в функцию будет передан объект в поле `task`, которого, будет содержаться описание задачи для которой отображается редактор (элемент массива `tasks`, см. выше описание полей), в поле `taskAttributes` - массив `taskAttributes` (см. выше описание полей), описывающий состав полей задачи, в поле `taskColors` - массив `taskColors` (см. выше описание полей), описывающий цвета заливки, определённые для задачи, в поле `close` - функция закрытия диалога задачи, может быть вызвана возвращаемым Reac-компонентом для сокрытия диалога. Должна возвращать значение или React-компонент.\
`taskDialogProps` - необязательный, объект, содержит свойства, которые будут переданы компоненту-контейнеру (`Dialog`) редактора задачи.\
`noDataFoundText` - обязательный, строка, текст для отображения ошибки об отсутствии данных\
`numbTaskEditorCaption` - обязательный, строка, подпись стандартного атрибута `numb` в диалоге редактора задачи\
`nameTaskEditorCaption` - обязательный, строка, подпись стандартного атрибута `name` в диалоге редактора задачи\
@ -2941,61 +2862,6 @@ export { Cyclogram };
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/cyclogram.js" данного репозитория соответственно.
##### Индикатор "P8PIndicator"
Компонент предназначен для отображения данных в виде индикатора. Поддерживается:
- Цветовая индикация предопределёнными цветами в зависимости от состояния (не определено, позитивное, негативное, пограничное)
- Цветовая индикация пользовательскими цветами
- Обработка нажатий
- Отображение иконки
- Упрвление внешним видом (парение, рамка)
- Интерактивные подсказки
![Пример P8PIndicator](docs/img/74.png)
**Подключение**
Клиентская часть индикатора реализована в компоненте `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. Тем не менее, при разработке пользовательских интерфейсов панелей важно придерживаться предложенных ниже правил. Это позволит создавать их в едином ключе и упростит работу конечного пользователя при их освоении.

View File

@ -3,49 +3,10 @@
Типовые стили
*/
//---------------------
//Подключение библиотек
//---------------------
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 = {
SCROLL: {

View File

@ -18,8 +18,7 @@ export const TITLES = {
//Текст
export const TEXTS = {
LOADING: "Ожидайте...", //Ожидание завершения процесса
NO_DATA_FOUND: "Данных не найдено", //Отсутствие данных
NO_DATA_FOUND_SHORT: "Н.Д." //Отсутствие данных (кратко)
NO_DATA_FOUND: "Данных не найдено" //Отсутствие данных
};
//Текст кнопок
@ -30,15 +29,11 @@ export const BUTTONS = {
OK: "ОК", //Ок
CANCEL: "Отмена", //Отмена
CLOSE: "Закрыть", //Сокрытие
DETAIL: "Подробнее", //Отображение подробностей/детализации
HIDE: "Скрыть", //Скрытие информации
CLEAR: "Очистить", //Очистка
ORDER_ASC: "По возрастанию", //Сортировка по возрастанию
ORDER_DESC: "По убыванию", //Сортировка по убыванию
FILTER: "Фильтр", //Фильтрация
MORE: "Ещё", //Догрузка данных
APPLY: "Применить", //Сохранение без закрытия интерфейса ввода
SAVE: "Сохранить" //Сохранение
MORE: "Ещё" //Догрузка данных
};
//Метки атрибутов, сопроводительные надписи
@ -66,12 +61,3 @@ export const ERRORS = {
export const ERRORS_HTTP = {
404: "Адрес не найден"
};
//Типовые статусы
export const STATE = {
UNDEFINED: "UNDEFINED",
INFO: "INFORMATION",
OK: "OK",
ERR: "ERR",
WARN: "WARN"
};

View File

@ -86,9 +86,6 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
//Подключение к контексту навигации
const { navigateRoot, navigatePanel } = useContext(NavigationCtx);
//Подключение к контексту приложения
const { appState } = useContext(ApplicationСtx);
//Отработка действия навигации домой
const handleHomeNavigate = () => navigateRoot();
@ -101,7 +98,6 @@ const Workspace = ({ panels = [], selectedPanel, children } = {}) => {
{...P8P_APP_WORKSPACE_CONFIG_PROPS}
panels={panels}
selectedPanel={selectedPanel}
caption={appState.appBarTitle}
onHomeNavigate={handleHomeNavigate}
onItemNavigate={handleItemNavigate}
>

View File

@ -7,7 +7,7 @@
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import Dialog from "@mui/material/Dialog"; //базовый класс диалога Material UI
import DialogTitle from "@mui/material/DialogTitle"; //Заголовок диалога
@ -18,8 +18,6 @@ import Typography from "@mui/material/Typography"; //Текст
import Button from "@mui/material/Button"; //Кнопки
import Container from "@mui/material/Container"; //Контейнер
import Box from "@mui/material/Box"; //Обёртка
import { BUTTONS, STATE } from "../../app.text"; //Типовые текстовые ресурсы и константы
import { APP_COLORS } from "../../app.styles"; //Типовые стили
//---------
//Константы
@ -27,9 +25,9 @@ import { APP_COLORS } from "../../app.styles"; //Типовые стили
//Варианты исполнения
const P8P_APP_MESSAGE_VARIANT = {
INFO: STATE.INFO,
WARN: STATE.WARN,
ERR: STATE.ERR
INFO: "information",
WARN: "warning",
ERR: "error"
};
//Стили
@ -38,35 +36,28 @@ const STYLES = {
wordBreak: "break-word"
},
INFO: {
titleText: {
color: APP_COLORS[STATE.INFO].contrColor
},
bodyText: {
color: APP_COLORS[STATE.INFO].contrColor
}
titleText: {},
bodyText: {}
},
WARN: {
titleText: {
color: APP_COLORS[STATE.WARN].contrColor
color: "orange"
},
bodyText: {
color: APP_COLORS[STATE.WARN].contrColor
color: "orange"
}
},
ERR: {
titleText: {
color: APP_COLORS[STATE.ERR].contrColor
color: "red"
},
bodyText: {
color: APP_COLORS[STATE.ERR].contrColor
color: "red"
}
},
INLINE_MESSAGE: {
with: "100%",
textAlign: "center"
},
FULL_ERROR_TEXT_BUTTON: {
color: APP_COLORS[STATE.WARN].contrColor
}
};
@ -75,25 +66,7 @@ const STYLES = {
//-----------
//Сообщение
const P8PAppMessage = ({
variant,
title,
titleText,
cancelBtn,
onCancel,
cancelBtnCaption,
okBtn,
onOk,
okBtnCaption,
open,
text,
fullErrorText,
showErrMoreCaption,
hideErrMoreCaption
}) => {
//Состояние подробной информации об ошибке
const [showFullErrorText, setShowFullErrorText] = useState(false);
const P8PAppMessage = ({ variant, title, titleText, cancelBtn, onCancel, cancelBtnCaption, okBtn, onOk, okBtnCaption, open, text }) => {
//Подбор стиля и ресурсов
let style = STYLES.INFO;
switch (variant) {
@ -113,7 +86,12 @@ const P8PAppMessage = ({
//Заголовок
let titlePart;
if (title && titleText) titlePart = <DialogTitle style={{ ...style.DEFAULT, ...style.titleText }}>{titleText}</DialogTitle>;
if (title && titleText)
titlePart = (
<DialogTitle id="message-dialog-title" style={{ ...style.DEFAULT, ...style.titleText }}>
{titleText}
</DialogTitle>
);
//Кнопка Отмена
let cancelBtnPart;
@ -124,26 +102,16 @@ const P8PAppMessage = ({
let okBtnPart;
if (okBtn && okBtnCaption)
okBtnPart = (
<Button onClick={() => (onOk ? onOk() : null)} autoFocus>
<Button onClick={() => (onOk ? onOk() : null)} color="primary" autoFocus>
{okBtnCaption}
</Button>
);
//Кнопка Подробнее
let fullErrorTextBtn;
if (fullErrorText && showErrMoreCaption && hideErrMoreCaption && variant === P8P_APP_MESSAGE_VARIANT.ERR)
fullErrorTextBtn = (
<Button onClick={() => setShowFullErrorText(!showFullErrorText)} sx={STYLES.FULL_ERROR_TEXT_BUTTON} autoFocus>
{!showFullErrorText ? showErrMoreCaption : hideErrMoreCaption}
</Button>
);
//Все действия
let actionsPart;
if (cancelBtnPart || okBtnPart)
actionsPart = (
<DialogActions>
{fullErrorTextBtn}
{okBtnPart}
{cancelBtnPart}
</DialogActions>
@ -151,10 +119,17 @@ const P8PAppMessage = ({
//Генерация содержимого
return (
<Dialog open={open || false} onClose={() => (onCancel ? onCancel() : null)}>
<Dialog
open={open || false}
aria-labelledby="message-dialog-title"
aria-describedby="message-dialog-description"
onClose={() => (onCancel ? onCancel() : null)}
>
{titlePart}
<DialogContent>
<DialogContentText style={style.bodyText}>{!showFullErrorText ? text : fullErrorText}</DialogContentText>
<DialogContentText id="message-dialog-description" style={style.bodyText}>
{text}
</DialogContentText>
</DialogContent>
{actionsPart}
</Dialog>
@ -173,10 +148,7 @@ P8PAppMessage.propTypes = {
onOk: PropTypes.func,
okBtnCaption: PropTypes.string,
open: PropTypes.bool,
text: PropTypes.string,
fullErrorText: PropTypes.string,
showErrMoreCaption: PropTypes.string,
hideErrMoreCaption: PropTypes.string
text: PropTypes.string
};
//Встроенное сообщение
@ -186,19 +158,13 @@ const P8PAppInlineMessage = ({ variant, text, okBtn, onOk, okBtnCaption }) => {
<Container style={STYLES.INLINE_MESSAGE}>
<Box p={1}>
<Typography
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
}
color={variant === P8P_APP_MESSAGE_VARIANT.ERR ? "error" : variant === P8P_APP_MESSAGE_VARIANT.WARN ? "primary" : "textSecondary"}
>
{text}
</Typography>
{okBtn && okBtnCaption ? (
<Box pt={1}>
<Button onClick={() => (onOk ? onOk() : null)} autoFocus>
<Button onClick={() => (onOk ? onOk() : null)} color="primary" autoFocus>
{okBtnCaption}
</Button>
</Box>
@ -250,28 +216,6 @@ const P8PAppInlineWarn = props => buildVariantInlineMessage(props, P8P_APP_MESSA
//Встраиваемое сообщение информации
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
};
//----------------
//Интерфейс модуля
//----------------
@ -285,6 +229,5 @@ export {
P8PAppInlineMessage,
P8PAppInlineError,
P8PAppInlineWarn,
P8PAppInlineInfo,
P8PHintDialog
P8PAppInlineInfo
};

View File

@ -47,7 +47,7 @@ const STYLES = {
//-----------
//Рабочее пространство
const P8PAppWorkspace = ({ children, panels = [], selectedPanel, caption, closeCaption, homeCaption, onHomeNavigate, onItemNavigate } = {}) => {
const P8PAppWorkspace = ({ children, panels = [], selectedPanel, closeCaption, homeCaption, onHomeNavigate, onItemNavigate } = {}) => {
//Собственное состояния
const [open, setOpen] = useState(false);
@ -86,7 +86,7 @@ const P8PAppWorkspace = ({ children, panels = [], selectedPanel, caption, closeC
<Icon>{open ? "chevron_left" : "menu"}</Icon>
</IconButton>
<Typography variant="h6" noWrap component="div">
{caption || selectedPanel?.caption}
{selectedPanel?.caption}
</Typography>
</Toolbar>
</AppBar>
@ -120,7 +120,6 @@ P8PAppWorkspace.propTypes = {
children: PropTypes.element,
panels: PropTypes.arrayOf(P8P_PANELS_MENU_PANEL_SHAPE).isRequired,
selectedPanel: P8P_PANELS_MENU_PANEL_SHAPE,
caption: PropTypes.string,
closeCaption: PropTypes.string.isRequired,
homeCaption: PropTypes.string.isRequired,
onHomeNavigate: PropTypes.func,

View File

@ -36,7 +36,6 @@ const P8P_DATA_GRID_FILTERS_HEIGHT = P8P_TABLE_FILTERS_HEIGHT;
//Таблица данных
const P8PDataGrid = ({
style = {},
columnsDef = [],
filtersInitial,
groups = [],
@ -115,7 +114,6 @@ const P8PDataGrid = ({
//Генерация содержимого
return (
<P8PTable
style={style}
columnsDef={columnsDef}
groups={groups}
rows={rows}
@ -156,7 +154,6 @@ const P8PDataGrid = ({
//Контроль свойств - Таблица данных
P8PDataGrid.propTypes = {
style: PropTypes.object,
columnsDef: PropTypes.array,
filtersInitial: PropTypes.arrayOf(P8P_DATA_GRID_FILTER_SHAPE),
groups: PropTypes.array,

View File

@ -139,7 +139,6 @@ const P8PGanttTaskEditor = ({
onCancel,
taskAttributeRenderer,
taskDialogRenderer,
taskDialogProps,
numbCaption,
nameCaption,
startCaption,
@ -187,7 +186,7 @@ const P8PGanttTaskEditor = ({
//Генерация содержимого
return (
<Dialog open onClose={handleCancel} {...(taskDialogProps ? taskDialogProps : {})}>
<Dialog open onClose={handleCancel}>
{taskDialogRenderer ? (
taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
) : (
@ -316,7 +315,6 @@ P8PGanttTaskEditor.propTypes = {
onCancel: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
numbCaption: PropTypes.string.isRequired,
nameCaption: PropTypes.string.isRequired,
startCaption: PropTypes.string.isRequired,
@ -349,7 +347,6 @@ const P8PGantt = ({
onTaskProgressChange,
taskAttributeRenderer,
taskDialogRenderer,
taskDialogProps,
noDataFoundText,
numbTaskEditorCaption,
nameTaskEditorCaption,
@ -470,7 +467,6 @@ const P8PGantt = ({
onCancel={handleTaskEditorCancel}
taskAttributeRenderer={taskAttributeRenderer}
taskDialogRenderer={taskDialogRenderer}
taskDialogProps={taskDialogProps}
numbCaption={numbTaskEditorCaption}
nameCaption={nameTaskEditorCaption}
startCaption={startTaskEditorCaption}
@ -506,7 +502,6 @@ P8PGantt.propTypes = {
onTaskProgressChange: PropTypes.func,
taskAttributeRenderer: PropTypes.func,
taskDialogRenderer: PropTypes.func,
taskDialogProps: PropTypes.object,
noDataFoundText: PropTypes.string.isRequired,
numbTaskEditorCaption: PropTypes.string.isRequired,
nameTaskEditorCaption: PropTypes.string.isRequired,

View File

@ -1,186 +0,0 @@
/*
Парус 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 };

View File

@ -34,7 +34,7 @@ import {
Link
} from "@mui/material"; //Интерфейсные компоненты
import { useTheme } from "@mui/material/styles"; //Взаимодействие со стилями MUI
import { P8PAppInlineError, P8PHintDialog } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { P8P_TABLE_AT, HEADER_INITIAL_STATE, hasValue, p8pTableReducer } from "./p8p_table_reducer"; //Редьюсер состояния
//---------
@ -89,7 +89,9 @@ const P8P_TABLE_FILTERS_HEIGHT = "48px";
//Стили
const STYLES = {
TABLE: {},
TABLE: {
with: "100%"
},
TABLE_HEAD_STICKY: {
position: "sticky",
top: 0,
@ -288,6 +290,28 @@ P8PTableColumnMenu.propTypes = {
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 = ({
columnDef,
@ -464,7 +488,6 @@ P8PTableFiltersChips.propTypes = {
//Таблица
const P8PTable = ({
style = {},
columnsDef = [],
groups = [],
rows = [],
@ -679,8 +702,10 @@ const P8PTable = ({
//Генерация содержимого
return (
<div style={{ ...(style || {}) }}>
{displayHintColumn ? <P8PHintDialog title={displayHintColumnDef.caption} hint={displayHintColumnDef.hint} onOk={handleHintOk} /> : null}
<div>
{displayHintColumn ? (
<P8PTableColumnHintDialog columnDef={displayHintColumnDef} okBtnCaption={okFilterBtnCaption} onOk={handleHintOk} />
) : null}
{filterColumn ? (
<P8PTableColumnFilterDialog
columnDef={filterColumnDef}
@ -875,7 +900,6 @@ const P8PTable = ({
//Контроль свойств - Таблица
P8PTable.propTypes = {
style: PropTypes.object,
columnsDef: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,

View File

@ -56,9 +56,6 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
//Установка списка панелей
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);
@ -172,7 +169,6 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
return (
<ApplicationСtx.Provider
value={{
setAppBarTitle,
findPanelByName,
pOnlineShowTab,
pOnlineShowUnit,

View File

@ -12,14 +12,12 @@ const APP_AT = {
SET_URL_BASE: "SET_URL_BASE", //Установка базового URL приложения
LOAD_PANELS: "LOAD_PANELS", //Загрузка списка панелей
SET_INITIALIZED: "SET_INITIALIZED", //Установка флага инициализированности приложения
SET_DISPLAY_SIZE: "SET_DISPLAY_SIZE", //Установка текущего типового размера экрана
SET_APP_BAR_TITLE: "SET_APP_BAR_TITLE" //Установка заголовка в шапке приложения
SET_DISPLAY_SIZE: "SET_DISPLAY_SIZE" //Установка текущего типового размера экрана
};
//Состояние приложения по умолчанию
const INITIAL_STATE = displaySizeGetter => ({
displaySize: displaySizeGetter(),
appBarTitle: "",
urlBase: "",
panels: [],
panelsLoaded: false,
@ -48,8 +46,6 @@ const handlers = {
[APP_AT.SET_INITIALIZED]: state => ({ ...state, initialized: true }),
//Установка текущего типового размера экрана
[APP_AT.SET_DISPLAY_SIZE]: (state, { payload }) => ({ ...state, displaySize: payload }),
//Установка заголовка в шапке приложения
[APP_AT.SET_APP_BAR_TITLE]: (state, { payload }) => ({ ...state, appBarTitle: payload }),
//Обработчик по умолчанию
DEFAULT: state => state
};

View File

@ -10,7 +10,6 @@
import React, { createContext, useContext, useCallback } from "react"; //ReactJS
import PropTypes from "prop-types"; //Контроль свойств компонента
import { MessagingСtx } from "./messaging"; //Контекст сообщений
import { formatErrorMessage } from "../core/utils"; //Вспомогательные функции
//---------
//Константы
@ -64,8 +63,7 @@ export const BackEndContext = ({ client, children }) => {
throwError = true,
showErrorMessage = true,
fullResponse = false,
spreadOutArguments = true,
signal = null
spreadOutArguments = true
} = {}) => {
try {
if (loader !== false) showLoader(loaderMessage);
@ -77,18 +75,12 @@ export const BackEndContext = ({ client, children }) => {
tagValueProcessor,
attributeValueProcessor,
throwError,
spreadOutArguments,
signal
spreadOutArguments
});
if (fullResponse === true || isRespErr(result)) return result;
else return result.XPAYLOAD;
} catch (e) {
if (showErrorMessage) {
//Разбираем текст ошибки
let errMsg = formatErrorMessage(e.message);
//Отображаем ошибку
showMsgErr(errMsg.text, null, errMsg.fullErrorText);
}
if (showErrorMessage) showMsgErr(e.message);
throw e;
} finally {
if (loader !== false) hideLoader();

View File

@ -33,9 +33,7 @@ const MESSAGING_CONTEXT_TEXTS_SHAPE = PropTypes.shape({
const MESSAGING_CONTEXT_BUTTONS_SHAPE = PropTypes.shape({
CLOSE: PropTypes.string.isRequired,
OK: PropTypes.string.isRequired,
CANCEL: PropTypes.string.isRequired,
DETAIL: PropTypes.string.isRequired,
HIDE: PropTypes.string.isRequired
CANCEL: PropTypes.string.isRequired
});
//----------------
@ -58,16 +56,12 @@ export const MessagingContext = ({ titles, texts, buttons, children }) => {
//Отображение сообщения
const showMsg = useCallback(
(type, text, msgOnOk = null, msgOnCancel = null, fullErrorText = null) =>
dispatch({ type: MSG_AT.SHOW_MSG, payload: { type, text, msgOnOk, msgOnCancel, fullErrorText } }),
(type, text, msgOnOk = null, msgOnCancel = null) => dispatch({ type: MSG_AT.SHOW_MSG, payload: { type, text, msgOnOk, msgOnCancel } }),
[]
);
//Отображение сообщения - ошибка
const showMsgErr = useCallback(
(text, msgOnOk = null, fullErrorText = null) => showMsg(MSG_TYPE.ERR, text, msgOnOk, null, fullErrorText),
[showMsg]
);
const showMsgErr = useCallback((text, msgOnOk = null) => showMsg(MSG_TYPE.ERR, text, msgOnOk), [showMsg]);
//Отображение сообщения - информация
const showMsgInfo = useCallback((text, msgOnOk = null) => showMsg(MSG_TYPE.INFO, text, msgOnOk), [showMsg]);
@ -132,7 +126,6 @@ export const MessagingContext = ({ titles, texts, buttons, children }) => {
open={true}
variant={state.msgType}
text={state.msgText}
fullErrorText={state.msgFullErrorText}
title
titleText={state.msgType == MSG_TYPE.ERR ? titles.ERR : state.msgType == MSG_TYPE.WARN ? titles.WARN : titles.INFO}
okBtn={true}
@ -141,8 +134,6 @@ export const MessagingContext = ({ titles, texts, buttons, children }) => {
cancelBtn={state.msgType == MSG_TYPE.WARN}
onCancel={handleMessageCancelClick}
cancelBtnCaption={buttons.CANCEL}
showErrMoreCaption={buttons.DETAIL}
hideErrMoreCaption={buttons.HIDE}
/>
) : null}
{children}

View File

@ -35,7 +35,6 @@ const INITIAL_STATE = {
msg: false,
msgType: MSG_TYPE.ERR,
msgText: null,
msgFullErrorText: null,
msgOnOk: null,
msgOnCancel: null
};
@ -60,7 +59,6 @@ const handlers = {
msg: true,
msgType: payload.type || MSG_TYPE.APP_ERR,
msgText: payload.text,
msgFullErrorText: payload.fullErrorText,
msgOnOk: payload.msgOnOk,
msgOnCancel: payload.msgOnCancel
}),

View File

@ -41,7 +41,7 @@ export const NavigationContext = ({ children }) => {
const navigate = useNavigate();
//Подключение к контексту приложения
const { findPanelByName, setAppBarTitle } = useContext(ApplicationСtx);
const { findPanelByName } = useContext(ApplicationСtx);
//Проверка наличия параметров запроса
const isNavigationSearch = () => (location.search ? true : false);
@ -65,8 +65,6 @@ export const NavigationContext = ({ children }) => {
const navigateTo = ({ path, search, state, replace = false }) => {
//Если указано куда переходить
if (path) {
//Сброс кастомного заголовка
setAppBarTitle("");
//Переходим к адресу
if (state) navigate(path, { state: JSON.stringify(state), replace });
else navigate({ pathname: path, search: queryString.stringify(search), replace });

View File

@ -34,7 +34,6 @@ const ERR_APPSERVER = "Ошибка сервера приложений"; //Об
const ERR_UNEXPECTED = "Неожиданный ответ сервера"; //Неожиданный ответ сервера
const ERR_NETWORK = "Ошибка соединения с сервером"; //Ошибка сети
const ERR_UNAUTH = "Сеанс завершен. Пройдите аутентификацию повторно."; //Ошибка аутентификации
const ERR_ABORTED = "Запрос прерван принудительно";
//-----------
//Тело модуля
@ -77,16 +76,7 @@ const getRespErrMessage = resp => (isRespErr(resp) && resp.SMESSAGE ? resp.SMESS
const getRespPayload = resp => (resp && resp.XPAYLOAD ? resp.XPAYLOAD : null);
//Исполнение действия на сервере
const executeAction = async ({
serverURL,
action,
payload = {},
isArray,
transformTagName,
tagValueProcessor,
attributeValueProcessor,
signal = null
} = {}) => {
const executeAction = async ({ serverURL, action, payload = {}, isArray, transformTagName, tagValueProcessor, attributeValueProcessor } = {}) => {
console.log(`EXECUTING ${action ? action : ""} ON ${serverURL} WITH PAYLOAD:`);
console.log(payload ? payload : "NO PAYLOAD");
let response = null;
@ -102,14 +92,11 @@ const executeAction = async ({
body: await buildXML(rqBody),
headers: {
"content-type": "application/xml"
},
...(signal ? { signal } : {})
}
});
} catch (e) {
//Прервано принудительно
if (signal?.aborted === true) throw new Error(ERR_ABORTED);
//Сетевая ошибка
else throw new Error(`${ERR_NETWORK}: ${e.message || "неопределённая ошибка"}`);
throw new Error(`${ERR_NETWORK}: ${e.message}`);
}
//Проверим на наличие ошибок HTTP - если есть вернём их
if (!response.ok) throw new Error(`${ERR_APPSERVER}: ${response.statusText}`);
@ -149,8 +136,7 @@ const executeStored = async ({
tagValueProcessor,
attributeValueProcessor,
throwError = true,
spreadOutArguments = false,
signal = null
spreadOutArguments = false
} = {}) => {
let res = null;
try {
@ -171,8 +157,7 @@ const executeStored = async ({
payload: { SSTORED: stored, XARGUMENTS: serverArgs, SRESP_ARG: respArg },
isArray,
tagValueProcessor,
attributeValueProcessor,
signal
attributeValueProcessor
});
if (spreadOutArguments === true && Array.isArray(res?.XPAYLOAD?.XOUT_ARGUMENTS)) {
let spreadArgs = {};
@ -208,11 +193,6 @@ const getConfig = async ({ throwError = true } = {}) => {
//----------------
export default {
ERR_APPSERVER,
ERR_UNEXPECTED,
ERR_NETWORK,
ERR_UNAUTH,
ERR_ABORTED,
SERV_DATA_TYPE_STR,
SERV_DATA_TYPE_NUMB,
SERV_DATA_TYPE_DATE,

View File

@ -102,7 +102,7 @@ const getDisplaySize = () => {
};
//Глубокое копирование объекта
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj)));
const deepCopyObject = obj => JSON.parse(JSON.stringify(obj));
//Конвертация объекта в Base64 XML
const object2Base64XML = (obj, builderOptions) => {
@ -158,34 +158,6 @@ const formatDateJSONDateOnly = value => (value ? dayjs(value).format("YYYY-MM-DD
//Форматирование числа в "Денежном" формате РФ
const formatNumberRFCurrency = value => (hasValue(value) ? new Intl.NumberFormat("ru-RU", { minimumFractionDigits: 2 }).format(value) : null);
//Форматирование текста ошибки
const formatErrorMessage = errorMsg => {
//Инициализируем текст заголовка ошибки
let text = "";
//Пробуем извлечь заголовок текста ошибки
try {
//Если это ошибка Oracle
if (errorMsg.match(/^ORA-/)) {
//Считываем первую строку с заголовочным текстом ошибки
text = errorMsg.match(/^.*(?=(\nORA-))/)[0];
//Убираем лишнюю информацию и пробелы
text = text.replace(/ORA-\d*:/g, "").trim();
}
//Если это ошибка PG
if (errorMsg.match(/^SQL Error/)) {
//Считываем первую строку с заголовочным текстом ошибки
text = errorMsg.match(/.*(?=(\n.*Where)|(.*Where))/)[0];
//Убираем лишнюю информацию и пробелы
text = text.replace(/SQL Error \[\d*\]: ERROR:/g, "").trim();
}
} catch {
//Если произошла ошибка - оставляем полный текст ошибки
text = errorMsg;
}
//Возвращаем результат
return { text: text || errorMsg, fullErrorText: text ? errorMsg : null };
};
//Формирование уникального идентификатора
const genGUID = () =>
"10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
@ -206,6 +178,5 @@ export {
formatDateTimeRF,
formatDateJSONDateOnly,
formatNumberRFCurrency,
formatErrorMessage,
genGUID
};

View File

@ -1,281 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
Кастомные хуки
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useCallback, useEffect, useContext } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Размер страницы данных
const DATA_GRID_PAGE_SIZE = 50;
//---------------------------------------------
//Вспомогательные функции форматирования данных
//---------------------------------------------
//-----------
//Тело модуля
//-----------
//Хук для основной таблицы
const useCostJobs = () => {
//Собственное состояние - таблица данных
const [state, setState] = useState({
init: false,
loaded: false,
jobInfo: {},
haveNote: false,
coeff: "1.0"
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Подключение к контексту навигации
const { getNavigationSearch } = useContext(NavigationCtx);
//При подключении компонента к странице
useEffect(() => {
const initJob = async fcJob => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBS_MP_INIT",
args: { NFCJOBS: parseInt(fcJob) },
respArg: "COUT",
attributeValueProcessor: (name, val) => (["NHAVE_NOTE"].includes(name) ? val == 1 : val)
});
setState(pv => ({
...pv,
init: true,
jobInfo: data.XFCJOBS ? data.XFCJOBS : {},
loaded: true
}));
};
if (!state.init) {
//Считаем параметры, переданные из действия
const actionPrms = getNavigationSearch();
//Иницализируем сменное задание
initJob(actionPrms.NRN);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return [state, setState];
};
//Хук для таблицы операций
const useCostJobsSpecs = task => {
//Собственное состояние - таблица данных
const [costJobsSpecs, setCostJobsSpecs] = useState({
task: null,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
selectedRow: {},
reload: true,
pageNumber: 1,
morePages: true,
fixedHeader: false,
fixedColumns: 0
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//Выдача задания
const issueCostJobsSpecs = useCallback(
async prms => {
try {
await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBSSP_MP_ISSUE",
args: { NFCJOBS: prms.NFCJOBS, NCOEFF: parseFloat(prms.NCOEFF) }
});
} catch (e) {
throw new Error(e.message);
}
},
[executeStored]
);
//При необходимости обновить данные таблицы
useEffect(() => {
//Если изменилось сменное задание - обновляем состояние
if (costJobsSpecs.dataLoaded && costJobsSpecs.task !== task) {
setCostJobsSpecs(pv => ({
...pv,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
selectedRow: {},
reload: true,
pageNumber: 1,
morePages: true
}));
}
//Если необходимо перезагрузить
if (costJobsSpecs.reload && task) {
const loadData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBSSP_MP_DG_GET",
args: {
NFCJOBS: task,
NPAGE_NUMBER: costJobsSpecs.pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
CORDERS: { VALUE: object2Base64XML(costJobsSpecs.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NINCLUDE_DEF: costJobsSpecs.dataLoaded ? 0 : 1
},
respArg: "COUT",
attributeValueProcessor: (name, val) =>
name === "NSELECT" ? val === 1 : name === "SWORKERS_LIST" ? (val ? val.split(",").map(Number) : []) : val
});
setCostJobsSpecs(pv => ({
...pv,
...data.XDATA_GRID,
task: task,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true,
reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE
}));
};
loadData();
}
}, [
SERV_DATA_TYPE_CLOB,
costJobsSpecs.dataLoaded,
costJobsSpecs.orders,
costJobsSpecs.pageNumber,
costJobsSpecs.reload,
costJobsSpecs.task,
executeStored,
task
]);
return [costJobsSpecs, setCostJobsSpecs, issueCostJobsSpecs];
};
//Хук для рабочих
const useCostJobsWorkers = task => {
//Собственное состояние - таблица данных
const [costJobsWorkers, setCostJobsWorkers] = useState({
task: null,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
selectedRows: [],
reload: true,
pageNumber: 1,
morePages: true
});
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//Включение рабочего в строку сменного задания
const includeWorker = useCallback(
async prms => {
try {
await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBSSP_MP_INC_PERFORM",
args: {
NFCJOBSSP: prms.NFCJOBSSP,
SPERFORM_LIST: {
VALUE: Array.isArray(prms.SELECTED_WORKERS) ? prms.SELECTED_WORKERS.join(";") : prms.SELECTED_WORKERS,
SDATA_TYPE: SERV_DATA_TYPE_CLOB
},
NQUANT_PLAN: prms.NQUANT_PLAN
}
});
} catch (e) {
throw new Error(e.message);
}
},
[SERV_DATA_TYPE_CLOB, executeStored]
);
//Исключение рабочего из строки сменного задания
const excludeWorker = useCallback(
async prms => {
try {
await executeStored({
stored: "PKG_P8PANELS_MECHREC.FCJOBSSP_MP_EXC_PERFORM",
args: { NFCJOBSSP: prms.NFCJOBSSP }
});
} catch (e) {
throw new Error(e.message);
}
},
[executeStored]
);
//При необходимости обновить данные таблицы
useEffect(() => {
//Если изменилось сменное задание - обновляем состояние
if (costJobsWorkers.dataLoaded && costJobsWorkers.task !== task) {
setCostJobsWorkers(pv => ({
...pv,
dataLoaded: false,
columnsDef: [],
orders: null,
rows: [],
selectedRows: [],
reload: true,
pageNumber: 1,
morePages: true
}));
}
//Если необходимо перезагрузить
if (costJobsWorkers.reload && task) {
const loadData = async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_MECHREC.WORKERS_MP_DG_GET",
args: {
NFCJOBS: task,
NPAGE_NUMBER: costJobsWorkers.pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
CORDERS: { VALUE: object2Base64XML(costJobsWorkers.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NINCLUDE_DEF: costJobsWorkers.dataLoaded ? 0 : 1
},
respArg: "COUT",
attributeValueProcessor: (name, val) => (["NSELECT"].includes(name) ? val === 1 : val)
});
setCostJobsWorkers(pv => ({
...pv,
...data.XDATA_GRID,
task: task,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef,
rows: pv.pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...pv.rows, ...(data.XDATA_GRID.rows || [])],
dataLoaded: true,
reload: false,
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE
}));
};
loadData();
}
}, [
SERV_DATA_TYPE_CLOB,
costJobsWorkers.dataLoaded,
costJobsWorkers.orders,
costJobsWorkers.pageNumber,
costJobsWorkers.reload,
costJobsWorkers.task,
executeStored,
task
]);
return [costJobsWorkers, setCostJobsWorkers, includeWorker, excludeWorker];
};
export { useCostJobs, useCostJobsSpecs, useCostJobsWorkers };

View File

@ -1,16 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
Панель мониторинга: Точка входа
*/
//---------------------
//Подключение библиотек
//---------------------
import { MechRecCostJobs } from "./mech_rec_cost_jobs_manage_mp"; //Корневая панель выдачи сменного задания на участок
//----------------
//Интерфейс модуля
//----------------
export const RootClass = MechRecCostJobs;

View File

@ -1,484 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
Панель мониторинга: Корневая панель выдачи сменного задания на участок
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import { Grid, Box, Typography, Checkbox, Icon, Stack, Button, Tooltip, TextField } from "@mui/material"; //Интерфейсные элементы
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { P8PDataGrid, P8P_DATA_GRID_SIZE, P8P_DATA_GRID_MORE_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useCostJobs, useCostJobsSpecs, useCostJobsWorkers } from "./hooks"; //Вспомогательные хуки
import { CostJobsSpecsInclude } from "./worker_include_dialog"; //Компонент диалога включения в задание
import { hasValue } from "../../core/utils"; //Вспомогательные функции
//---------
//Константы
//---------
//Мнемокод раздела операций
const UNIT_COST_JOBS_SPECS = "CostJobsSpecs";
//Мнемокод раздела исполнений должности
const UNIT_WORKERS = "ClientPostPerform";
//Высота основного заголовка
const MAIN_HEADER_HEIGHT = "35px";
//Высота подзаголовка
const SUB_HEADER_HEIGHT = "35px";
//Высота заголовка таблицы
const TABLE_HEADER_HEIGHT = "35px";
//Высота панели кнопок таблицы
const TABLE_BUTTONS_HEIGHT = "35px";
//Отступ таблицы
const TABLE_PADDING_TOP = "15px";
//Формат для коэффициент выполнения норм
const issueCoeffFormat = /^(?!.*\..*\.)[0-9]{0,3}(\.[0-9]{0,1})?$/;
//Стили
const STYLES = {
MAIN_HEADER: { height: MAIN_HEADER_HEIGHT, overflow: "hidden" },
SUB_HEADER: { height: SUB_HEADER_HEIGHT, overflow: "hidden" },
CONTAINER: { textAlign: "center" },
TABLE: { paddingTop: TABLE_PADDING_TOP },
TABLE_HEADER: { height: TABLE_HEADER_HEIGHT, overflow: "hidden" },
TABLE_BUTTONS: { display: "flex", justifyContent: "flex-end", height: TABLE_BUTTONS_HEIGHT, overflow: "hidden", alignItems: "flex-end" },
DATA_GRID_CONTAINER: morePages => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${MAIN_HEADER_HEIGHT} - ${SUB_HEADER_HEIGHT} - ${TABLE_HEADER_HEIGHT} - ${TABLE_BUTTONS_HEIGHT} - ${TABLE_PADDING_TOP} - 32px - ${
morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"
})`,
...APP_STYLES.SCROLL
})
};
//Цвета
const colors = {
LINKED: "#bce0de",
UNAVAILABLE: "#949494",
WITH_WORKER: "#82df83"
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Проверка правильности значения коэффициент выполнения норм
const isValidIssueCoeff = value => {
return issueCoeffFormat.test(value);
};
//Форматирование значения ячейки
const dataCellRender = ({ row, columnDef, handleSelectChange, sUnit, selectedWorkerRows = [], selectedJobSpec }) => {
//Стиль
let cellStyle = {};
//Если это рабочие
if (sUnit === UNIT_WORKERS) {
//Признак недоступности
let disabled = true;
//Если в выбранной строке смены указан исполнитель факт
if (selectedJobSpec.NPERFORM_FACT) {
//Если это текущей исполнитель
if (selectedJobSpec.SWORKERS_LIST.includes(row["NRN"])) {
//Подсвечиваем строку рабочего
cellStyle = { backgroundColor: colors.LINKED };
}
} else {
//Если выбрана строка смены
if (selectedJobSpec.NRN) {
//Если текущий рабочий может принять задание
if (row["NLOADING"] < 100) {
//Подсвечиваем строку рабочего
cellStyle = { backgroundColor: colors.LINKED };
disabled = false;
}
}
}
//Если уже выбрано достаточное количество рабочих и текущий рабочий не отмечен
if (selectedJobSpec.NRESOURCE_NUMB === selectedWorkerRows.length && !selectedWorkerRows.includes(row["NRN"])) {
//Устанавливаем признак недоступности
disabled = true;
}
//Если загрузка рабочего больше 100
if (row["NLOADING"] >= 100) {
//Если поле не поле выбора
if (columnDef.name !== "NSELECT") {
//Указываем, что рабочее место недоступно
cellStyle = { ...cellStyle, color: colors.UNAVAILABLE };
}
}
//Для колонки выбора
if (columnDef.name === "NSELECT") {
return {
cellStyle,
data: (
<Box sx={STYLES.CONTAINER}>
<Checkbox
disabled={disabled}
checked={selectedWorkerRows.includes(row["NRN"])}
onChange={() => handleSelectChange({ NRN: row["NRN"], SUNIT: sUnit, BFULL_LOADED: row["NLOADING"] >= 100 })}
/>
</Box>
)
};
}
//Отформатированная колонка
return {
cellStyle,
data: row[columnDef.name]
};
}
//Если это сменное задание
if (sUnit === UNIT_COST_JOBS_SPECS) {
//Если указан исполнитель факт
if (row["NPERFORM_FACT"]) {
//Подсвечиваем сменное задание зеленым
cellStyle = { ...cellStyle, backgroundColor: colors.WITH_WORKER };
}
//Для колонки выбора
if (columnDef.name === "NSELECT") {
return {
cellStyle,
data: (
<Box sx={STYLES.CONTAINER}>
<Checkbox
disabled={row["DBEG_FACT"] ? true : false}
checked={row["NRN"] === selectedJobSpec.NRN}
onChange={() =>
handleSelectChange({
NRN: row["NRN"],
SUNIT: sUnit,
NPERFORM_FACT: row["NPERFORM_FACT"],
NRESOURCE_NUMB: row["NRESOURCE_NUMB"],
NQUANT_PLAN: row["NQUANT_PLAN"],
SWORKERS_LIST: row["SWORKERS_LIST"]
})
}
/>
</Box>
)
};
}
//Отформатированная колонка
return {
cellStyle,
data: row[columnDef.name]
};
}
//Необрабатываемый раздел
return {
data: row[columnDef.name]
};
};
//Генерация представления ячейки заголовка группы
export const headCellRender = ({ columnDef }) => {
if (columnDef.name === "NSELECT") {
return {
stackStyle: { padding: "2px", justifyContent: "space-around" },
data: <Icon>done</Icon>
};
} else {
return {
stackStyle: { padding: "2px" },
data: columnDef.caption
};
}
};
//-----------
//Тело модуля
//-----------
//Корневая панель выдачи сменного задания на участок
const MechRecCostJobs = () => {
//Состояние диалога включения в задание
const [showInclude, setShowInclude] = useState(false);
//Состояние информации о сменном задании
const [state, setState] = useCostJobs();
//Состояние таблицы сменных заданий
const [costJobsSpecs, setCostJobsSpecs, issueCostJobsSpecs] = useCostJobsSpecs(state.jobInfo.NRN);
//Состояние таблицы рабочих
const [costJobsWorkers, setCostJobsWorkers, includeWorker, excludeWorker] = useCostJobsWorkers(state.jobInfo.NRN);
//При изменении состояния сортировки операций
const handleCostJobsSpecOrderChanged = ({ orders }) => setCostJobsSpecs(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
//При изменении количества отображаемых страниц операций
const handleCostJobsSpecPagesCountChanged = () => setCostJobsSpecs(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
//При изменении состояния сортировки рабочих
const handleCostJobsWorkersOrderChanged = ({ orders }) => setCostJobsWorkers(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
//При изменении количества отображаемых страниц рабочих
const handleCostJobsWorkersPagesCountChanged = () => setCostJobsWorkers(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
//При исключении рабочих из строки сменного задания
const handleCostJobsSpecExcludeWorker = () => {
//Делаем асинхронно, чтобы при ошибке ничего не обновлять
const excludeAsync = async () => {
//Исключаем рабочего из строки сменного задания
try {
await excludeWorker({
NFCJOBSSP: costJobsSpecs.selectedRow.NRN
});
//Необходимо обновить данные
setCostJobsSpecs(pv => ({ ...pv, selectedRow: {}, pageNumber: 1, reload: true }));
setCostJobsWorkers(pv => ({ ...pv, selectedRows: [], pageNumber: 1, reload: true }));
} catch (e) {
throw new Error(e.message);
}
};
//Исключаем рабочего асинхронно
excludeAsync();
};
//Выдача задания операции
const handleCostJobsSpecIssue = () => {
//Делаем асинхронно, чтобы при ошибке ничего не обновлять
const issueAsync = async () => {
//Включаем рабочих в операции
try {
await issueCostJobsSpecs({ NFCJOBS: state.jobInfo.NRN, NCOEFF: state.coeff });
//Необходимо обновить данные
setCostJobsSpecs(pv => ({ ...pv, selectedRow: {}, pageNumber: 1, reload: true }));
setCostJobsWorkers(pv => ({ ...pv, selectedRows: [], pageNumber: 1, reload: true }));
} catch (e) {
throw new Error(e.message);
}
};
//Выдаем задание асинхронно
issueAsync();
};
//При изменение состояния выбора
const handleSelectChange = prms => {
//Выбранный элемент
let selectedRow = null;
//Буфер для выбранных рабочих
let selectedWorkers = [];
//Индекс рабочего в списке выбранных
let workerIndex = null;
//Исходим от раздела
switch (prms.SUNIT) {
//Сменное задание
case UNIT_COST_JOBS_SPECS:
//Определяем это новое отмеченное сменное задание или сброс старого
selectedRow = costJobsSpecs.selectedRow.NRN ? (costJobsSpecs.selectedRow.NRN === prms.NRN ? null : prms.NRN) : prms.NRN;
//Актуализируем строки
setCostJobsSpecs(pv => ({
...pv,
selectedRow: selectedRow
? {
NRN: selectedRow,
NPERFORM_FACT: prms.NPERFORM_FACT,
NRESOURCE_NUMB: prms.NRESOURCE_NUMB,
NQUANT_PLAN: prms.NQUANT_PLAN,
SWORKERS_LIST: prms.SWORKERS_LIST
}
: { NRN: null, NPERFORM_FACT: null, NRESOURCE_NUMB: null, NQUANT_PLAN: null, SWORKERS_LIST: [] }
}));
//Выходим
break;
//Рабочие центры
case UNIT_WORKERS:
//Инициализируем рабочими центрами
selectedWorkers = costJobsWorkers.selectedRows || [];
//Определяем индекс элемента в массиве
workerIndex = selectedWorkers.indexOf(prms.NRN);
//Если такого рег. номера нет в списке - добавляем, иначе удаляем
workerIndex > -1 ? selectedWorkers.splice(workerIndex, 1) : selectedWorkers.push(prms.NRN);
//Актуализируем строки
setCostJobsWorkers(pv => ({ ...pv, selectedRows: selectedWorkers }));
//Выходим
break;
default:
return;
}
};
//При открытии/закрытии диалога добавления
const handleShowIncludeChange = needShow => setShowInclude(needShow);
//При изменении коэффициент выполнения норм
const handleIssueCoeffChange = e => {
isValidIssueCoeff(e.target.value) ? setState(pv => ({ ...pv, coeff: e.target.value })) : null;
};
return (
<Box p={2}>
{state.loaded ? (
<Box sx={STYLES.CONTAINER}>
<Typography
sx={STYLES.MAIN_HEADER}
variant={"h6"}
>{`Сменное задание №${state.jobInfo.SDOC_NUMB} на ${state.jobInfo.SPERIOD}`}</Typography>
<Typography sx={STYLES.SUB_HEADER} variant={"h6"}>{`${state.jobInfo.SSUBDIV}`}</Typography>
<Box sx={STYLES.CONTAINER}>
<Grid container spacing={2}>
<Grid item sx={STYLES.CONTAINER} xs={6}>
<Typography sx={STYLES.TABLE_HEADER} variant={"h6"} color={"text.secondary"}>
Сменное задание
</Typography>
{costJobsWorkers.dataLoaded ? (
<>
<Box sx={STYLES.TABLE_BUTTONS}>
<Stack direction={"row"} spacing={1}>
<Tooltip
title={
state.jobInfo.NHAVE_NOTE
? "Сменное задание имеет строку с примечанием"
: "Коэффициент выполнения норм"
}
>
<TextField
name="editIssueValue"
variant="outlined"
sx={{ width: "68px" }}
inputProps={{ sx: { padding: "4.2px 14px" } }}
size="small"
value={state.coeff}
onChange={handleIssueCoeffChange}
disabled={state.jobInfo.NHAVE_NOTE}
/>
</Tooltip>
<Tooltip
title={
state.jobInfo.NHAVE_NOTE
? "Сменное задание имеет строку с примечанием"
: !hasValue(state.coeff)
? "Не указано значение коэффициент выполнения норм"
: null
}
>
<Box>
<Button
variant="contained"
size="small"
disabled={state.jobInfo.NHAVE_NOTE || !hasValue(state.coeff)}
onClick={handleCostJobsSpecIssue}
>
Выдать задания
</Button>
</Box>
</Tooltip>
</Stack>
</Box>
<Box sx={STYLES.TABLE}>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER(costJobsSpecs.morePages), elevation: 4 }}
columnsDef={costJobsSpecs.columnsDef}
rows={costJobsSpecs.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
morePages={costJobsSpecs.morePages}
reloading={costJobsSpecs.reload}
onOrderChanged={handleCostJobsSpecOrderChanged}
onPagesCountChanged={handleCostJobsSpecPagesCountChanged}
dataCellRender={prms =>
dataCellRender({
...prms,
handleSelectChange,
sUnit: UNIT_COST_JOBS_SPECS,
selectedJobSpec: costJobsSpecs.selectedRow
})
}
headCellRender={prms => headCellRender({ ...prms })}
fixedHeader={costJobsSpecs.fixedHeader}
fixedColumns={costJobsSpecs.fixedColumns}
/>
</Box>
</>
) : null}
</Grid>
<Grid item sx={STYLES.CONTAINER} xs={6}>
<Typography sx={STYLES.TABLE_HEADER} variant={"h6"} color={"text.secondary"}>
Рабочие
</Typography>
{costJobsWorkers.dataLoaded ? (
<>
<Box sx={STYLES.TABLE_BUTTONS}>
<Stack direction={"row"} spacing={1}>
<Button
variant="contained"
size="small"
disabled={!(costJobsSpecs.selectedRow.NRESOURCE_NUMB === costJobsWorkers.selectedRows.length)}
onClick={() => handleShowIncludeChange(true)}
>
Включить в задание
</Button>
<Button
variant="contained"
size="small"
disabled={!costJobsSpecs.selectedRow.NRN || !costJobsSpecs.selectedRow.NPERFORM_FACT}
onClick={handleCostJobsSpecExcludeWorker}
>
Исключить из задания
</Button>
</Stack>
</Box>
<Box sx={STYLES.TABLE}>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER(costJobsWorkers.morePages), elevation: 4 }}
columnsDef={costJobsWorkers.columnsDef}
rows={costJobsWorkers.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
morePages={costJobsWorkers.morePages}
reloading={costJobsWorkers.reload}
onOrderChanged={handleCostJobsWorkersOrderChanged}
onPagesCountChanged={handleCostJobsWorkersPagesCountChanged}
dataCellRender={prms =>
dataCellRender({
...prms,
handleSelectChange,
sUnit: UNIT_WORKERS,
selectedWorkerRows: costJobsWorkers.selectedRows,
selectedJobSpec: costJobsSpecs.selectedRow
})
}
headCellRender={prms => headCellRender({ ...prms })}
fixedHeader={true}
/>
</Box>
</>
) : null}
</Grid>
</Grid>
</Box>
</Box>
) : null}
{showInclude ? (
<CostJobsSpecsInclude
includePrms={{
NFCJOBSSP: costJobsSpecs.selectedRow.NRN,
SELECTED_WORKERS: costJobsWorkers.selectedRows,
NQUANT_PLAN: costJobsSpecs.selectedRow.NQUANT_PLAN
}}
setShowInclude={setShowInclude}
setCostJobsSpecs={setCostJobsSpecs}
setCostJobsWorkers={setCostJobsWorkers}
includeWorker={includeWorker}
/>
) : null}
</Box>
);
};
//----------------
//Интерфейс модуля
//----------------
export { MechRecCostJobs };

View File

@ -1,103 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
Панель мониторинга: Диалог включения рабочего в сменное задание
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Button, Dialog, DialogTitle, DialogContent, TextField, DialogActions } from "@mui/material"; //Интерфейсные элементы
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы
//-----------
//Тело модуля
//-----------
//Диалог включения рабочего в сменное задание
const CostJobsSpecsInclude = ({ includePrms, setShowInclude, setCostJobsSpecs, setCostJobsWorkers, includeWorker }) => {
//Собственное состояние - Значение приоритета
const [state, setState] = useState(includePrms.NQUANT_PLAN);
//При закрытии включения рабочего
const handlePriorEditClose = () => setShowInclude(false);
//При включении рабочего в строку сменного задания
const costJobsSpecIncludeCostEquipment = () => {
//Делаем асинхронно, чтобы при ошибке ничего не обновлять
const includeAsync = async () => {
//Включаем рабочего в строку сменного задания
try {
await includeWorker({
NFCJOBSSP: includePrms.NFCJOBSSP,
SELECTED_WORKERS: includePrms.SELECTED_WORKERS,
NQUANT_PLAN: state
});
//Необходимо обновить все данные
setCostJobsSpecs(pv => ({ ...pv, selectedRow: {}, pageNumber: 1, reload: true }));
setCostJobsWorkers(pv => ({ ...pv, selectedRows: [], pageNumber: 1, reload: true }));
handlePriorEditClose();
} catch (e) {
throw new Error(e.message);
}
};
//Включаем рабочего асинхронно
includeAsync();
};
return (
<Dialog open onClose={() => handlePriorEditClose()}>
<DialogTitle>Включить в задание</DialogTitle>
<DialogContent>
<Box>
<TextField
name="editInculdeValue"
label="Количество"
variant="standard"
fullWidth
InputProps={{
type: "number",
inputProps: {
max: includePrms.NQUANT_PLAN,
min: 0
}
}}
value={state}
onChange={event => {
var value = parseInt(event.target.value, 10);
if (value > includePrms.NQUANT_PLAN) {
value = includePrms.NQUANT_PLAN;
}
if (value < 0) {
value = 0;
}
setState(value);
}}
/>
<Box></Box>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={costJobsSpecIncludeCostEquipment}>{BUTTONS.OK}</Button>
<Button onClick={() => handlePriorEditClose(null)}>{BUTTONS.CANCEL}</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог включения рабочего в сменное задание
CostJobsSpecsInclude.propTypes = {
includePrms: PropTypes.object.isRequired,
setShowInclude: PropTypes.func.isRequired,
setCostJobsSpecs: PropTypes.func.isRequired,
setCostJobsWorkers: PropTypes.func.isRequired,
includeWorker: PropTypes.func.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { CostJobsSpecsInclude };

View File

@ -48,8 +48,7 @@ const useCostRouteLists = (task, taskType) => {
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
NINCLUDE_DEF: costRouteLists.dataLoaded ? 0 : 1
},
attributeValueProcessor: (name, val) =>
["DEXEC_DATE", "DREL_DATE"].includes(name) ? formatDateRF(val) : ["SDOCPREF", "SDOCNUMB"].includes(name) ? undefined : val,
attributeValueProcessor: (name, val) => (["DEXEC_DATE", "DREL_DATE"].includes(name) ? formatDateRF(val) : val),
respArg: "COUT"
});
setCostRouteLists(pv => ({

View File

@ -84,26 +84,7 @@ const STYLES = {
TASK_DIALOG_ACTION_CONTAINER: { border: 1, borderColor: "text.primary", borderRadius: "5px", width: "100%" },
FILTERS: { display: "table", float: "right" },
FILTERS_DATE: { display: "table-cell", verticalAlign: "middle" },
FILTERS_LEVEL: { display: "table-cell", verticalAlign: "middle", paddingLeft: "15px" },
FILTERS_LEVEL_CAPTION: { display: "flex", alignItems: "center" },
FILTERS_LEVEL_LIMIT_ICON: { padding: "0px 8px", color: "#9f9c9c" },
FILTERS_LIMIT_SELECT: nOutOfLimit => {
return nOutOfLimit === 1
? {
".MuiOutlinedInput-notchedOutline": {
borderColor: "#e9863c"
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: "#e9863c",
borderWidth: "0.15rem"
},
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
borderColor: "#e9863c",
borderWidth: "0.15rem"
}
}
: {};
}
FILTERS_LEVEL: { display: "table-cell", verticalAlign: "middle", paddingLeft: "15px" }
};
//------------------------------------
@ -255,7 +236,6 @@ const MechRecCostProdPlans = () => {
selectedPlanCtlg: null,
selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null,
selectedPlanCtlgOutOfLimit: 0,
selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null,
gantt: {},
@ -278,9 +258,6 @@ const MechRecCostProdPlans = () => {
//Подключение к контексту навигации
const { getNavigationSearch } = useContext(NavigationCtx);
//Подключение к контексту сообщений
const { showMsgInfo } = useContext(MessagingСtx);
//Инициализация каталогов планов
const initPlanCtlgs = useCallback(async () => {
if (!state.init) {
@ -303,7 +280,6 @@ const MechRecCostProdPlans = () => {
selectedPlanCtlgSpecsLoaded: false,
selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null,
selectedPlanCtlgOutOfLimit: 0,
selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null,
gantt: {},
@ -321,7 +297,6 @@ const MechRecCostProdPlans = () => {
selectedPlanCtlg: null,
selectedPlanCtlgMaxLevel: null,
selectedPlanCtlgLevel: null,
selectedPlanCtlgOutOfLimit: 0,
selectedPlanCtlgSort: null,
selectedPlanCtlgMenuItems: null,
gantt: {},
@ -342,7 +317,6 @@ const MechRecCostProdPlans = () => {
...pv,
selectedPlanCtlgMaxLevel: data.NMAX_LEVEL,
selectedPlanCtlgLevel: level || level === 0 ? level : data.NMAX_LEVEL,
selectedPlanCtlgOutOfLimit: data.NOUT_OF_LIMIT,
selectedPlanCtlgSort: sort,
selectedPlanCtlgMenuItems: state.selectedPlanCtlgMenuItems
? state.selectedPlanCtlgMenuItems
@ -396,17 +370,6 @@ const MechRecCostProdPlans = () => {
setState(pv => ({ ...pv, selectedTaskDetail: taskRn, selectedTaskDetailType: taskType }));
};
//При открытии окна информации об ограничении уровня
const handleLevelLimitInfoOpen = () => {
//Отображаем информацию
showMsgInfo(
`Размер производственной программы превышает предельно допустимый для одновременного отображения в виде диаграммы Ганта.
Доступные для просмотра уровни вложенности ограничены.
Вы можете просматривать производственную программу частями, используя действие "Открытие панели Производственная программа" в спецификации "Выпуск"
раздела "Планы и отчеты производства изделий".`
);
};
//Генерация содержимого
return (
<Box>
@ -471,16 +434,8 @@ const MechRecCostProdPlans = () => {
</Select>
</Box>
<Box sx={STYLES.FILTERS_LEVEL}>
<Box sx={STYLES.FILTERS_LEVEL_CAPTION}>
<InputLabel id="select-label-level">До уровня</InputLabel>
{state.selectedPlanCtlgOutOfLimit === 1 ? (
<IconButton sx={STYLES.FILTERS_LEVEL_LIMIT_ICON} onClick={handleLevelLimitInfoOpen}>
<Icon>info</Icon>
</IconButton>
) : null}
</Box>
<InputLabel id="select-label-level">До уровня</InputLabel>
<Select
sx={STYLES.FILTERS_LIMIT_SELECT(state.selectedPlanCtlgOutOfLimit)}
labelId="select-label-level"
id="select-level"
value={state.selectedPlanCtlgLevel}

View File

@ -1,52 +0,0 @@
/*
Парус 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 };

View File

@ -1,72 +0,0 @@
/*
Парус 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>
);
};
*/

View File

@ -1,56 +0,0 @@
/*
Парус 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;

View File

@ -1,79 +0,0 @@
/*
Парус 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;

View File

@ -1,129 +0,0 @@
/*
Парус 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 };

View File

@ -1,174 +0,0 @@
/*
Парус 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 };

View File

@ -1,434 +0,0 @@
/*
Парус 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 };

View File

@ -1,49 +0,0 @@
/*
Парус 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"
};

View File

@ -1,306 +0,0 @@
/*
Парус 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;

View File

@ -1,168 +0,0 @@
/*
Парус 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;

View File

@ -1,56 +0,0 @@
/*
Парус 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;

View File

@ -1,84 +0,0 @@
/*
Парус 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;

View File

@ -1,56 +0,0 @@
/*
Парус 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;

View File

@ -1,96 +0,0 @@
/*
Парус 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;

View File

@ -1,67 +0,0 @@
/*
Парус 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 };

View File

@ -1,16 +0,0 @@
/*
Парус 8 - Панели мониторинга - Редактор панелей
Редактор панелей: точка входа
*/
//---------------------
//Подключение библиотек
//---------------------
import { PanelsEditor } from "./panels_editor"; //Корневая панель редактора
//----------------
//Интерфейс модуля
//----------------
export const RootClass = PanelsEditor;

View File

@ -1,87 +0,0 @@
/*
Парус 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 };

View File

@ -1,40 +0,0 @@
: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;
}

View File

@ -1,249 +0,0 @@
/*
Парус 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 };

View File

@ -1,170 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Фильтр
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Grid, Chip, Stack, Input, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
import { FILTER_INITIAL, FILTER_ITEMS, PRICE_STRUCT_STATUS, PROJECT_STATE, FilterDialog } from "./filter_dialog"; //Компонент "Диалог фильтра"
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { paddingTop: "10px" },
FILTER: { maxWidth: "99vw" },
SEARCH_GRID_ITEM: { paddingRight: "15px" }
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Элемент фильтра
const FilterItem = ({ caption, value, defaultValue, onClick, onDelete }) => {
//При нажатии на элемент
const handleClick = () => (onClick ? onClick() : null);
//При нажатии на удаление элемента
const handleDelete = () => (onDelete ? onDelete() : null);
//Генерация содержимого
return (
<Chip
label={
<Stack direction={"row"} alignItems={"center"}>
<strong>{caption}</strong>:&nbsp;{value || defaultValue}
</Stack>
}
variant="outlined"
onClick={handleClick}
onDelete={onDelete ? handleDelete : null}
/>
);
};
//Контроль свойств компонента - Элемент фильтра
FilterItem.propTypes = {
caption: PropTypes.string.isRequired,
value: PropTypes.any,
defaultValue: PropTypes.string.isRequired,
onClick: PropTypes.func,
onDelete: PropTypes.func
};
//-----------
//Тело модуля
//-----------
//Фильтр
const Filter = ({ values, onChange }) => {
//Собственное состояние - отображение диалога ввода значений фильтра
const [isOpen, setIsOpen] = useState(false);
//Собственное состояние - строка поиска
const [search, setSearch] = useState(values.search);
//Передача сообщения об измении фильтра родителю
const notifyChange = values => (onChange ? onChange(values) : null);
//При закрытии диалога с сохранением значений
const handleFilterDialogOk = values => {
setIsOpen(false);
notifyChange(values);
};
//При закрытии диалога без сохранения значений
const handleFilterDialogCancel = () => setIsOpen(false);
//При нажатии на фильтр
const handleClick = () => setIsOpen(true);
//При выполнении поиска
const handleDoSearch = (clear = false) => {
if (clear === true) setSearch("");
notifyChange({ ...values, search: clear === true ? "" : search });
};
//При изменении значения в строке поиска
const handleSearchChange = e => setSearch(e.target.value);
//При нажатии клавиши в строке поиска
const handleSearchKeyPress = e => ([13, 27].includes(e.keyCode) ? handleDoSearch(e.keyCode == 27) : null);
//Формирование функции обработки очистки элемента фильтар
const buildFilterItemClearHandler = сode =>
values[сode] != FILTER_INITIAL[сode] ? () => notifyChange({ ...values, [сode]: FILTER_INITIAL[сode] }) : null;
//Генерация содержимого
return (
<Grid container sx={STYLES.CONTAINER}>
<Grid xs={10} item>
{isOpen ? <FilterDialog valuesInitial={values} onOk={handleFilterDialogOk} onCancel={handleFilterDialogCancel} /> : null}
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.FILTER} onClick={handleClick}>
<FilterItem
caption={"Тип заказа"}
value={values.prjType}
defaultValue={"Любой"}
onDelete={buildFilterItemClearHandler("prjType")}
/>
<FilterItem
caption={"Подразделение-ответственный"}
defaultValue={"Любое"}
value={values.insDep}
onDelete={buildFilterItemClearHandler("insDep")}
/>
<FilterItem
caption={"Статус структуры цены"}
defaultValue={"Неподдерживаемое значение"}
value={PRICE_STRUCT_STATUS.find(item => item.value == values.priceStructStatus)?.name}
onDelete={buildFilterItemClearHandler("priceStructStatus")}
/>
<FilterItem
caption={"Состояние проекта"}
defaultValue={"Неподдерживаемое значение"}
value={PROJECT_STATE.find(item => item.value == values.prjState)?.name}
onDelete={buildFilterItemClearHandler("prjState")}
/>
</Stack>
</Grid>
<Grid xs={2} item sx={STYLES.SEARCH_GRID_ITEM}>
<Input
fullWidth
placeholder="Поиск..."
value={search}
endAdornment={
<InputAdornment position="end">
<IconButton onClick={() => handleDoSearch(true)}>
<Icon>clear</Icon>
</IconButton>
<IconButton onClick={handleDoSearch}>
<Icon>search</Icon>
</IconButton>
</InputAdornment>
}
onKeyDown={handleSearchKeyPress}
onChange={handleSearchChange}
/>
</Grid>
</Grid>
);
};
//Контроль свойств компонента - Фильтр
Filter.propTypes = {
values: FILTER_ITEMS.isRequired,
onChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { FILTER_INITIAL, Filter };

View File

@ -1,147 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Диалог фильтра
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Button, Dialog, DialogTitle, DialogContent, DialogActions } from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { BUTTONS } from "../../../app.text"; //Типовые тексты
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { FormField } from "./layouts"; //Общие компоненты панели
//---------
//Константы
//---------
//Стили
const STYLES = {
DIALOG_CONTENT: { overflowY: "auto", ...APP_STYLES.SCROLL }
};
//Структура фильтра
const FILTER_ITEMS = PropTypes.shape({
prjType: PropTypes.string,
insDep: PropTypes.string,
priceStructStatus: PropTypes.number.isRequired,
prjState: PropTypes.number.isRequired,
search: PropTypes.string
});
//Начальное состояние фильтра
const FILTER_INITIAL = { prjType: "", insDep: "", priceStructStatus: 0, prjState: 0, search: "" };
//Статусы структуры цены
const PRICE_STRUCT_STATUS = [
{ value: 0, name: "Все" },
{ value: 1, name: "Есть статьи с расходом больше 90%" },
{ value: 2, name: "Есть статьи с перерасходом" }
];
//Состояния проекта
const PROJECT_STATE = [
{ value: 0, name: "Все" },
{ value: 1, name: "Открытые" },
{ value: 2, name: "Неоткрытые" }
];
//-----------
//Тело модуля
//-----------
//Диалог фильтра
const FilterDialog = ({ valuesInitial, onOk, onCancel }) => {
//Собственное состояние элементов фильтра
const [values, setValues] = useState({ ...valuesInitial });
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Изменение элемента формы фильтра
const handleValueChanged = (name, value) => setValues(pv => ({ ...pv, [name]: value }));
//Сброс настроек фильтра
const handleResetClick = () => setValues({ ...FILTER_INITIAL });
//Сохранение фильтра
const handleOkClick = () => (onOk ? onOk(values) : null);
//Отмена фильтра
const handleCancelClick = () => (onCancel ? onCancel() : null);
//Выбор значения элемента формы из словаря
const selectFromDictionary = (unitCode, name, applyValue) => {
pOnlineShowDictionary({
unitCode,
showMethod: "main",
inputParameters: [{ name: "in_CODE", value: values[name] }],
callBack: res => applyValue(res.success ? [{ name, value: res.outParameters.out_CODE }] : null)
});
};
//Генерация содержимого
return (
<Dialog open onClose={handleCancelClick} fullWidth maxWidth={"md"}>
<DialogTitle>Фильтр отбора</DialogTitle>
<DialogContent sx={STYLES.DIALOG_CONTENT}>
<FormField
elementCode={"prjType"}
elementValue={values.prjType}
labelText={"Тип заказа"}
onChange={handleValueChanged}
dictionary={applyValue => selectFromDictionary("ProjectTypes", "prjType", applyValue)}
/>
<FormField
elementCode={"insDep"}
elementValue={values.insDep}
labelText={"Подразделение-ответственный"}
onChange={handleValueChanged}
dictionary={applyValue => selectFromDictionary("INS_DEPARTMENT", "insDep", applyValue)}
/>
<FormField
elementCode={"priceStructStatus"}
elementValue={values.priceStructStatus}
labelText={"Статус структуры цены"}
onChange={handleValueChanged}
list={PRICE_STRUCT_STATUS}
/>
<FormField
elementCode={"prjState"}
elementValue={values.prjState}
labelText={"Состояние проекта"}
onChange={handleValueChanged}
list={PROJECT_STATE}
/>
</DialogContent>
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button variant="text" onClick={handleOkClick}>
{BUTTONS.OK}
</Button>
<Button variant="text" onClick={handleResetClick}>
{BUTTONS.CLEAR}
</Button>
<Button variant="text" onClick={handleCancelClick}>
{BUTTONS.CANCEL}
</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств компонента - Диалог фильтра
FilterDialog.propTypes = {
valuesInitial: FILTER_ITEMS.isRequired,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { FILTER_ITEMS, FILTER_INITIAL, PRICE_STRUCT_STATUS, PROJECT_STATE, FilterDialog };

View File

@ -1,16 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Панель мониторинга: точка входа
*/
//---------------------
//Подключение библиотек
//---------------------
import { PrjInfo } from "./prj_info"; //Корневая панель информации о проектах
//----------------
//Интерфейс модуля
//----------------
export const RootClass = PrjInfo;

View File

@ -1,168 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Общие дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Icon, Input, InputAdornment, FormControl, Select, InputLabel, MenuItem, IconButton, Typography, Switch, Stack } from "@mui/material"; //Интерфейсные компоненты
//---------
//Константы
//---------
//Стили
const STYLES = {
STATE: value => ({ color: value === 1 ? "green" : "black" }),
COST_STATUS: color => ({ color, verticalAlign: "middle" }),
COST_READY: value => ({ color: value <= 30 ? "red" : value >= 80 ? "green" : "#e2af00" }),
TOGGLE_COLOR: checked => ({ color: checked ? "#006dd9" : "lightgrey" })
};
//-----------
//Тело модуля
//-----------
//Поле ввода формы
const FormField = ({ elementCode, elementValue, labelText, onChange, dictionary, list, type, ...other }) => {
//Значение элемента
const [value, setValue] = useState(elementValue);
//При получении нового значения из вне
useEffect(() => {
setValue(elementValue);
}, [elementValue]);
//Выбор значения из словаря
const handleDictionaryClick = () =>
dictionary ? dictionary(res => (res ? res.map(i => handleChange({ target: { name: i.name, value: i.value } })) : null)) : null;
//Изменение значения элемента (по событию)
const handleChange = e => {
setValue(e.target.value);
if (onChange) onChange(e.target.name, e.target.value);
};
//Генерация содержимого
return (
<Box p={1}>
<FormControl variant="standard" fullWidth {...other}>
{list ? (
<>
<InputLabel id={`${elementCode}Lable`} shrink>
{labelText}
</InputLabel>
<Select
labelId={`${elementCode}Lable`}
id={elementCode}
name={elementCode}
label={labelText}
value={value || value == 0 ? value : ""}
onChange={handleChange}
displayEmpty
>
{list.map((item, i) => (
<MenuItem key={i} value={item.value || item.value == 0 ? item.value : ""}>
{item.name}
</MenuItem>
))}
</Select>
</>
) : (
<>
<InputLabel {...(type == "date" ? { shrink: true } : {})} htmlFor={elementCode}>
{labelText}
</InputLabel>
<Input
id={elementCode}
name={elementCode}
value={value || value == 0 ? value : ""}
endAdornment={
dictionary ? (
<InputAdornment position="end">
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
<Icon>list</Icon>
</IconButton>
</InputAdornment>
) : null
}
{...(type ? { type } : {})}
onChange={handleChange}
/>
</>
)}
</FormControl>
</Box>
);
};
//Контроль свойств - Поле ввода формы
FormField.propTypes = {
elementCode: PropTypes.string.isRequired,
elementValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.instanceOf(Date)]),
labelText: PropTypes.string.isRequired,
onChange: PropTypes.func,
dictionary: PropTypes.func,
list: PropTypes.array,
type: PropTypes.string
};
//Переключатель
const Toggle = ({ labels, checked, onChange }) => {
//Обработка переключения
const handleChange = event => (onChange ? onChange(event.target.checked) : null);
//Генерация содержимого
return (
<Stack direction={"row"} spacing={1} alignItems={"center"} justifyContent={"center"}>
<Typography sx={STYLES.TOGGLE_COLOR(!checked)}>{labels[0]}</Typography>
<Switch checked={checked} size="small" onChange={handleChange} />
<Typography sx={STYLES.TOGGLE_COLOR(checked)}>{labels[1]}</Typography>
</Stack>
);
};
//Контроль свойств компонента - Переключатель
Toggle.propTypes = {
labels: PropTypes.arrayOf(PropTypes.string).isRequired,
checked: PropTypes.bool.isRequired,
onChange: PropTypes.func
};
//Формирование значения для колонки "Статус структуры цены"
const formatCostStatusValue = ({ value, onClick, type = 1 }) => {
const [text, color] =
value == 0
? ["Без отклонений", "lightgreen"]
: value == 1
? [type == 1 ? "Есть статьи с расходом более 90%" : "Расход более 90%", "#ffdf71"]
: value == 2
? [type == 1 ? "Есть статьи с перерасходом" : "Перерасход", "#eb6b6b"]
: ["Не определено", "lightgray"];
return onClick ? (
<IconButton onClick={onClick}>
<Icon sx={STYLES.COST_STATUS(color)} title={`${text}\nНажмите для детальной информации`}>
circle
</Icon>
</IconButton>
) : (
<Icon sx={STYLES.COST_STATUS(color)} title={text}>
circle
</Icon>
);
};
//Формирование значения для колонки "Готов (%, зтраты)"
const formatCostReadyValue = value => {
return <span style={STYLES.COST_READY(value)}>{value}</span>;
};
//----------------
//Интерфейс модуля
//----------------
export { STYLES as COMMON_STYLES, FormField, Toggle, formatCostStatusValue, formatCostReadyValue };

View File

@ -1,27 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Корневой компонент панели
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Projects } from "./projects"; //Список проектов
//-----------
//Тело модуля
//-----------
//Корневой компонент панели "Информация о проектах"
const PrjInfo = () => {
//Генерация содержимого
return <Projects />;
};
//----------------
//Интерфейс модуля
//----------------
export { PrjInfo };

View File

@ -1,70 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список проектов
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import { P8PDataGrid } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { useProjectsDataGrid } from "./projects_hooks"; //Хуки списка проектов
import { FILTER_INITIAL, Filter } from "./filter"; //Компонент "Фильтр"
import { PROJECTS_STYLES, projectDataCellRender, projectRowExpandRender } from "./projects_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
//-----------
//Тело модуля
//-----------
//Список проектов
const Projects = () => {
//Собственное состояние
const [projects, setProjects] = useState({ pageNumber: 1, orders: [], filter: { ...FILTER_INITIAL } });
//Состояние таблицы проектов
const [projectsDataGrid] = useProjectsDataGrid({ ...projects.filter, pageNumber: projects.pageNumber, orders: projects.orders });
//Подключение к контексту приложения
const { pOnlineShowDocument } = useContext(ApplicationСtx);
//Отображение записи проекта в штатном разделе
const showProject = async rn => pOnlineShowDocument({ unitCode: "Projects", document: rn, modal: false });
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setProjects(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setProjects(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));
//При изменении фильтра
const handleFilterChanged = values => setProjects(pv => ({ ...pv, filter: { ...values }, pageNumber: 1 }));
//Генерация содержимого
return (
<>
<Filter values={projects.filter} onChange={handleFilterChanged} />
{projectsDataGrid.init ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...projectsDataGrid}
containerComponentProps={{ sx: PROJECTS_STYLES.DATA_GRID_CONTAINER(projectsDataGrid.morePages), elevation: 0 }}
expandable={true}
fixedHeader={true}
onPagesCountChanged={handlePagesCountChanged}
onOrderChanged={handleOrderChanged}
dataCellRender={prms => projectDataCellRender({ ...prms, showProject })}
rowExpandRender={projectRowExpandRender}
/>
) : null}
</>
);
};
//----------------
//Интерфейс модуля
//----------------
export { Projects };

View File

@ -1,82 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список проектов: пользовательские хуки для взаимодействия с сервером
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
import config from "../../../app.config"; //Настройки приложения
//---------
//Константы
//---------
//Размер страницы данных
const DATA_GRID_PAGE_SIZE = config.SYSTEM.PAGE_SIZE;
//-----------
//Тело модуля
//-----------
//Получение данных проектов с сервера
const useProjectsDataGrid = ({ prjType, insDep, priceStructStatus, prjState, search, pageNumber, orders }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({ init: false, morePages: true });
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_PROJECTS_DG",
args: {
SPRJ_TYPE: prjType,
SINS_DEPARTMENT: insDep,
NCOST_STATUS: priceStructStatus,
NSTATE: prjState,
SSEARCH: search,
CORDERS: { VALUE: object2Base64XML(orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
NINCLUDE_DEF: pageNumber == 1 ? 1 : 0
},
respArg: "COUT",
loader: true,
attributeValueProcessor: (name, val) => (["DBEGPLAN", "DENDPLAN"].includes(name) ? formatDateRF(val) : val)
});
setData(pv => ({
...pv,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [],
rows: pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])],
morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE,
init: true
}));
} finally {
setLoading(false);
}
};
loadData();
}, [prjType, insDep, priceStructStatus, prjState, search, pageNumber, orders, executeStored, SERV_DATA_TYPE_CLOB]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//----------------
//Интерфейс модуля
//----------------
export { useProjectsDataGrid };

View File

@ -1,96 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список проектов: дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Icon, Stack, Paper, Link } from "@mui/material"; //Интерфейсные элементы
import { P8P_DATA_GRID_MORE_HEIGHT } from "../../components/p8p_data_grid"; //Таблица данных
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { COMMON_STYLES, formatCostStatusValue, formatCostReadyValue } from "./layouts"; //Общие стили и разметка панели
import { Stages } from "./stages"; //Компонент "Этапы проекта"
//---------
//Константы
//---------
//Высота фильтра (пиксели)
const FILTER_HEIGHT = "60px";
//Стили
const STYLES = {
DATA_GRID_CONTAINER: morePages => ({
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${FILTER_HEIGHT} - ${morePages ? P8P_DATA_GRID_MORE_HEIGHT : "0px"} - 8px)`,
...APP_STYLES.SCROLL
})
};
//-----------
//Тело модуля
//-----------
//Формирование значения для колонки "Состояние" проекта
const formatPrjStateValue = value => {
const [text, icon] =
value == 0
? ["Зарегистрирован", "app_registration"]
: value == 1
? ["Открыт", "lock_open"]
: value == 2
? ["Остановлен", "do_not_disturb_on"]
: value == 3
? ["Закрыт", "lock_outline"]
: value == 4
? ["Согласован", "thumb_up_alt"]
: ["Исполнение прекращено", "block"];
return (
<Stack direction="row" gap={0.5} alignItems="center" justifyContent="center">
<Icon title={text} sx={COMMON_STYLES.STATE(value)}>
{icon}
</Icon>
</Stack>
);
};
//Форматирование ячеек таблицы "Проекты"
const projectDataCellRender = ({ row, columnDef, showProject }) => {
//Формирование представлений
switch (columnDef.name) {
case "NCOST_STATUS":
return { cellProps: { align: "center" }, data: formatCostStatusValue({ value: row[columnDef.name] }) };
case "NCOST_READY":
return { cellProps: { align: "center" }, data: formatCostReadyValue(row[columnDef.name]) };
case "NSTATE":
return { cellProps: { align: "center" }, data: formatPrjStateValue(row[columnDef.name]) };
case "SCODE":
return {
data: (
<Link component="button" align="left" underline="hover" onClick={() => showProject(row["NRN"])}>
{row[columnDef.name]}
</Link>
)
};
default:
return { data: row[columnDef.name] };
}
};
//Генерация представления расширения строки таблицы "Проектов"
const projectRowExpandRender = ({ row }) => {
return (
<Paper elevation={6}>
<Stages projectRn={row.NRN} projectCode={row.SCODE} />
</Paper>
);
};
//----------------
//Интерфейс модуля
//----------------
export { STYLES as PROJECTS_STYLES, projectDataCellRender, projectRowExpandRender };

View File

@ -1,173 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Детальная информация об этапе проекта
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Grid, Box, Typography, Paper, Drawer, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { TEXTS } from "../../../app.text"; //Тектовые ресурсы и константы
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8PChart } from "../../components/p8p_chart"; //График
import { P8PAppInlineError } from "../../components/p8p_app_message"; //Встраиваемое сообщение об ошибке
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useStageDetailInfoDataGrid, useStageDetailArtsDataGrid, useStageDetailArtsChart } from "./stage_detail_hooks"; //Хуки детализации этапов проекта
import { Toggle } from "./layouts"; //Общая разметка и компоненты панели
import {
STAGE_DETAIL_STYLES,
stageDetailInfoHeadCellRender,
stageDetailInfoDataCellRender,
stageDetailArtsHeadCellRender,
stageDetailArtsDataCellRender
} from "./stage_detail_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Данные этапа
const StageDetailData = ({ stageRn }) => {
//Собственное состояние
const [state, setState] = useState({ artsDisplayType: 0, artsChartType: 0 });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Подключение к контексту приложения
const { pOnlineShowUnit } = useContext(ApplicationСtx);
//Подключение к контексту сообщений
const { showMsgErr } = useContext(MessagingСtx);
//Отображение журнала затрат (фактического, по рег. номеру ЛС и статьи затрат)
const showCostNotesFact = async ({ faceAccRn, artclRn }) => {
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_FCCOSTNOTES_FACT_SELECT",
args: { NFACEACC: faceAccRn, NFPDARTCL: artclRn }
});
if (data.NIDENT) pOnlineShowUnit({ unitCode: "CostNotes", inputParameters: [{ name: "in_IDENT", value: data.NIDENT }] });
else showMsgErr(TEXTS.NO_DATA_FOUND);
};
//Состояние таблицы с информацией об этапе
const [stageDeatilInfoDataGrid] = useStageDetailInfoDataGrid({ stageRn });
//Состояние таблицы с данными структуры цены
const [stageDeatilArtsDataGrid] = useStageDetailArtsDataGrid({ stageRn });
//Состояние графика с данными структуры цены
const [stageDeatilArtsChart] = useStageDetailArtsChart({ stageRn, display: state.artsDisplayType == 1, type: state.artsChartType });
//При изменении способа отображения структуры цены
const handleArtsDisplayTypeChange = checked => setState(pv => ({ ...pv, artsDisplayType: checked ? 1 : 0 }));
//При изменении типа данных графика структуры цены
const handleArtsChartTypeChange = checked => setState(pv => ({ ...pv, artsChartType: checked ? 1 : 0 }));
//Отработка нажатия на график
const handleChartClick = ({ item }) =>
state.artsChartType === 1 && item.NFACEACC && item.NFPDARTCL
? showCostNotesFact({ faceAccRn: item.NFACEACC, artclRn: item.NFPDARTCL })
: null;
//Генерация содержимого
return (
<Grid container spacing={2} sx={STAGE_DETAIL_STYLES.DATA_AREA_CONTAINER}>
<Grid item xs={5}>
<Typography variant={"h6"} sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER}>
Сведения
</Typography>
{stageDeatilInfoDataGrid.init ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STAGE_DETAIL_STYLES.DATA_AREA, elevation: 6 }}
{...stageDeatilInfoDataGrid}
size={P8P_DATA_GRID_SIZE.SMALL}
fixedHeader={true}
headCellRender={stageDetailInfoHeadCellRender}
dataCellRender={stageDetailInfoDataCellRender}
/>
) : null}
</Grid>
<Grid item xs={7}>
<Box sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER_CONTAINER}>
<Typography variant={"h6"} sx={STAGE_DETAIL_STYLES.DATA_AREA_HEADER}>
Структура цены
</Typography>
<Toggle labels={["Таблица", "График"]} checked={state.artsDisplayType === 1} onChange={handleArtsDisplayTypeChange} />
</Box>
{state.artsDisplayType === 0 ? (
stageDeatilArtsDataGrid.init ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
containerComponentProps={{ sx: STAGE_DETAIL_STYLES.DATA_AREA, elevation: 6 }}
{...stageDeatilArtsDataGrid}
size={P8P_DATA_GRID_SIZE.SMALL}
fixedHeader={true}
headCellRender={stageDetailArtsHeadCellRender}
dataCellRender={prms => stageDetailArtsDataCellRender({ ...prms, showCostNotesFact })}
/>
) : null
) : (
<Paper elevation={6} sx={STAGE_DETAIL_STYLES.DATA_AREA}>
<Box sx={STAGE_DETAIL_STYLES.CHART_CONTAINER}>
<Toggle labels={["План", "Факт"]} checked={state.artsChartType === 1} onChange={handleArtsChartTypeChange} />
{stageDeatilArtsDataGrid?.rows?.length > 0 ? (
stageDeatilArtsChart.init ? (
<P8PChart style={STAGE_DETAIL_STYLES.CHART} {...stageDeatilArtsChart} onClick={handleChartClick} />
) : null
) : (
<P8PAppInlineError text={TEXTS.NO_DATA_FOUND} />
)}
</Box>
</Paper>
)}
</Grid>
</Grid>
);
};
//Контроль свойств компонента - Данные этапа
StageDetailData.propTypes = {
stageRn: PropTypes.number
};
//-----------
//Тело модуля
//-----------
//Детальная информация об этапе проекта
const StageDetail = ({ stageRn, stageName, isOpen, onClose }) => {
return (
<Drawer anchor={"right"} open={isOpen} onClose={onClose} sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_DRAWER}>
<Box sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_HEADER}>
<IconButton sx={STAGE_DETAIL_STYLES.STAGE_DETAIL_CLOSE_BUTTON} size={"small"} onClick={onClose}>
<Icon>close</Icon>
</IconButton>
<Typography variant={"h6"} color={"white"} pl={2}>{`Этап: ${stageName}`}</Typography>
</Box>
<StageDetailData stageRn={stageRn} />
</Drawer>
);
};
//Контроль свойств компонента - Детальная информация об этапе проекта
StageDetail.propTypes = {
stageRn: PropTypes.number,
stageName: PropTypes.string,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { StageDetail };

View File

@ -1,126 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Детальная информация об этапе проекта: пользовательские хуки для взаимодействия с сервером
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
//-----------
//Тело модуля
//-----------
//Детали этапа проекта - информация об этапе
const useStageDetailInfoDataGrid = ({ stageRn }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({ init: false });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_DTL_DG",
args: { NPROJECTSTAGE: stageRn },
respArg: "COUT",
loader: true
});
setData(pv => ({ ...pv, ...data.XDATA_GRID, init: true }));
} finally {
setLoading(false);
}
};
if (stageRn) loadData();
}, [stageRn, executeStored]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//Детали этапа проекта - структура цены - таблица данных
const useStageDetailArtsDataGrid = ({ stageRn }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({ init: false });
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const artsData = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_ARTS_DG",
args: { NPROJECTSTAGE: stageRn },
respArg: "COUT",
loader: true
});
setData(pv => ({ ...pv, ...artsData.XDATA_GRID, init: true }));
} finally {
setLoading(false);
}
};
if (stageRn) loadData();
}, [stageRn, executeStored, SERV_DATA_TYPE_CLOB]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//Детали этапа проекта - структура цены - график
const useStageDetailArtsChart = ({ stageRn, display, type }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - график
const [data, setData] = useState({ init: false, currentType: null });
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGE_ARTS_CHART",
args: { NPROJECTSTAGE: stageRn, NTYPE: type },
respArg: "COUT",
loader: true
});
setData(pv => ({ ...pv, ...data.XCHART, currentType: type, init: true }));
} finally {
setLoading(false);
}
};
if (stageRn && display && data.currentType != type) loadData();
}, [stageRn, display, type, data.currentType, executeStored]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//----------------
//Интерфейс модуля
//----------------
export { useStageDetailInfoDataGrid, useStageDetailArtsDataGrid, useStageDetailArtsChart };

View File

@ -1,148 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Детальная информация об этапе проекта: дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Link } from "@mui/material"; //Интерфейсные элементы
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
import { formatNumberRFCurrency } from "../../core/utils"; //Вспомогательные функции
import { formatCostStatusValue } from "./layouts"; //Общие стили и разметка панели
import { formatStageStatusValue } from "./stages_layouts"; //Cтили и разметка списка этапов проекта
//---------
//Константы
//---------
//Высота заголовка информационного блока
const DATA_AREA_HEADER_HEIGHT = "52px";
//Стили
const STYLES = {
STAGE_DETAIL_DRAWER: { flexShrink: 0, [`& .MuiDrawer-paper`]: { width: "70%", boxSizing: "border-box", ...APP_STYLES.SCROLL } },
STAGE_DETAIL_HEADER: {
height: APP_BAR_HEIGHT,
paddingLeft: "24px",
backgroundColor: "#1976d2",
display: "flex",
alignItems: "center",
justifyContent: "flex-start"
},
STAGE_DETAIL_CLOSE_BUTTON: { color: "white", marginBottom: "3px" },
DATA_AREA_CONTAINER: { paddingLeft: "10px", paddingRight: "10px" },
DATA_AREA: { height: `calc(100vh - ${APP_BAR_HEIGHT} - ${DATA_AREA_HEADER_HEIGHT} - 10px)`, overflowY: "auto", ...APP_STYLES.SCROLL },
DATA_AREA_HEADER_CONTAINER: { display: "flex", justifyContent: "space-between" },
DATA_AREA_HEADER: { paddingTop: "10px", paddingBottom: "10px" },
DATA_GRID_HEADER: { fontSize: "10pt", padding: "6px 10px" },
DATA_GRID_CELL: value => ({ fontSize: "9pt", padding: "6px 10px", ...(value ? { color: value > 0 ? "green" : "red" } : {}) }),
CHART_CONTAINER: { paddingTop: "20px" },
CHART: { maxHeight: "60vh", display: "flex", justifyContent: "center" }
};
//-----------
//Тело модуля
//-----------
//Форматирование заголовков колонок таблицы "Сведения"
const stageDetailInfoHeadCellRender = ({ columnDef }) => {
//Инициализируем общий стиль ячеек
let cellStyle = STYLES.DATA_GRID_HEADER;
//Формирование представлений
switch (columnDef.name) {
case "SATTR":
return { cellStyle, stackProps: { justifyContent: "left" } };
case "SVALUE":
return { cellStyle, stackProps: { justifyContent: "right" } };
default:
return { cellStyle: cellStyle };
}
};
//Форматирование ячеек строк таблицы "Сведения"
const stageDetailInfoDataCellRender = ({ row, columnDef }) => {
//Инициализируем общий стиль ячеек
let cellStyle = STYLES.DATA_GRID_CELL();
//Формирование представлений
switch (columnDef.name) {
case "SATTR":
return { cellStyle: { ...cellStyle, color: "#1976d2" }, cellProps: { align: "left" } };
case "SVALUE": {
const res = { cellStyle, cellProps: { align: "right" } };
if (["NCOST_SUM", "NSTAGE_COST_SUM"].includes(row["SCODE"]))
res.data = row["SVALUE"] || row["SVALUE"] === 0 ? formatNumberRFCurrency(row["SVALUE"]) : "-";
if (row["SCODE"] == "NSTATE")
res.data = formatStageStatusValue({ value: parseInt(row["SVALUE"]), addText: true, justifyContent: "right" });
return res;
}
default:
return { cellStyle };
}
};
//Форматирование заголовков колонок таблицы "Структура затрат"
const stageDetailArtsHeadCellRender = ({ columnDef }) => {
//Инициализируем общий стиль ячеек
let cellStyle = STYLES.DATA_GRID_HEADER;
//Формирование представлений
switch (columnDef.name) {
case "NSTATE":
return { cellStyle: { ...cellStyle, justifyContent: "center" }, stackStyle: { justifyContent: "center" } };
default:
return { cellStyle: cellStyle };
}
};
//Форматирование ячеек строк таблицы "Структура затрат"
const stageDetailArtsDataCellRender = ({ row, columnDef, showCostNotesFact }) => {
//Инициализируем общий стиль ячеек
let cellStyle = STYLES.DATA_GRID_CELL;
//Формирование представлений
switch (columnDef.name) {
case "NCOST_STATUS":
return {
cellProps: { align: "center" },
data: formatCostStatusValue({ value: row[columnDef.name], type: 0 })
};
case "NPLAN_SUM":
case "NPLAN_FACT_SUM":
return {
cellStyle: cellStyle(columnDef.name == "NPLAN_FACT_SUM" ? row[columnDef.name] : null),
data: row[columnDef.name] || row[columnDef.name] === 0 ? formatNumberRFCurrency(row[columnDef.name]) : "-"
};
case "NFACT_SUM":
return {
cellStyle: cellStyle(),
data:
row[columnDef.name] || row[columnDef.name] === 0 ? (
row[columnDef.name] > 0 ? (
<Link component="button" onClick={() => showCostNotesFact({ faceAccRn: row["NFACEACC"], artclRn: row["NFPDARTCL"] })}>
{formatNumberRFCurrency(row[columnDef.name])}
</Link>
) : (
formatNumberRFCurrency(row[columnDef.name])
)
) : (
"-"
)
};
default:
return { cellStyle: cellStyle(), data: row[columnDef.name] };
}
};
//----------------
//Интерфейс модуля
//----------------
export {
STYLES as STAGE_DETAIL_STYLES,
stageDetailInfoHeadCellRender,
stageDetailInfoDataCellRender,
stageDetailArtsHeadCellRender,
stageDetailArtsDataCellRender
};

View File

@ -1,95 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список этапов проекта
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Typography } from "@mui/material"; //Интерфейсные элементы
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { P8PDataGrid } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { useStagesDataGrid } from "./stages_hooks"; //Хуки списка этапов проекта
import { STAGES_STYLES, projectStageDataCellRender } from "./stages_layouts"; //Дополнительная разметка и вёрстка клиентских элементов
import { StageDetail } from "./stage_detail"; //Компонент "Информация об этапе проекта"
//-----------
//Тело модуля
//-----------
//Список этапов проекта
const Stages = ({ projectRn, projectCode }) => {
//Собственное состояние
const [stages, setStages] = useState({ pageNumber: 1, orders: [] });
//Состояние таблицы этапов
const [stagesDataGrid] = useStagesDataGrid({ ...stages, projectRn });
//Состояние информации о этапе
const [stageInfo, setStageInfo] = useState({ showInfo: false, stage: null, sFaceAcc: null });
//Подключение к контексту приложения
const { pOnlineShowUnit } = useContext(ApplicationСtx);
//Отображение записи этапа проекта в штатном разделе
const showProjectStage = (prn, rn) => {
pOnlineShowUnit({
unitCode: "Projects",
inputParameters: [
{ name: "in_RN", value: prn },
{ name: "in_STAGE_RN", value: rn }
],
modal: false
});
};
//Отображение деталей этапа
const showStageDetails = stage => setStageInfo(pv => ({ ...pv, showInfo: true, stage: stage["NRN"], sFaceAcc: stage["SFACEACC"] }));
//При изменении количества отображаемых страниц
const handlePagesCountChanged = () => setStages(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
//При изменении состояния сортировки
const handleOrderChanged = ({ orders }) => setStages(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));
//Генерация содержимого
return stagesDataGrid.init ? (
<>
<div style={STAGES_STYLES.CONTAINER}>
<Typography variant={"subtitle2"} sx={STAGES_STYLES.TITLE}>
{`Этапы проекта "${projectCode}"`}
</Typography>
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...stagesDataGrid}
containerComponentProps={{ sx: STAGES_STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
onPagesCountChanged={handlePagesCountChanged}
onOrderChanged={handleOrderChanged}
dataCellRender={prms => projectStageDataCellRender({ ...prms, showProjectStage, showStageDetails })}
/>
</div>
<StageDetail
stageRn={stageInfo.stage}
stageName={stageInfo.sFaceAcc}
isOpen={stageInfo.showInfo}
onClose={() => setStageInfo(pv => ({ ...pv, showInfo: false, stage: null, sFaceAcc: null }))}
/>
</>
) : null;
};
//Контроль свойств компонента - Список этапов проекта
Stages.propTypes = {
projectRn: PropTypes.number.isRequired,
projectCode: PropTypes.string.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { Stages };

View File

@ -1,78 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список этапов проекта: пользовательские хуки для взаимодействия с сервером
*/
//---------------------
//Подключение библиотек
//---------------------
import { useState, useContext, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
import config from "../../../app.config"; //Настройки приложения
//---------
//Константы
//---------
//Размер страницы данных
const DATA_GRID_PAGE_SIZE = config.SYSTEM.PAGE_SIZE;
//-----------
//Тело модуля
//-----------
//Этапы проекта
const useStagesDataGrid = ({ projectRn, pageNumber, orders }) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({ init: false, morePages: true });
//Подключение к контексту взаимодействия с сервером
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
//При необходимости обновить данные таблицы
useEffect(() => {
//Загрузка данных таблицы с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_PROJECTS.INFO_STAGES_DG",
args: {
NPROJECT: projectRn,
CORDERS: { VALUE: object2Base64XML(orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
NPAGE_NUMBER: pageNumber,
NPAGE_SIZE: DATA_GRID_PAGE_SIZE,
NINCLUDE_DEF: pageNumber == 1 ? 1 : 0
},
respArg: "COUT",
loader: true,
attributeValueProcessor: (name, val) => (["DBEGPLAN", "DENDPLAN"].includes(name) ? formatDateRF(val) : val)
});
setData(pv => ({
...pv,
columnsDef: data.XDATA_GRID.columnsDef ? [...data.XDATA_GRID.columnsDef] : pv.columnsDef || [],
rows: pageNumber == 1 ? [...(data.XDATA_GRID.rows || [])] : [...(pv.rows || []), ...(data.XDATA_GRID.rows || [])],
morePages: DATA_GRID_PAGE_SIZE == 0 ? false : (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE,
init: true
}));
} finally {
setLoading(false);
}
};
if (projectRn) loadData();
}, [projectRn, orders, pageNumber, executeStored, SERV_DATA_TYPE_CLOB]);
//Возвращаем интерфейс хука
return [data, isLoading];
};
//----------------
//Интерфейс модуля
//----------------
export { useStagesDataGrid };

View File

@ -1,86 +0,0 @@
/*
Парус 8 - Панели мониторинга - ПУП - Информация о проектах
Список этапов проекта: дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { Icon, Stack, Link } from "@mui/material"; //Интерфейсные элементы
import { formatNumberRFCurrency } from "../../core/utils"; //Спомогательные функции
import { COMMON_STYLES, formatCostStatusValue, formatCostReadyValue } from "./layouts"; //Общие стили и разметка панели
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { textAlign: "center", paddingTop: "10px", backgroundColor: "lightcyan" },
TITLE: { fontSize: "13pt", paddingBottom: "10px" },
DATA_GRID_CONTAINER: { backgroundColor: "lightcyan" }
};
//-----------
//Тело модуля
//-----------
//Формирование значения для колонки "Состояние" этапа
const formatStageStatusValue = ({ value, addText = false, justifyContent = "center" }) => {
const [text, icon] =
value == 0
? ["Зарегистрирован", "app_registration"]
: value == 1
? ["Открыт", "lock_open"]
: value == 2
? ["Закрыт", "lock_outline"]
: value == 3
? ["Согласован", "thumb_up_alt"]
: value == 4
? ["Исполнение прекращено", "block"]
: ["Остановлен", "do_not_disturb_on"];
return (
<Stack direction="row" gap={0.5} alignItems={"center"} justifyContent={justifyContent || "center"}>
<Icon title={text} sx={COMMON_STYLES.STATE(value)}>
{icon}
</Icon>
{addText == true ? text : null}
</Stack>
);
};
//Форматирование ячеек таблицы "Этапы проекта"
const projectStageDataCellRender = ({ row, columnDef, showProjectStage, showStageDetails }) => {
//Формирование представлений
switch (columnDef.name) {
case "NCOST_STATUS":
return {
cellProps: { align: "center" },
data: formatCostStatusValue({ value: row[columnDef.name], onClick: () => showStageDetails(row) })
};
case "NCOST_READY":
return { cellProps: { align: "center" }, data: formatCostReadyValue(row[columnDef.name]) };
case "NSTATE":
return { cellProps: { align: "center" }, data: formatStageStatusValue({ value: row[columnDef.name] }) };
case "SFACEACC":
return {
data: (
<Link component="button" align="left" underline="hover" onClick={() => showProjectStage(row["NPRN"], row["NRN"])}>
{row[columnDef.name]}
</Link>
)
};
case "NCOST_SUM":
return { data: formatNumberRFCurrency(row[columnDef.name]) };
default:
return { data: row[columnDef.name] };
}
};
//----------------
//Интерфейс модуля
//----------------
export { STYLES as STAGES_STYLES, projectStageDataCellRender, formatStageStatusValue };

View File

@ -9,23 +9,7 @@
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Box,
IconButton,
Icon,
Dialog,
DialogTitle,
DialogContent,
Button,
Typography,
List,
ListItem,
Select,
FormControl,
InputLabel,
MenuItem,
DialogActions
} from "@mui/material"; //Интерфейсные элементы
import { Box, IconButton, Icon, Dialog, DialogTitle, DialogContent, Typography, List, ListItem } from "@mui/material"; //Интерфейсные элементы
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { SectionTabPanel } from "./section_tab_panel"; //Компонент вкладки раздела
@ -83,8 +67,7 @@ const STYLES = {
},
HELP_LIST_ITEM_DESC: {
fontSize: "inherit"
},
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" }
}
};
//------------------------------------
@ -153,89 +136,6 @@ HelpDialog.propTypes = {
handleOpenHelpChange: PropTypes.func.isRequired
};
//Диалог сортировки
const SortDialog = ({ init, handleOpenSortChange, onOrderChange }) => {
//Собственное состояние сортировки
const [order, setOrder] = useState({ row: init.rowOrder ? init.rowOrder : 0, column: init.columnOrder ? init.columnOrder : 0 });
//Изменеие сортировки
const handleOrderChange = e => setOrder(pv => ({ ...pv, [e.target.name]: e.target.value }));
//Нажатие на кнопку Ok
const handleOk = () => {
onOrderChange({ rowOrder: order.row, columnOrder: order.column });
handleOpenSortChange();
};
//Кнопка "Очистить", значения по умолчанию
const handleClear = () => {
setOrder({ row: 0, column: 0 });
};
//Кнопка "Отмена"
const handleCancel = () => {
handleOpenSortChange();
};
//Генерация содержимого
return (
<Dialog open onClose={handleOpenSortChange}>
<DialogTitle>
<Box display="flex" alignItems="center">
<Box flexGrow={1} textAlign="center">
Сортировка
</Box>
<Box>
<IconButton aria-label="close" onClick={handleCancel}>
<Icon>close</Icon>
</IconButton>
</Box>
</Box>
</DialogTitle>
<DialogContent>
<Box component="section" p={1}>
<FormControl variant="standard" fullWidth>
<InputLabel id="row-label">Строки</InputLabel>
<Select labelId="row-label" id="row" name="row" value={order.row} label="Строки" onChange={handleOrderChange}>
<MenuItem value={0}>Номер</MenuItem>
<MenuItem value={1}>Код</MenuItem>
<MenuItem value={2}>Мнемокод</MenuItem>
</Select>
</FormControl>
</Box>
<Box component="section" p={1}>
<FormControl variant="standard" fullWidth>
<InputLabel id="column-label">Графы</InputLabel>
<Select labelId="column-label" id="column" name="column" value={order.column} label="Графы" onChange={handleOrderChange}>
<MenuItem value={0}>Номер</MenuItem>
<MenuItem value={1}>Код</MenuItem>
<MenuItem value={2}>Мнемокод</MenuItem>
</Select>
</FormControl>
</Box>
</DialogContent>
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button variant="text" onClick={handleOk}>
ОК
</Button>
<Button variant="text" onClick={handleClear}>
Очистить
</Button>
<Button variant="text" onClick={handleCancel}>
Отмена
</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог сортировки
SortDialog.propTypes = {
init: PropTypes.object.isRequired,
handleOpenSortChange: PropTypes.func.isRequired,
onOrderChange: PropTypes.func.isRequired
};
//-----------
//Тело модуля
//-----------
@ -245,8 +145,6 @@ const SectionTab = ({
section,
tabValue,
index,
order,
onOrderChange,
containerProps,
handleMarkAdd,
handleReload,
@ -263,14 +161,6 @@ const SectionTab = ({
setOpenHelp(!openHelp);
};
//Состояние - диалог сортировки
const [openSort, setOpenSort] = useState(false);
//Изменение состояния диалога сортировки
const handleOpenSortChange = () => {
setOpenSort(!openSort);
};
//Генерация содержимого
return (
<>
@ -285,9 +175,6 @@ const SectionTab = ({
</IconButton>
</Box>
<Box>
<IconButton onClick={() => handleOpenSortChange()}>
<Icon>sort</Icon>
</IconButton>
<IconButton onClick={() => handleOpenHelpChange()}>
<Icon>help</Icon>
</IconButton>
@ -330,7 +217,6 @@ const SectionTab = ({
</Box>
) : null}
</SectionTabPanel>
{openSort ? <SortDialog init={order} handleOpenSortChange={handleOpenSortChange} onOrderChange={onOrderChange} /> : null}
{openHelp ? <HelpDialog handleOpenHelpChange={handleOpenHelpChange} /> : null}
</>
);
@ -341,8 +227,6 @@ SectionTab.propTypes = {
section: PropTypes.object.isRequired,
tabValue: PropTypes.number,
index: PropTypes.number,
order: PropTypes.object.isRequired,
onOrderChange: PropTypes.func.isRequired,
containerProps: PropTypes.object,
handleMarkAdd: PropTypes.func,
handleReload: PropTypes.func,

View File

@ -39,7 +39,7 @@ const useWindowResize = () => {
};
//Хук для настройки регламентированного отчета
const useConf = (currentTab, handleSectionChange, order) => {
const useConf = (currentTab, handleSectionChange) => {
//Собственное состояние - таблица данных
const dataGrid = {
rn: 0,
@ -58,7 +58,6 @@ const useConf = (currentTab, handleSectionChange, order) => {
const [rrpConf, setRrpConf] = useState({
docLoaded: false,
sections: [],
orderChanged: false,
reload: true
});
@ -77,116 +76,103 @@ const useConf = (currentTab, handleSectionChange, order) => {
}, []);
//Загрузка данных разделов регламентированного отчёта
const loadData = useCallback(
async () => {
if (rrpConf.reload) {
//Переменная номера раздела с фокусом
let tabFocus = currentTab ? currentTab : 0;
const data = await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONF_GET_SECTIONS",
args: {
NRN_RRPCONF: Number(getNavigationSearch().NRN),
NROW_ORDER: Number(order.rowOrder),
NCOL_ORDER: Number(order.columnOrder)
},
respArg: "COUT"
const loadData = useCallback(async () => {
if (rrpConf.reload) {
//Переменная номера раздела с фокусом
let tabFocus = currentTab ? currentTab : 0;
const data = await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONF_GET_SECTIONS",
args: {
NRN_RRPCONF: Number(getNavigationSearch().NRN)
},
respArg: "COUT"
});
//Флаг первой загрузки данных
let firstLoad = dataGrids.length == 0 ? true : false;
//Копирование массива уже загруженных разделов
let cloneDGs = dataGrids.slice();
//Массив из нескольких разделов и из одного
const sections = data.SECTIONS ? (data.SECTIONS.length ? data.SECTIONS : [data.SECTIONS]) : [];
//Заполнение очередного раздела по шаблону
sections.map(s => {
let dg = {};
Object.assign(dg, dataGrid, {
...s.XDATA.XDATA_GRID,
rn: s.NRN,
code: s.SCODE,
name: s.SNAME,
delete_allow: s.NDELETE_ALLOW,
dataLoaded: true,
columnsDef: [...(s.XDATA.XDATA_GRID.columnsDef || [])],
groups: [...(s.XDATA.XDATA_GRID.groups || [])],
rows: [...(s.XDATA.XDATA_GRID.rows || [])],
reload: false
});
//Флаг первой загрузки данных
let firstLoad = dataGrids.length == 0 ? true : false;
//Копирование массива уже загруженных разделов
let cloneDGs = dataGrids.slice();
//Массив из нескольких разделов и из одного
const sections = data.SECTIONS ? (data.SECTIONS.length ? data.SECTIONS : [data.SECTIONS]) : [];
//Заполнение очередного раздела по шаблону
sections
.sort((a, b) => a.SCODE - b.SCODE)
.map(s => {
let dg = {};
Object.assign(dg, dataGrid, {
...s.XDATA.XDATA_GRID,
rn: s.NRN,
code: s.SCODE,
name: s.SNAME,
delete_allow: s.NDELETE_ALLOW,
dataLoaded: true,
columnsDef: [...(s.XDATA.XDATA_GRID.columnsDef || [])],
groups: [...(s.XDATA.XDATA_GRID.groups || [])],
rows: [...(s.XDATA.XDATA_GRID.rows || [])],
reload: false
});
//Если раздел имеет составы показателей
if (s.MARK_CNS.MARK_CN) {
//Обходим строки раздела
dg.rows.map(row => {
//Цикл по ключам строки
for (let key in row) {
//Если это ключ для группы составов показателей
if (key.match(/MARK_CNS_.*/)) {
//Считываем рег. номер показателя
let markRn = key.substring(9);
//Переносим из раздела
row[key] = Array.isArray(s.MARK_CNS.MARK_CN)
? [...s.MARK_CNS.MARK_CN].filter(el => el.NPRN === row[`NMARK_RN_${markRn}`])
: s.MARK_CNS.MARK_CN.NPRN === row[`NMARK_RN_${markRn}`]
? [s.MARK_CNS.MARK_CN]
: null;
}
}
});
}
//Ищем загружен ли уже раздел с таким же ид.
const dgItem = dataGrids.find(x => x.rn === dg.rn);
//Его индекс, если нет соответствия, то -1
let index = dataGrids.indexOf(dgItem);
//Если было соответствие
if (dgItem) {
//Если в нём не найдено изменений
if (JSON.stringify(dgItem, null, 4) === JSON.stringify(dg, null, 4)) {
//То из копированного массива его удаляем
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dgItem.rn)), 1);
} else {
//Иначе обновляем раздел в массиве
dataGrids[index] = dg;
//Удаляем из копированного массива
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dg.rn)), 1);
//Устанавливаем фокус на обновлённый раздел, если был добавлен
tabFocus = rrpConf.orderChanged ? 0 : index;
//Если раздел имеет составы показателей
if (s.MARK_CNS.MARK_CN) {
//Обходим строки раздела
dg.rows.map(row => {
//Цикл по ключам строки
for (let key in row) {
//Если это ключ для группы составов показателей
if (key.match(/MARK_CNS_.*/)) {
//Считываем рег. номер показателя
let markRn = key.substring(9);
//Переносим из раздела
row[key] = Array.isArray(s.MARK_CNS.MARK_CN)
? [...s.MARK_CNS.MARK_CN].filter(el => el.NPRN === row[`NMARK_RN_${markRn}`])
: s.MARK_CNS.MARK_CN.NPRN === row[`NMARK_RN_${markRn}`]
? [s.MARK_CNS.MARK_CN]
: null;
}
} else {
//Если раздел новый, то добавляем его в массив данных
dataGrids.push(dg);
//И устанавливаем на него фокус, если флаг первой загрузки = false
tabFocus = !firstLoad ? dataGrids.length - 1 : 0;
}
});
//Обходим разделы, что остались в копированном массиве (на удаление)
cloneDGs.map(s => {
let curIndex = dataGrids.indexOf(dataGrids.find(x => x.rn === s.rn));
//Устаревший раздел удаляем из массива данных
dataGrids.splice(curIndex, 1);
//Фокус на предшествующий раздел
if (curIndex > 0) tabFocus = curIndex - 1;
//Иначе фокус на следующий, если был удалён первый раздел
else tabFocus = curIndex;
});
setRrpConf(pv => ({
...pv,
docLoaded: true,
orderChanged: false,
reload: false,
sections: dataGrids
}));
handleSectionChange(tabFocus);
}
},
}
//Ищем загружен ли уже раздел с таким же ид.
const dgItem = dataGrids.find(x => x.rn === dg.rn);
//Его индекс, если нет соответствия, то -1
let index = dataGrids.indexOf(dgItem);
//Если было соответствие
if (dgItem) {
//Если в нём не найдено изменений
if (JSON.stringify(dgItem, null, 4) === JSON.stringify(dg, null, 4)) {
//То из копированного массива его удаляем
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dgItem.rn)), 1);
} else {
//Иначе обновляем раздел в массиве
dataGrids[index] = dg;
//Удаляем из копированного массива
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dg.rn)), 1);
//Устанавливаем фокус на обновлённый раздел
tabFocus = index;
}
} else {
//Если раздел новый, то добавляем его в массив данных
dataGrids.push(dg);
//И устанавливаем на него фокус, если флаг первой загрузки = false
tabFocus = !firstLoad ? dataGrids.length - 1 : 0;
}
});
//Обходим разделы, что остались в копированном массиве (на удаление)
cloneDGs.map(s => {
let curIndex = dataGrids.indexOf(dataGrids.find(x => x.rn === s.rn));
//Устаревший раздел удаляем из массива данных
dataGrids.splice(curIndex, 1);
//Фокус на предшествующий раздел
if (curIndex > 0) tabFocus = curIndex - 1;
//Иначе фокус на следующий, если был удалён первый раздел
else tabFocus = curIndex;
});
setRrpConf(pv => ({
...pv,
docLoaded: true,
reload: false,
sections: dataGrids
}));
handleSectionChange(tabFocus);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
[rrpConf.reload, rrpConf.docLoaded, dataGrid.reload, dataGrid.docLoaded, executeStored]
);
//При изменении сортировок
useEffect(() => {
setRrpConf(pv => ({ ...pv, orderChanged: true, reload: true }));
}, [order]);
}, [rrpConf.reload, rrpConf.docLoaded, dataGrid.reload, dataGrid.docLoaded, executeStored]);
//При необходимости обновить данные таблицы
useEffect(() => {

View File

@ -44,14 +44,8 @@ const RrpConfEditor = () => {
//Состояние вкладки
const [tabValue, handleSectionChange] = useTab("");
//Состояние сортировки строк и граф
const [order, setOrder] = useState({ rowOrder: 0, columnOrder: 0 });
//Изменение состояния сортировки строк и граф
const handleOrder = newOrder => setOrder(newOrder);
//Состояние настройки
const [rrpConf, handleReload] = useConf(tabValue, handleSectionChange, order);
const [rrpConf, handleReload] = useConf(tabValue, handleSectionChange);
//Функции открытия разделов
const [handleMarkOpen, handleMarkCnOpen, handleMarkCnInsert] = useRecOpen(handleReload);
@ -142,8 +136,6 @@ const RrpConfEditor = () => {
section={s}
tabValue={tabValue}
index={i}
order={order}
onOrderChange={handleOrder}
containerProps={{ height, pxOuterMenuH, pxPanelHeaderH, pxTabsH }}
handleReload={handleReload}
handleMarkOpen={handleMarkOpen}

View File

@ -1,156 +0,0 @@
/*
Парус 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'>&#960; = <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 };

View File

@ -46,20 +46,6 @@ const Messages = ({ title }) => {
Ошибка
</Button>
<Divider sx={STYLES.DIVIDER} />
{/* Сообщение об ошибке (диалог с подробностями) */}
<Button
variant="contained"
onClick={() =>
showMsgErr(
"Что-то пошло не так :( ...но мы точно знаем что ;)",
null,
"Здесь подробная информация об ошибке (стек вызова СУБД, например)"
)
}
>
Ошибка с подробностями
</Button>
<Divider sx={STYLES.DIVIDER} />
{/* Предупреждение (диалог) */}
<Button
variant="contained"

View File

@ -19,7 +19,6 @@ import { Chart } from "./chart"; //Пример: Графики "P8PChart"
import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt"
import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG"
import { Cyclogram } from "./cyclogram"; //Пример: Циклограмма "P8PCyclogram"
import { Indicator } from "./indicator"; //Пример: Индикатор "P8PIndicator"
//---------
//Константы
@ -35,8 +34,7 @@ const MODES = {
CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart },
GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt },
SVG: { name: "SVG", caption: 'Интерактивные изображения "P8PSVG"', component: Svg },
CYCLOGRAM: { name: "CYCLOGRAM", caption: 'Циклограмма "P8PCyclogram"', component: Cyclogram },
INDICATOR: { name: "INDICATOR", caption: 'Индикатор "P8PIndicator"', component: Indicator }
CYCLOGRAM: { name: "CYCLOGRAM", caption: 'Циклограмма "P8PCyclogram"', component: Cyclogram }
};
//Стили

View File

@ -1,127 +0,0 @@
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;
/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,25 +3,23 @@ create or replace package PKG_P8PANELS_RRPCONFED as
/* Получение разделов регламентированного отчёта */
procedure RRPCONF_GET_SECTIONS
(
NRN_RRPCONF in number, -- Рег. номер настройки форм регламентированного отчёта
NROW_ORDER in number := 0, -- Порядок сортировки строк
NCOL_ORDER in number := 0, -- Порядок сортировки граф
COUT out clob -- Список разделов
NRN_RRPCONF in number, -- Ид. нстройки форм регламентированного отчёта
COUT out clob -- Список разделов
);
/* Добавление раздела регламентированного отчёта */
procedure RRPCONFSCTN_INSERT
(
NPRN in number, -- Рег. номер настройки форм регламентированного отчёта
NPRN in number, -- Ид. настройки форм регламентированного отчёта
SCODE in varchar2, -- Мнемокод
SNAME in varchar2, -- Наименование
NRN out number -- Рег. номер созданной записи
NRN out number -- Ид. созданной записи
);
/* Исправление раздела регламентированного отчёта */
procedure RRPCONFSCTN_UPDATE
(
NRN in number, -- Рег. номер раздела
NRN in number, -- Ид. раздела
SCODE in varchar2, -- Мнемокод раздела
SNAME in varchar2 -- Наименование раздела
);
@ -29,16 +27,16 @@ create or replace package PKG_P8PANELS_RRPCONFED as
/* Удаление раздела регламентированного отчёта */
procedure RRPCONFSCTN_DELETE
(
NRN in number -- Рег. номер раздела
NRN in number -- Ид. раздела
);
/* Получение кодов настройки, раздела и показателя раздела по ид. показателя раздела */
procedure RRPCONFSCTNMRK_GET_CODES
(
NRN in number, -- Рег. номер показателя раздела
SRRPCONF out varchar2, -- Мнемокод настройки формы регламентированного отчёта
SRRPCONFSCTN out varchar2, -- Мнемокод раздела
SRRPCONFSCTNMRK out varchar2 -- Мнемокод показателя раздела
NRN in number, -- Ид. показателя раздела
SRRPCONF out varchar2, -- Код настройки формы регламентированного отчёта
SRRPCONFSCTN out varchar2, -- Код раздела
SRRPCONFSCTNMRK out varchar2 -- Код показателя раздела
);
/* Формирование кода и наименования показателя раздела регламентированного отчёта */
@ -61,25 +59,25 @@ create or replace package PKG_P8PANELS_RRPCONFED as
/* Добавление показателя раздела регламентированного отчёта */
procedure RRPCONFSCTNMRK_INSERT
(
NPRN in number, -- Рег. номер раздела
NPRN in number, -- Ид. раздела
SCODE in varchar2, -- Мнемокод показателя раздела
SNAME in varchar2, -- Наименование показателя раздела
NRRPROW in number, -- Рег. номер строки
NRRPCOLUMN in number, -- Рег. номер графы
NRN out number -- Рег. номер созданной записи
NRN out number -- Ид. созданной записи
);
/* Исправление показателя раздела регламентированного отчёта */
procedure RRPCONFSCTNMRK_UPDATE
(
NRN in number, -- Рег. номер показателя раздела
NRN in number, -- Ид. показателя раздела
SNAME in varchar2 -- Новое наименование
);
/* Удаление показателя раздела регламентированного отчёта */
procedure RRPCONFSCTNMRK_DELETE
(
NRN in number -- Рег. номер показателя раздела
NRN in number -- Ид. показателя раздела
);
end PKG_P8PANELS_RRPCONFED;
@ -780,9 +778,7 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
/* Получение разделов регламентированного отчёта */
procedure RRPCONF_GET_SECTIONS
(
NRN_RRPCONF in number, -- Рег. номер настройки форм регламентированного отчёта
NROW_ORDER in number := 0, -- Порядок сортировки строк
NCOL_ORDER in number := 0, -- Порядок сортировки граф
NRN_RRPCONF in number, -- Ид. нстройки форм регламентированного отчёта
COUT out clob -- Список разделов
)
is
@ -798,140 +794,58 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
SSECTION_CODE PKG_STD.TSTRING; -- Мнемокод раздела настройки
SSECTION_NAME PKG_STD.TSTRING; -- Наименование раздела настройки
CSECTION_CLOB clob; -- Данные по разделу настройки
CSQL_ROWS clob; -- Запрос по строкам
SORDERS_R PKG_STD.TSTRING; -- Сортировка строк
ICURSOR_R integer; -- Курсор для исполнения запроса по строкам
NROW_RN PKG_STD.TREF; -- Рег. номер строки
NROW_POS PKG_STD.TNUMBER := 0; -- Позиция строки
SROW_CODE PKG_STD.TSTRING; -- Код строки
SROW_NAME PKG_STD.TSTRING; -- Наименование строки
CSQL_COLS clob; -- Запрос по графам
SORDERS_C PKG_STD.TSTRING; -- Сортировка граф
ICURSOR_C integer; -- Курсор для исполнения запроса по графам
NCOL_RN PKG_STD.TREF; -- Рег. номер графы
SCOL_CODE PKG_STD.TSTRING; -- Код графы
SCOL_NAME PKG_STD.TSTRING; -- Наименование графы
/* Формирование текстового представления сортировки */
function ORDER_TEXT_GET
(
NORDER in number, -- Порядок сортировки
SALIAS in varchar2, -- Алиас таблицы
NTYPE in number -- Тип таблицы (0 - графы, 1 - строки)
) return varchar2 -- Текстовое представление сортировки
is
SRESULT PKG_STD.TSTRING; -- Текстовое представление сортировки
STABLE_COLUMN_CODE PKG_STD.TSTRING; -- Наименование колонки таблицы, содержащую код
begin
/* Определяем наименование колонки, содержающую код */
if (NTYPE = 0) then
/* Код графы */
STABLE_COLUMN_CODE := 'COLUMN_CODE';
else
/* Код графы */
STABLE_COLUMN_CODE := 'ROW_CODE';
end if;
/* Формируем представление сортировки */
case NORDER
/* Код - номер сортировки - мнемокод */
when 1 then
SRESULT := PKG_SQL_BUILD.LPAD_() || '(' || SALIAS || '.' || STABLE_COLUMN_CODE || ', 40, ''0''),' || SALIAS ||
'.SORT_NUMB,' || PKG_SQL_BUILD.LPAD_() || '(' || SALIAS || '.CODE, 20, ''0'')';
/* Мнемокод - номер сортировки - код */
when 2 then
SRESULT := PKG_SQL_BUILD.LPAD_() || '(' || SALIAS || '.CODE, 20, ''0''),' || SALIAS || '.SORT_NUMB,' ||
PKG_SQL_BUILD.LPAD_() || '(' || SALIAS || '.' || STABLE_COLUMN_CODE || ', 40, ''0'')';
/* Номер сортировки - код - мнемокод */
else
SRESULT := SALIAS || '.SORT_NUMB,' || PKG_SQL_BUILD.LPAD_() || '(' || SALIAS || '.' || STABLE_COLUMN_CODE ||
', 40, ''0''),' || PKG_SQL_BUILD.LPAD_() || '(' || SALIAS || '.CODE, 20, ''0'')';
end case;
/* Возвращаем результат */
return SRESULT;
end ORDER_TEXT_GET;
/* Инициализация колонок граф показателей */
procedure MARKS_COLUMNS_INIT
(
RDG in out nocopy PKG_P8PANELS_VISUAL.TDG, -- Описание таблицы
NRRPCONFSCTN in number, -- Рег. номер раздела
SORDER in varchar2 -- Сортировка
NRRPCONFSCTN in number -- Рег. номер раздела
)
is
CSQL clob; -- Запрос по графам показателей раздела
ICURSOR integer; -- Курсор для исполнения запроса по графам показателей раздела
SCOL_CODE PKG_STD.TSTRING; -- Мнемокод графы
SCOL_NAME PKG_STD.TSTRING; -- Наименование графы
begin
/* Добавляем подсказку совместимости */
CSQL := null;
CSQL := PKG_SQL_BUILD.COMPATIBLE(SSQL => CSQL);
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => 'select C.CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' C.NAME');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from RRPCONFSCTNMRK T,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' RRPCOLUMN C');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' where T.PRN = ' || NRRPCONFSCTN);
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' and T.RRPCOLUMN = C.RN');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' group by C.RN,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' C.CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' C.NAME,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' C.COLUMN_CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' C.SORT_NUMB');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' order by ' || SORDER);
/* Разбираем его */
ICURSOR := PKG_SQL_DML.OPEN_CURSOR(SWHAT => 'SELECT');
PKG_SQL_DML.PARSE(ICURSOR => ICURSOR, SQUERY => CSQL);
/* Описываем структуру записи курсора */
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 1);
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 2);
/* Делаем выборку */
if (PKG_SQL_DML.EXECUTE(ICURSOR => ICURSOR) = 0) then
null;
end if;
/* Обходим графы показателей раздела */
while (PKG_SQL_DML.FETCH_ROWS(ICURSOR => ICURSOR) > 0)
/* Цикл по графам показателей раздела */
for REC in (select C.CODE,
C.NAME
from RRPCONFSCTNMRK T,
RRPCOLUMN C
where T.PRN = NRRPCONFSCTN
and T.RRPCOLUMN = C.RN
group by C.RN,
C.CODE,
C.NAME
order by C.CODE)
loop
/* Считываем рег. номер строки */
PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR, IPOSITION => 1, SVALUE => SCOL_CODE);
/* Считываем код строки */
PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR, IPOSITION => 2, SVALUE => SCOL_NAME);
/* Наименование графы */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SCOL_' || SCOL_CODE,
SCAPTION => SCOL_NAME,
SNAME => 'SCOL_' || REC.CODE,
SCAPTION => REC.NAME,
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
NWIDTH => 200);
/* Рег. номер графы */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NCOL_RN_' || SCOL_CODE,
SCAPTION => SCOL_NAME || ' рег. номер',
SNAME => 'NCOL_RN_' || REC.CODE,
SCAPTION => REC.NAME || ' рег. номер',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB,
BVISIBLE => false);
/* Рег. номер показателя */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NMARK_RN_' || SCOL_CODE,
SCAPTION => SCOL_NAME || ' рег. номер показателя',
SNAME => 'NMARK_RN_' || REC.CODE,
SCAPTION => REC.NAME || ' рег. номер показателя',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB,
BVISIBLE => false);
/* Мнемокод показателя */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SMARK_CODE_' || SCOL_CODE,
SCAPTION => SCOL_NAME || ' мнемокод показателя',
SNAME => 'SMARK_CODE_' || REC.CODE,
SCAPTION => REC.NAME || ' мнемокод показателя',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
BVISIBLE => false);
/* Для составов показтелей */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'MARK_CNS_' || SCOL_CODE,
SCAPTION => SCOL_NAME || ' состав показателя',
SNAME => 'MARK_CNS_' || REC.CODE,
SCAPTION => REC.NAME || ' состав показателя',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
BVISIBLE => false);
end loop;
/* Освобождаем курсор */
PKG_SQL_DML.CLOSE_CURSOR(ICURSOR => ICURSOR);
exception
when others then
PKG_SQL_DML.CLOSE_CURSOR(ICURSOR => ICURSOR);
raise;
end MARKS_COLUMNS_INIT;
/* Считывание показателя по строке/графе */
@ -984,159 +898,91 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
loop
/* Инициализируем таблицу данных */
RDG := PKG_P8PANELS_VISUAL.TDG_MAKE(BFIXED_HEADER => true, NFIXED_COLUMNS => 1);
/* Наименование строки */
/* Формируем структуру заголовка */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SROW_NAME',
SCAPTION => '',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
NWIDTH => 150);
/* Позиция строки */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NROW_POS',
SCAPTION => 'Позиция строки',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB,
BVISIBLE => false);
/* Мнемокод строки */
/* Формируем структуру заголовка */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SROW_CODE',
SCAPTION => 'Мнемокод строки',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
BVISIBLE => false);
/* Рег. номер строки */
/* Формируем структуру заголовка */
PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NROW_RN',
SCAPTION => 'Рег. номер строки',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB,
BVISIBLE => false);
/* Обнуляем счётчик позиции строки */
NROW_POS := 0;
/* Если раздел содержит показатели */
if (S.NMARKS_EXISTS = 1) then
/* Формируем текстовое представление сортировки графы */
SORDERS_C := ORDER_TEXT_GET(NORDER => NCOL_ORDER, SALIAS => 'C', NTYPE => 0);
/* Инициализируем колонки граф */
MARKS_COLUMNS_INIT(RDG => RDG, NRRPCONFSCTN => S.NRN, SORDER => SORDERS_C);
begin
/* Формируем текстовое представление сортировки строки */
SORDERS_R := ORDER_TEXT_GET(NORDER => NROW_ORDER, SALIAS => 'R', NTYPE => 1);
/* Добавляем подсказку совместимости */
CSQL_ROWS := PKG_SQL_BUILD.COMPATIBLE();
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => 'select R.RN,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' R.CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' R.NAME');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' from RRPCONFSCTNMRK T,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' RRPROW R');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' where T.PRN = ' || S.NRN);
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' and R.RN = T.RRPROW');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' group by R.RN,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' R.CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' R.NAME,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' R.ROW_CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' R.SORT_NUMB');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_ROWS, SELEMENT1 => ' order by ' || SORDERS_R);
/* Разбираем его */
ICURSOR_R := PKG_SQL_DML.OPEN_CURSOR(SWHAT => 'SELECT');
PKG_SQL_DML.PARSE(ICURSOR => ICURSOR_R, SQUERY => CSQL_ROWS);
/* Описываем структуру записи курсора */
PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR_R, IPOSITION => 1);
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR_R, IPOSITION => 2);
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR_R, IPOSITION => 3);
/* Делаем выборку */
if (PKG_SQL_DML.EXECUTE(ICURSOR => ICURSOR_R) = 0) then
null;
end if;
/* Обходим выбранные записи */
while (PKG_SQL_DML.FETCH_ROWS(ICURSOR => ICURSOR_R) > 0)
MARKS_COLUMNS_INIT(RDG => RDG, NRRPCONFSCTN => S.NRN);
/* Обходим строки раздела */
for R in (select R.RN,
R.CODE,
R.NAME
from RRPCONFSCTNMRK T,
RRPROW R
where T.PRN = S.NRN
and R.RN = T.RRPROW
group by R.RN,
R.CODE,
R.NAME,
R.ROW_CODE,
R.SORT_NUMB
order by R.SORT_NUMB,
LPAD(R.ROW_CODE, 40, '0'),
LPAD(R.CODE, 20, '0'))
loop
/* Заполняем наименование строки */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SROW_NAME', SVALUE => R.NAME, BCLEAR => true);
/* Заполняем мнемокод строки */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SROW_CODE', SVALUE => R.CODE);
/* Заполняем рег. номер строки */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NROW_RN', NVALUE => R.RN);
/* Обходим графы раздела */
for C in (select C.RN,
C.CODE,
C.NAME
from RRPCONFSCTNMRK T,
RRPCOLUMN C
where T.PRN = S.NRN
and C.RN = T.RRPCOLUMN
group by C.RN,
C.CODE,
C.NAME,
C.COLUMN_CODE,
C.SORT_NUMB
order by C.SORT_NUMB,
LPAD(C.COLUMN_CODE, 40, '0'),
LPAD(C.CODE, 20, '0'))
loop
/* Считываем рег. номер строки */
PKG_SQL_DML.COLUMN_VALUE_NUM(ICURSOR => ICURSOR_R, IPOSITION => 1, NVALUE => NROW_RN);
/* Считываем код строки */
PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR_R, IPOSITION => 2, SVALUE => SROW_CODE);
/* Считываем наименование строки */
PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR_R, IPOSITION => 3, SVALUE => SROW_NAME);
/* Заполняем наименование строки */
/* Считываем показатель по строке/графе */
RRRPCONFSCTNMRK := RRPCONFSCTNMRK_GET_ROWCOL(NRRPCONFSCTN => S.NRN, NRRPROW => R.RN, NRRPCOLUMN => C.RN);
/* Заполняем рег. номер графы */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SCOL_' || C.CODE, SVALUE => C.NAME);
/* Заполняем рег. номер графы */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NCOL_RN_' || C.CODE, NVALUE => C.RN);
/* Заполняем рег. номер показателя */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW,
SNAME => 'SROW_NAME',
SVALUE => SROW_NAME,
BCLEAR => true);
/* Заполняем позицию строки */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NROW_POS', NVALUE => NROW_POS);
/* Заполняем мнемокод строки */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SROW_CODE', SVALUE => SROW_CODE);
/* Заполняем рег. номер строки */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NROW_RN', NVALUE => NROW_RN);
/* Добавляем подсказку совместимости */
CSQL_COLS := PKG_SQL_BUILD.COMPATIBLE();
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => 'select C.RN,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' C.CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' C.NAME');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' from RRPCONFSCTNMRK T,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' RRPCOLUMN C');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' where T.PRN = ' || S.NRN);
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' and C.RN = T.RRPCOLUMN');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' group by C.RN,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' C.CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' C.NAME,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' C.COLUMN_CODE,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' C.SORT_NUMB');
PKG_SQL_BUILD.APPEND(SSQL => CSQL_COLS, SELEMENT1 => ' order by ' || SORDERS_C);
/* Разбираем его */
ICURSOR_C := PKG_SQL_DML.OPEN_CURSOR(SWHAT => 'SELECT');
PKG_SQL_DML.PARSE(ICURSOR => ICURSOR_C, SQUERY => CSQL_COLS);
/* Описываем структуру записи курсора */
PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR_C, IPOSITION => 1);
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR_C, IPOSITION => 2);
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR_C, IPOSITION => 3);
/* Делаем выборку */
if (PKG_SQL_DML.EXECUTE(ICURSOR => ICURSOR_C) = 0) then
null;
end if;
/* Обходим выбранные записи */
while (PKG_SQL_DML.FETCH_ROWS(ICURSOR => ICURSOR_C) > 0)
loop
/* Считываем рег. номер графы */
PKG_SQL_DML.COLUMN_VALUE_NUM(ICURSOR => ICURSOR_C, IPOSITION => 1, NVALUE => NCOL_RN);
/* Считываем код графы */
PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR_C, IPOSITION => 2, SVALUE => SCOL_CODE);
/* Считываем наименование графы */
PKG_SQL_DML.COLUMN_VALUE_STR(ICURSOR => ICURSOR_C, IPOSITION => 3, SVALUE => SCOL_NAME);
/* Считываем показатель по строке/графе */
RRRPCONFSCTNMRK := RRPCONFSCTNMRK_GET_ROWCOL(NRRPCONFSCTN => S.NRN,
NRRPROW => NROW_RN,
NRRPCOLUMN => NCOL_RN);
/* Заполняем наименование графы */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SCOL_' || SCOL_CODE, SVALUE => SCOL_NAME);
/* Заполняем рег. номер графы */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NCOL_RN_' || SCOL_CODE, NVALUE => NCOL_RN);
/* Заполняем рег. номер показателя */
SNAME => 'NMARK_RN_' || C.CODE,
NVALUE => RRRPCONFSCTNMRK.RN);
/* Если ошибка считывания показателя */
if (RRRPCONFSCTNMRK.RN is not null) then
/* Заполняем мнемокод показателя */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW,
SNAME => 'NMARK_RN_' || SCOL_CODE,
NVALUE => RRRPCONFSCTNMRK.RN);
/* Если ошибка считывания показателя */
if (RRRPCONFSCTNMRK.RN is not null) then
/* Заполняем мнемокод показателя */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW,
SNAME => 'SMARK_CODE_' || SCOL_CODE,
SVALUE => RRRPCONFSCTNMRK.CODE);
/* Добавляем атрибут состава показателей */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'MARK_CNS_' || SCOL_CODE, SVALUE => null);
end if;
end loop;
/* Освобождаем курсор */
PKG_SQL_DML.CLOSE_CURSOR(ICURSOR => ICURSOR_C);
/* Добавим строку для раздела */
PKG_P8PANELS_VISUAL.TDG_ADD_ROW(RDATA_GRID => RDG, RROW => RDG_ROW);
/* Инкрементируем счётчик позиции строки */
NROW_POS := NROW_POS + 1;
SNAME => 'SMARK_CODE_' || C.CODE,
SVALUE => RRRPCONFSCTNMRK.CODE);
/* Добавляем атрибут состава показателей */
PKG_P8PANELS_VISUAL.TDG_ROW_ADD_COL(RROW => RDG_ROW, SNAME => 'MARK_CNS_' || C.CODE, SVALUE => null);
end if;
end loop;
/* Освобождаем курсор */
PKG_SQL_DML.CLOSE_CURSOR(ICURSOR => ICURSOR_R);
exception
when others then
PKG_SQL_DML.CLOSE_CURSOR(ICURSOR => ICURSOR_R);
PKG_SQL_DML.CLOSE_CURSOR(ICURSOR => ICURSOR_C);
raise;
end;
/* Добавим строку для раздела */
PKG_P8PANELS_VISUAL.TDG_ADD_ROW(RDATA_GRID => RDG, RROW => RDG_ROW);
end loop;
end if;
/* Сериализуем описание */
CDG := PKG_P8PANELS_VISUAL.TDG_TO_XML(RDATA_GRID => RDG, NINCLUDE_DEF => 1);
@ -1212,10 +1058,10 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
/* Добавление раздела регламентированного отчёта */
procedure RRPCONFSCTN_INSERT
(
NPRN in number, -- Рег. номер настройки форм регламентированного отчёта
NPRN in number, -- Ид. настройки форм регламентированного отчёта
SCODE in varchar2, -- Мнемокод
SNAME in varchar2, -- Наименование
NRN out number -- Рег. номер созданной записи
NRN out number -- Ид. созданной записи
)
is
NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации
@ -1240,7 +1086,7 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
/* Исправление раздела регламентированного отчёта */
procedure RRPCONFSCTN_UPDATE
(
NRN in number, -- Рег. номер раздела
NRN in number, -- Ид. раздела
SCODE in varchar2, -- Мнемокод раздела
SNAME in varchar2 -- Наименование раздела
)
@ -1279,7 +1125,7 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
/* Удаление раздела регламентированного отчёта */
procedure RRPCONFSCTN_DELETE
(
NRN in number -- Рег. номер раздела
NRN in number -- Ид. раздела
)
is
NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации
@ -1296,10 +1142,10 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
/* Получение кодов настройки, раздела и показателя раздела по ид. показателя раздела */
procedure RRPCONFSCTNMRK_GET_CODES
(
NRN in number, -- Рег. номер показателя раздела
SRRPCONF out varchar2, -- Мнемокод настройки формы регламентированного отчёта
SRRPCONFSCTN out varchar2, -- Мнемокод раздела
SRRPCONFSCTNMRK out varchar2 -- Мнемокод показателя раздела
NRN in number, -- Ид. показателя раздела
SRRPCONF out varchar2, -- Код настройки формы регламентированного отчёта
SRRPCONFSCTN out varchar2, -- Код раздела
SRRPCONFSCTNMRK out varchar2 -- Код показателя раздела
)
is
begin
@ -1381,12 +1227,12 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
/* Добавление показателя раздела регламентированного отчёта */
procedure RRPCONFSCTNMRK_INSERT
(
NPRN in number, -- Рег. номер раздела
NPRN in number, -- Ид. раздела
SCODE in varchar2, -- Мнемокод показателя раздела
SNAME in varchar2, -- Наименование показателя раздела
NRRPROW in number, -- Рег. номер строки
NRRPCOLUMN in number, -- Рег. номер графы
NRN out number -- Рег. номер созданной записи
NRN out number -- Ид. созданной записи
)
is
NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации
@ -1439,7 +1285,7 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
/* Исправление показателя раздела регламентированного отчёта */
procedure RRPCONFSCTNMRK_UPDATE
(
NRN in number, -- Рег. номер показателя раздела
NRN in number, -- Ид. показателя раздела
SNAME in varchar2 -- Новое наименование
)
is
@ -1494,7 +1340,7 @@ create or replace package body PKG_P8PANELS_RRPCONFED as
/* Удаление показателя раздела регламентированного отчёта */
procedure RRPCONFSCTNMRK_DELETE
(
NRN in number -- Рег. номер показателя раздела
NRN in number -- Ид. показателя раздела
)
is
NCOMPANY PKG_STD.TREF := GET_SESSION_COMPANY(); -- Рег. номер организации

View File

@ -1,56 +1,46 @@
create or replace package PKG_P8PANELS_VISUAL as
/* Константы - типы данных */
SDATA_TYPE_STR constant PKG_STD.TSTRING := 'STR'; -- Тип данных "строка"
SDATA_TYPE_NUMB constant PKG_STD.TSTRING := 'NUMB'; -- Тип данных "число"
SDATA_TYPE_DATE constant PKG_STD.TSTRING := 'DATE'; -- Тип данных "дата"
SDATA_TYPE_STR constant PKG_STD.TSTRING := 'STR'; -- Тип данных "строка"
SDATA_TYPE_NUMB constant PKG_STD.TSTRING := 'NUMB'; -- Тип данных "число"
SDATA_TYPE_DATE constant PKG_STD.TSTRING := 'DATE'; -- Тип данных "дата"
/* Константы - направление сортировки */
SORDER_DIRECTION_ASC constant PKG_STD.TSTRING := 'ASC'; -- По возрастанию
SORDER_DIRECTION_DESC constant PKG_STD.TSTRING := 'DESC'; -- По убыванию
SORDER_DIRECTION_ASC constant PKG_STD.TSTRING := 'ASC'; -- По возрастанию
SORDER_DIRECTION_DESC constant PKG_STD.TSTRING := 'DESC'; -- По убыванию
/* Константы - диаграмма Ганта - масштаб */
NGANTT_ZOOM_QUARTER_DAY constant PKG_STD.TNUMBER := 0; -- Четверть дня
NGANTT_ZOOM_HALF_DAY constant PKG_STD.TNUMBER := 1; -- Пол дня
NGANTT_ZOOM_DAY constant PKG_STD.TNUMBER := 2; -- День
NGANTT_ZOOM_WEEK constant PKG_STD.TNUMBER := 3; -- Неделя
NGANTT_ZOOM_MONTH constant PKG_STD.TNUMBER := 4; -- Месяц
/* Константы - масштаб диаграммы Ганта */
NGANTT_ZOOM_QUARTER_DAY constant PKG_STD.TNUMBER := 0; -- Четверть дня
NGANTT_ZOOM_HALF_DAY constant PKG_STD.TNUMBER := 1; -- Пол дня
NGANTT_ZOOM_DAY constant PKG_STD.TNUMBER := 2; -- День
NGANTT_ZOOM_WEEK constant PKG_STD.TNUMBER := 3; -- Неделя
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'; -- Кольцевая
/* Константы - масштаб циклограммы */
NCYCLOGRAM_ZOOM_MIN constant PKG_STD.TLNUMBER := 0.2; -- Минимальный (0.2 от исходного)
NCYCLOGRAM_ZOOM_TINY constant PKG_STD.TLNUMBER := 0.4; -- Мелкий (0.4 от исходного)
NCYCLOGRAM_ZOOM_SMALL constant PKG_STD.TLNUMBER := 0.7; -- Уменьшенный (0.7 от исходного)
NCYCLOGRAM_ZOOM_DEFAULT constant PKG_STD.TLNUMBER := 1; -- Исходный
NCYCLOGRAM_ZOOM_LARGE constant PKG_STD.TLNUMBER := 1.5; -- Увеличенный (1.5 от исходного)
NCYCLOGRAM_ZOOM_HUGE constant PKG_STD.TLNUMBER := 2; -- Большой (2 от исходного)
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_ZOOM_MIN constant PKG_STD.TLNUMBER := 0.2; -- Минимальный (0.2 от исходного)
NCYCLOGRAM_ZOOM_TINY constant PKG_STD.TLNUMBER := 0.4; -- Мелкий (0.4 от исходного)
NCYCLOGRAM_ZOOM_SMALL constant PKG_STD.TLNUMBER := 0.7; -- Уменьшенный (0.7 от исходного)
NCYCLOGRAM_ZOOM_DEFAULT constant PKG_STD.TLNUMBER := 1; -- Исходный
NCYCLOGRAM_ZOOM_LARGE constant PKG_STD.TLNUMBER := 1.5; -- Увеличенный (1.5 от исходного)
NCYCLOGRAM_ZOOM_HUGE constant PKG_STD.TLNUMBER := 2; -- Большой (2 от исходного)
NCYCLOGRAM_ZOOM_MAX constant PKG_STD.TLNUMBER := 2.5; -- Максимальный (2.5 от исходного)
/* Константы - расположение легенды графика */
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_HEIGHT constant PKG_STD.TNUMBER := 42; -- Ширина заголовка группы (по умолчанию)
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'; -- Плоский с рамкой
/* Константы - циклограмма */
NCYCLOGRAM_GROUP_DEF_WIDTH constant PKG_STD.TNUMBER := 100; -- Высота заголовка группы (по умолчанию)
NCYCLOGRAM_GROUP_DEF_HEIGHT constant PKG_STD.TNUMBER := 42; -- Ширина заголовка группы (по умолчанию)
NCYCLOGRAM_LINE_HEIGHT constant PKG_STD.TNUMBER := 20; -- Высота строк циклограммы
/* Типы данных - значение колонки таблицы данных */
type TDG_COL_VAL is record
@ -343,21 +333,6 @@ create or replace package PKG_P8PANELS_VISUAL as
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
(
@ -775,92 +750,62 @@ create or replace package PKG_P8PANELS_VISUAL as
NINCLUDE_DEF in number := 1 -- Включить описание колонок (0 - нет, 1 - да)
) 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;
/
create or replace package body PKG_P8PANELS_VISUAL as
/* Константы - тэги запросов */
SRQ_TAG_XROOT constant PKG_STD.TSTRING := 'XROOT'; -- Тэг для корня данных запроса
SRQ_TAG_XFILTERS constant PKG_STD.TSTRING := 'filters'; -- Тэг для строк данных
SRQ_TAG_XORDERS constant PKG_STD.TSTRING := 'orders'; -- Тэг для описания колонок
SRQ_TAG_SNAME constant PKG_STD.TSTRING := 'name'; -- Тэг для наименования
SRQ_TAG_SDIRECTION constant PKG_STD.TSTRING := 'direction'; -- Тэг для направления
SRQ_TAG_SFROM constant PKG_STD.TSTRING := 'from'; -- Тэг для значения "с"
SRQ_TAG_STO constant PKG_STD.TSTRING := 'to'; -- Тэг для значения "по"
SRQ_TAG_XROOT constant PKG_STD.TSTRING := 'XROOT'; -- Тэг для корня данных запроса
SRQ_TAG_XFILTERS constant PKG_STD.TSTRING := 'filters'; -- Тэг для строк данных
SRQ_TAG_XORDERS constant PKG_STD.TSTRING := 'orders'; -- Тэг для описания колонок
SRQ_TAG_SNAME constant PKG_STD.TSTRING := 'name'; -- Тэг для наименования
SRQ_TAG_SDIRECTION constant PKG_STD.TSTRING := 'direction'; -- Тэг для направления
SRQ_TAG_SFROM constant PKG_STD.TSTRING := 'from'; -- Тэг для значения "с"
SRQ_TAG_STO constant PKG_STD.TSTRING := 'to'; -- Тэг для значения "по"
/* Константы - тэги ответов */
SRESP_TAG_XDATA constant PKG_STD.TSTRING := 'XDATA'; -- Тэг для корня описания данных
SRESP_TAG_XCHART constant PKG_STD.TSTRING := 'XCHART'; -- Тэг для описания графика
SRESP_TAG_XDATA_GRID constant PKG_STD.TSTRING := 'XDATA_GRID'; -- Тэг для описания таблицы данных
SRESP_TAG_XCYCLOGRAM constant PKG_STD.TSTRING := 'XCYCLOGRAM'; -- Тэг для описания циклограммы
SRESP_TAG_XGANTT constant PKG_STD.TSTRING := 'XGANTT'; -- Тэг для описания диаграммы Ганта
SRESP_TAG_XINDICATOR constant PKG_STD.TSTRING := 'XINDICATOR'; -- Тэг для описания индикатора
SRESP_TAG_XDATA constant PKG_STD.TSTRING := 'XDATA'; -- Тэг для корня описания данных
SRESP_TAG_XCHART constant PKG_STD.TSTRING := 'XCHART'; -- Тэг для описания графика
SRESP_TAG_XDATA_GRID constant PKG_STD.TSTRING := 'XDATA_GRID'; -- Тэг для описания таблицы данных
SRESP_TAG_XCYCLOGRAM constant PKG_STD.TSTRING := 'XCYCLOGRAM'; -- Тэг для описания циклограммы
SRESP_TAG_XGANTT constant PKG_STD.TSTRING := 'XGANTT'; -- Тэг для описания диаграммы Ганта
/* Константы - атрибуты ответов (универсальные) */
SRESP_ATTR_NAME constant PKG_STD.TSTRING := 'name'; -- Атрибут для наименования
SRESP_ATTR_CAPTION constant PKG_STD.TSTRING := 'caption'; -- Атрибут для подписи
SRESP_ATTR_DATA_TYPE constant PKG_STD.TSTRING := 'dataType'; -- Атрибут для типа данных
SRESP_ATTR_VISIBLE constant PKG_STD.TSTRING := 'visible'; -- Атрибут для флага видимости
SRESP_ATTR_TITLE constant PKG_STD.TSTRING := 'title'; -- Атрибут для заголовка
SRESP_ATTR_ZOOM constant PKG_STD.TSTRING := 'zoom'; -- Атрибут для масштаба
SRESP_ATTR_ID constant PKG_STD.TSTRING := 'id'; -- Атрибут для идентификатора
SRESP_ATTR_START constant PKG_STD.TSTRING := 'start'; -- Атрибут для даты начала
SRESP_ATTR_END constant PKG_STD.TSTRING := 'end'; -- Атрибут для даты окончания
SRESP_ATTR_RN constant PKG_STD.TSTRING := 'rn'; -- Атрибут для рег. номера
SRESP_ATTR_NUMB constant PKG_STD.TSTRING := 'numb'; -- Атрибут для номера
SRESP_ATTR_FULL_NAME constant PKG_STD.TSTRING := 'fullName'; -- Атрибут для полного наименования
SRESP_ATTR_DESC constant PKG_STD.TSTRING := 'desc'; -- Атрибут для описания
SRESP_ATTR_TYPE constant PKG_STD.TSTRING := 'type'; -- Атрибут для типа
SRESP_ATTR_HINT constant PKG_STD.TSTRING := 'hint'; -- Атрибут для подсказки
SRESP_ATTR_GROUP_NAME constant PKG_STD.TSTRING := 'groupName'; -- Атрибут для наименования группы
SRESP_ATTR_PARENT constant PKG_STD.TSTRING := 'parent'; -- Атрибут для ссылки на родителя
SRESP_ATTR_EXPANDABLE constant PKG_STD.TSTRING := 'expandable'; -- Атрибут для доступности действия сокрытия/отображения
SRESP_ATTR_EXPANDED constant PKG_STD.TSTRING := 'expanded'; -- Атрибут для флага сокрытия/отображения
SRESP_ATTR_FIXED_HEADER constant PKG_STD.TSTRING := 'fixedHeader'; -- Атрибут для флага фиксации заголовка
SRESP_ATTR_FIXED_COLUMNS constant PKG_STD.TSTRING := 'fixedColumns'; -- Атрибут для количества фиксированных колонок
SRESP_ATTR_WIDTH constant PKG_STD.TSTRING := 'width'; -- Атрибут для ширины
SRESP_ATTR_HEIGHT constant PKG_STD.TSTRING := 'height'; -- Атрибут для высоты
SRESP_ATTR_COLUMNS constant PKG_STD.TSTRING := 'columns'; -- Атрибут для колонок
SRESP_ATTR_TASKS constant PKG_STD.TSTRING := 'tasks'; -- Атрибут для задач
SRESP_ATTR_HL_COLOR constant PKG_STD.TSTRING := 'highlightColor'; -- Атрибут для цвета подсветки
SRESP_ATTR_ZOOM_BAR constant PKG_STD.TSTRING := 'zoomBar'; -- Атрибут для флага отображения панели масштаба
SRESP_ATTR_ROWS constant PKG_STD.TSTRING := 'rows'; -- Атрибут для строк данных
SRESP_ATTR_COLUMNS_DEF constant PKG_STD.TSTRING := 'columnsDef'; -- Атрибут для описания колонок
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_NAME constant PKG_STD.TSTRING := 'name'; -- Атрибут для наименования
SRESP_ATTR_CAPTION constant PKG_STD.TSTRING := 'caption'; -- Атрибут для подписи
SRESP_ATTR_DATA_TYPE constant PKG_STD.TSTRING := 'dataType'; -- Атрибут для типа данных
SRESP_ATTR_VISIBLE constant PKG_STD.TSTRING := 'visible'; -- Атрибут для флага видимости
SRESP_ATTR_TITLE constant PKG_STD.TSTRING := 'title'; -- Атрибут для заголовка
SRESP_ATTR_ZOOM constant PKG_STD.TSTRING := 'zoom'; -- Атрибут для масштаба
SRESP_ATTR_ID constant PKG_STD.TSTRING := 'id'; -- Атрибут для идентификатора
SRESP_ATTR_START constant PKG_STD.TSTRING := 'start'; -- Атрибут для даты начала
SRESP_ATTR_END constant PKG_STD.TSTRING := 'end'; -- Атрибут для даты окончания
SRESP_ATTR_RN constant PKG_STD.TSTRING := 'rn'; -- Атрибут для рег. номера
SRESP_ATTR_NUMB constant PKG_STD.TSTRING := 'numb'; -- Атрибут для номера
SRESP_ATTR_FULL_NAME constant PKG_STD.TSTRING := 'fullName'; -- Атрибут для полного наименования
SRESP_ATTR_DESC constant PKG_STD.TSTRING := 'desc'; -- Атрибут для описания
SRESP_ATTR_TYPE constant PKG_STD.TSTRING := 'type'; -- Атрибут для типа
SRESP_ATTR_HINT constant PKG_STD.TSTRING := 'hint'; -- Атрибут для подсказки
SRESP_ATTR_GROUP_NAME constant PKG_STD.TSTRING := 'groupName'; -- Атрибут для наименования группы
SRESP_ATTR_PARENT constant PKG_STD.TSTRING := 'parent'; -- Атрибут для ссылки на родителя
SRESP_ATTR_EXPANDABLE constant PKG_STD.TSTRING := 'expandable'; -- Атрибут для доступности действия сокрытия/отображения
SRESP_ATTR_EXPANDED constant PKG_STD.TSTRING := 'expanded'; -- Атрибут для флага сокрытия/отображения
SRESP_ATTR_FIXED_HEADER constant PKG_STD.TSTRING := 'fixedHeader'; -- Атрибут для флага фиксации заголовка
SRESP_ATTR_FIXED_COLUMNS constant PKG_STD.TSTRING := 'fixedColumns'; -- Атрибут для количества фиксированных колонок
SRESP_ATTR_WIDTH constant PKG_STD.TSTRING := 'width'; -- Атрибут для ширины
SRESP_ATTR_HEIGHT constant PKG_STD.TSTRING := 'height'; -- Атрибут для высоты
SRESP_ATTR_COLUMNS constant PKG_STD.TSTRING := 'columns'; -- Атрибут для колонок
SRESP_ATTR_TASKS constant PKG_STD.TSTRING := 'tasks'; -- Атрибут для задач
SRESP_ATTR_HL_COLOR constant PKG_STD.TSTRING := 'highlightColor'; -- Атрибут для цвета подсветки
SRESP_ATTR_ZOOM_BAR constant PKG_STD.TSTRING := 'zoomBar'; -- Атрибут для флага отображения панели масштаба
SRESP_ATTR_ROWS constant PKG_STD.TSTRING := 'rows'; -- Атрибут для строк данных
SRESP_ATTR_COLUMNS_DEF constant PKG_STD.TSTRING := 'columnsDef'; -- Атрибут для описания колонок
SRESP_ATTR_GROUPS constant PKG_STD.TSTRING := 'groups'; -- Атрибут для описания групп
/* Константы - атрибуты ответов (таблица данных) */
SRESP_ATTR_DT_ORDER constant PKG_STD.TSTRING := 'order'; -- Атрибут для флага сортировки
SRESP_ATTR_DT_FILTER constant PKG_STD.TSTRING := 'filter'; -- Атрибут для флага отбора
SRESP_ATTR_DT_COLUMN_VALUES constant PKG_STD.TSTRING := 'values'; -- Атрибут для предопределённых значений
SRESP_ATTR_DT_ORDER constant PKG_STD.TSTRING := 'order'; -- Атрибут для флага сортировки
SRESP_ATTR_DT_FILTER constant PKG_STD.TSTRING := 'filter'; -- Атрибут для флага отбора
SRESP_ATTR_DT_COLUMN_VALUES constant PKG_STD.TSTRING := 'values'; -- Атрибут для предопределённых значений
/* Константы - атрибуты ответов (диаграмма Ганта) */
SRESP_ATTR_TASK_PROGRESS constant PKG_STD.TSTRING := 'progress'; -- Атрибут для прогресса задачи
@ -875,23 +820,23 @@ create or replace package body PKG_P8PANELS_VISUAL as
SRESP_ATTR_TASK_COLORS constant PKG_STD.TSTRING := 'taskColors'; -- Атрибут для коллекции цветов задачи
/* Константы - атрибуты ответов (графики) */
SRESP_ATTR_CHART_LGND_POS constant PKG_STD.TSTRING := 'legendPosition'; -- Атрибут для места размешения легенды графика
SRESP_ATTR_CHART_LABELS constant PKG_STD.TSTRING := 'labels'; -- Атрибут для меток графика
SRESP_ATTR_CHART_DATASETS constant PKG_STD.TSTRING := 'datasets'; -- Атрибут для наборов данных графика
SRESP_ATTR_CHART_DS_LABEL constant PKG_STD.TSTRING := 'label'; -- Атрибут для метки набора данных графика
SRESP_ATTR_CHART_DS_BR_CLR constant PKG_STD.TSTRING := 'borderColor'; -- Атрибут для цвета границы элемента набора данных графика
SRESP_ATTR_CHART_DS_BG_CLR constant PKG_STD.TSTRING := 'backgroundColor'; -- Атрибут для цвета заливки элемента набора данных графика
SRESP_ATTR_CHART_DS_DATA constant PKG_STD.TSTRING := 'data'; -- Атрибут для коллекции значений элементов набора данных
SRESP_ATTR_CHART_DS_ITEMS constant PKG_STD.TSTRING := 'items'; -- Атрибут для коллекции элементов набора данных
SRESP_ATTR_CHART_DS_I_VAL constant PKG_STD.TSTRING := 'value'; -- Атрибут для значения элемента набора данных
SRESP_ATTR_CHART_LGND_POS constant PKG_STD.TSTRING := 'legendPosition'; -- Атрибут для места размешения легенды графика
SRESP_ATTR_CHART_LABELS constant PKG_STD.TSTRING := 'labels'; -- Атрибут для меток графика
SRESP_ATTR_CHART_DATASETS constant PKG_STD.TSTRING := 'datasets'; -- Атрибут для наборов данных графика
SRESP_ATTR_CHART_DS_LABEL constant PKG_STD.TSTRING := 'label'; -- Атрибут для метки набора данных графика
SRESP_ATTR_CHART_DS_BR_CLR constant PKG_STD.TSTRING := 'borderColor'; -- Атрибут для цвета границы элемента набора данных графика
SRESP_ATTR_CHART_DS_BG_CLR constant PKG_STD.TSTRING := 'backgroundColor'; -- Атрибут для цвета заливки элемента набора данных графика
SRESP_ATTR_CHART_DS_DATA constant PKG_STD.TSTRING := 'data'; -- Атрибут для коллекции значений элементов набора данных
SRESP_ATTR_CHART_DS_ITEMS constant PKG_STD.TSTRING := 'items'; -- Атрибут для коллекции элементов набора данных
SRESP_ATTR_CHART_DS_I_VAL constant PKG_STD.TSTRING := 'value'; -- Атрибут для значения элемента набора данных
/* Константы - атрибуты ответов (циклограмма) */
SRESP_ATTR_CG_TASK_LINE constant PKG_STD.TSTRING := 'lineNumb'; -- Атрибут для номера строки (по оси Y)
SRESP_ATTR_CG_LINE_HEIGHT constant PKG_STD.TSTRING := 'lineHeight'; -- Атрибут для высоты строк
SRESP_ATTR_CG_TASK_LINE constant PKG_STD.TSTRING := 'lineNumb'; -- Атрибут для номера строки (по оси Y)
SRESP_ATTR_CG_LINE_HEIGHT constant PKG_STD.TSTRING := 'lineHeight'; -- Атрибут для высоты строк
/* Константы - параметры условий отбора */
SCOND_FROM_POSTFIX constant PKG_STD.TSTRING := 'From'; -- Постфикс наименования нижней границы условия отбора
SCOND_TO_POSTFIX constant PKG_STD.TSTRING := 'To'; -- Постфикс наименования верхней границы условия отбора
SCOND_FROM_POSTFIX constant PKG_STD.TSTRING := 'From'; -- Постфикс наименования нижней границы условия отбора
SCOND_TO_POSTFIX constant PKG_STD.TSTRING := 'To'; -- Постфикс наименования верхней границы условия отбора
/* Расчет диапаона выдаваемых записей */
procedure UTL_ROWS_LIMITS_CALC
@ -3124,93 +3069,5 @@ create or replace package body PKG_P8PANELS_VISUAL as
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
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;
/

8327
dist/p8-panels.js vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

View File

@ -8,7 +8,6 @@
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelFin" caption="Экономика проектов" panelName="PrjFin"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelJob" caption="Работы проектов" panelName="PrjJobs"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelGraph" caption="Графики проектов" panelName="PrjGraph"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelInfo" caption="Информация о проектах" panelName="PrjInfo"/>
<MenuItem parent="{BA073333-DFBC-4BA3-8EA7-172F3F6B4FEE}" name="ShowPrjPanelHelp" caption="Инструкции ПУП" panelName="PrjHelp"/>
</App>
<App name="EquipSrv">
@ -60,16 +59,6 @@
icon="insights"
showInPanelsList="true"
preview="./img/prj_graph.jpg"/>
<Panel
name="PrjInfo"
group="Планирование и учёт в проектах"
caption="Информация о проектах"
desc="Информация о проектах"
url="prj_info"
path="prj_info"
icon="featured_play_list"
showInPanelsList="true"
preview="./img/prj_info.jpg"/>
<Panel
name="PrjHelp"
group="Планирование и учёт в проектах"
@ -120,16 +109,6 @@
icon="psychology"
showInPanelsList="true"
preview="./img/mech_rec_cost_jobs_manage.jpg"/>
<Panel
name="MechRecCostJobsManageMP"
group="Планирование и учёт в дискретном производстве"
caption="Выдача сменного задания на участок"
desc="Управление составом сменных заданий трудового ресурса"
url="mech_rec_cost_jobs_manage_mp"
path="mech_rec_cost_jobs_manage_mp"
icon=""
showInPanelsList="false"
preview=""/>
<Panel
name="MechRecDeptCostJobs"
group="Планирование и учёт в дискретном производстве"

743
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,6 @@
"@mui/x-tree-view": "^7.11.0",
"babel-loader": "^9.1.3",
"chart.js": "^4.4.0",
"css-loader": "^7.1.2",
"dayjs": "^1.11.9",
"eslint": "^8.46.0",
"eslint-plugin-react": "^7.33.1",
@ -36,11 +35,8 @@
"file-loader": "^6.2.0",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"react-grid-layout": "^1.5.1",
"react-router-dom": "^6.15.0",
"style-loader": "^4.0.0",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
}

View File

@ -48,8 +48,7 @@ module.exports = {
options: {
name: "[path][name].[hash].[ext]"
}
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] }
}
]
}
};