Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 426864a4b7 | |||
| 814a15c80e |
182
README.md
182
README.md
@ -104,21 +104,8 @@
|
||||
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", размещённый в этом же каталоге.
|
||||
|
||||
При компиляции учитывайте следующее:
|
||||
|
||||
- объекты `PKG_P8PANELS`, `PKG_P8PANELS_BASE`, `PKG_P8PANELS_VISUAL` и `P8PNL_SELECTLIST` - обязательны к компиляции. **Это ядро серверной части расширения. Без этих объектов его работа невозможна. Все остальные, перечисленные ниже объекты серверной части - прикладные, не обязательны к компиляции и на функционирование непосредственно фреймворка влияния не оказывают.**
|
||||
- объекты `PKG_P8PANELS_SAMPLES`, `P8PNL_SMPL_CYCLOGRAM` и `P8PNL_SMPL_GANTT` - обеспечивают работу панели "Samples" ("Примеры для разработчиков"). Это абстрактный набор примеров применения фреймворка, не привязанный жестко к комплектации Системы. Должен откомпилироваться практически в любой комплектации Системы (единственное требование - наличие словаря "Контрагенты").
|
||||
- объекты `PKG_P8PANELS_PROJECTS`, `P8PNL_JB_JOBS`, `P8PNL_JB_JOBSPREV`, `P8PNL_JB_PERIODS`, `P8PNL_JB_PRJCTS` и `P8PNL_JB_PRMS` - могут быть откомпилированы в Системе, имеющей в комлектации приложения "Планирование и учёт в проектах" и "Управление закупками, складом и реализацией". Эти объекты отвечают за работу панелей группы "Планирование и учёт в проектах" ("PrjFin" - "Экономика проектов", "PrjJobs" - "Работы проектов", "PrjGraph" - "Графики проектов", "PrjInfo" - "Информация о проектах"). Компилируйте только если комлектация Системы включает указанные приложения.
|
||||
- объект `PKG_P8PANELS_CLNTTSKBRD` - может быть откомпилирован в Системе, имеющей в комлектации приложение "Управление деловыми процессами". Этот объект отвечает за работу панелей группы "Управление деловыми процессами" ("ClntTaskBoard" - "Доски задач"). Компилируйте только если комлектация Системы включает указанное приложение.
|
||||
- объект `PKG_P8PANELS_MECHREC` - может быть откомпилирован в Системе, имеющей в комлектации приложение "Планирование и учёт в дискретном производстве". Этот объект отвечает за работу панелей группы "Планирование и учёт в дискретном производстве" ("MechRecCostProdPlans" - "Производственная программа", "MechRecDeptCostProdPlans" - "Производственный план цеха", "MechRecCostJobsManage" - "Выдача сменного задания", "MechRecCostJobsManageMP" - "Выдача сменного задания на участок", "MechRecDeptCostJobs" - "Загрузка цеха", "MechRecAssemblyMon" - "Мониторинг сборки изделий"). Компилируйте только если комлектация Системы включает указанное приложение.
|
||||
- объект `PKG_P8PANELS_EQUIPSRV` - может быть откомпилирован в Системе, имеющей в комлектации приложение "Управление техническим обслуживанием и ремонтами". Этот объект отвечает за работу панелей группы "Управление техническим обслуживанием и ремонтами" ("EqsPrfrm" - "Выполнение работ по ТОиР"). Компилируйте только если комлектация Системы включает указанное приложение.
|
||||
- объект `PKG_P8PANELS_RRPCONFED` - может быть откомпилирован в Системе, имеющей в комлектации приложение "Сервис регламентированной и управленческой отчётности". Этот объект отвечает за работу панели "RrpConfEditor" ("Редактор настройки регламентированного отчёта"). Компилируйте только если комлектация Системы включает указанное приложение.
|
||||
|
||||
7. Перезапустите сервер приложений "ПАРУС 8 Онлайн"
|
||||
|
||||
> **Внимание:** при установке учитывайте следующее:
|
||||
@ -129,12 +116,7 @@ git clone https://git.citpb.ru/CITKParus/P8-Panels.git
|
||||
> - Права доступа - файлы конфигурации и файлы дистрибутива фреймворка должны быть доступны процессу WEB-сервера
|
||||
>
|
||||
> - **Для Windows 7 и прочих устаревших версий Windows**
|
||||
>
|
||||
> - Версия IIS, доступная для этих ОС, зачастую не имеет автоматической поддержки шрифтов в формате "WOFF2", применяемых фреймворком. Это может вызывать некорректное отображение панелей, иконок, некоторых элементов пользовательского интерфейса. Добавте в файл "web.config", сервера приложений "ПАРУС 8 Онлайн", строку для определения формата: `<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />`. Путь для добавления данной настройки в файл "web.config": `configuration/system.webServer/staticContent/<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />`
|
||||
>
|
||||
> - **Для всех**
|
||||
>
|
||||
> - Если после установки фреймворка, при открытии панелей появляется ошибка `Internal server error` (HTTP-статус ответа сервера - 500), и Вы уверены, что перепроверили всё описанное выше в интрукции и не нашли ошибок установки, то: остановите сервер приложений "ПАРУС 8 Онлайн", сделайте резервную копию его каталога "bin" и удалите каталог, затем скачайте "web.zip" из дистрибутива Вашего релиза "ПАРУС 8 Онлайн" и восстановите удалённый каталог "bin", распаковав его из архива. Затем - запустите сервер приложений "ПАРУС 8 Онлайн".
|
||||
|
||||
## V. Подключение панелей
|
||||
|
||||
@ -355,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 Онлайн" с очисткой системного кэша.
|
||||
|
||||
#### Настройка КОР-действия для вызова панели "Производственная программа" из раздела "Планы и отчеты производства изделий"
|
||||
|
||||
Входящая в состав поставки фреймворка панель "Производственная программа" доступна для вызова из спецификации "Выпуск" раздела "Планы и отчеты производства изделий" (приложение "Планирование и учёт в дискретном производстве", главное меню > "Документы" > "Планы и отчеты производства изделий").
|
||||
@ -531,7 +460,7 @@ c:\inetpub\p8web20\WebClient\Modules\P8-Panels>npm run build
|
||||
- `isRespErr` - функция, проверка результата исполнения серверного объекта на наличие ошибок
|
||||
- `getRespErrMessage` - функция, получение ошибки исполнения серверного объекта
|
||||
- `getRespPayload` - функция, получение выходных значений, полученных после успешного исполнения
|
||||
- `executeStored` - функция, асинхронное исполнение хранимой процедуры/функции БД Системы
|
||||
- `executeStored` -функция, асинхронное исполнение хранимой процедуры/функции БД Системы
|
||||
- `getConfig` - функция, асинхронное считывание параметров конфигурации, определённых в "p8panels.config" (возвращает их JSON-представление)
|
||||
|
||||
При формировании ответов, функции, получающие данные с сервера, возвращают типовые значения:
|
||||
@ -599,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
|
||||
}
|
||||
```
|
||||
|
||||
@ -613,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`) или полный типовой ответ (описан выше).
|
||||
|
||||
@ -1108,7 +1035,7 @@ const Mui = ({ title }) => {
|
||||
|
||||

|
||||
|
||||
###### `undefined showMsg(type, text, msgOnOk = null, msgOnCancel = null, fullErrorText = null)`
|
||||
###### `undefined showMsg(type, text, msgOnOk = null, msgOnCancel = null)`
|
||||
|
||||
Отображает модальное окно сообщения заданного типа.
|
||||
|
||||
@ -1116,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`
|
||||
|
||||
@ -1218,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"
|
||||
@ -1282,7 +1190,7 @@ const Messages = ({ title }) => {
|
||||
|
||||

|
||||
|
||||
###### `undefined showLoader(message)`
|
||||
###### `undefined showMsg(message)`
|
||||
|
||||
Отображает модальный индикатор процесса с указанным сообщением.
|
||||
|
||||
@ -1349,14 +1257,14 @@ const Loader = ({ title }) => {
|
||||
- состоят из значительного числа интерфейсных примитивов
|
||||
- имеют специальный API на стороне сервера БД Системы для управления их содержимым
|
||||
|
||||
Необходимо понимать, что с одной стороны, наличие серверного API в БД значительно упрощает взаимодействие с компонентом, с другой стороны - ограничивает возможности его применения только теми прикладными задачами и функциональными возможностями, которые заложены в него. При этом "примитивы" HTML и MUI, хоть и сложнее в применении, но позволяют "собирать" практически любые интерфейсные решения на вкус разработчика.
|
||||
Необходимо понимать, что с одной стороны, наличие серверного API в БД значительно упрощает взаимодействие с компонентом, с другой стороны - ограничивает возможности его примерения только теми прикладными задачами и функциональными возможностями, которые заложены в него. При этом "примитивы" HTML и MUI, хоть и сложнее в применении, но позволяют "собирать" практически любые интерфейсные решения на вкус разработчика.
|
||||
|
||||
##### Таблица данных "P8PDataGrid"
|
||||
|
||||
Предназначена для формирования табличных представлений данных с поддержкой:
|
||||
|
||||
- постраничного вывода данных
|
||||
- сортировки и отбора данных по колонкам на стороне сервера БД
|
||||
- сортировки и отбора данных по колонкам на строне сервера БД
|
||||
- сложных заголовков с возможностью отображения/сокрытия уровней
|
||||
- разворачивающихся строк (accordion)
|
||||
- группировки строк с возможностью отображения/сокрытия содержимого группы
|
||||
@ -1383,17 +1291,11 @@ const MyPanel = () => {
|
||||
|
||||
**Свойства**
|
||||
|
||||
`style` - необязательный, объект, если задан, то будет применён в качестве атрибута `style` коневого контейнера (`div`) компонента\
|
||||
`tableStyle` - необязательный, объект, если задан, то будет применён в качестве атрибута `sx` компонента `Table`\
|
||||
`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>}`\
|
||||
`rows` - необязательный, массив, отображаемые таблицой строки данных, содержит объекты вида `{groupName: <ИМЯ_ГРУППЫ_СОДЕРЖАЩЕЙ_СТРОКУ>, <ИМЯ_КОЛОНКИ>: <ЗНАЧЕНИЕ>}`\
|
||||
`size` - необязательный, строка, размер отступов при вёрстке таблицы, `small|medium` (см. константу `P8P_DATA_GRID_SIZE` в исходном коде компонента)\
|
||||
`pageNumber` - необязательный, число, номер отображаемой страницы таблицы, по умолчанию - 1\
|
||||
`pagesCount` - необязательный, число, количество страниц таблицы, по умолчанию - 0. При значении > 0, параметр `morePages` игнорируется, а у таблицы появляется область отображения страниц (исходя из параметров `pagesAlign` и `pagesPosition`)\
|
||||
`pagesAlign` - необязательный, строка, размещение области страниц по вертикали, по умолчанию `right`, поддерживает значения `left|right|center` (см. константу `P8P_DATA_GRID_PAGINATOR_ALIGN` в исходном коде компонента)\
|
||||
`pagesPosition` - необязательный, строка, размещение области страниц по горизонтали, по умолчанию `bottom`, поддерживает значения `top|bottom|both` (см. константу `P8P_DATA_GRID_PAGINATOR_POSITION` в исходном коде компонента)\
|
||||
`fixedHeader` - необязательный, логический, признак фиксации заголовка таблицы, по умолчанию - `false`\
|
||||
`fixedColumns` - необязательный, число, количество фиксированных колонок слева, по умолчанию - 0\
|
||||
`morePages` - необязательный, логический, признак отображения кнопки догрузки данных, по умолчанию - `false`\
|
||||
@ -1417,11 +1319,10 @@ const MyPanel = () => {
|
||||
`rowExpandRender` - необязательный, функция формирования представления развёрнутой строки таблицы (если не указана - интерфейсный элемент для "разворачивания" строки не будет отображён, даже при `expandable=true`). Сигнатура функции `f({row, columnsDef})`. Будет вызвана в момент "развёртывания" строки таблицы пользователем, в функцию будет передан объект, поле `row` которого будет содержать данные текущей "разворачиваемой" строки таблицы, а поле `columnsDef` - описание колонок таблицы. Должна возвращать представление "развёрнутой" строки таблицы в виде значения или Rect-компонента.\
|
||||
`valueFormatter` - необязательный, функция форматирования значений колонки (если не указана - форматирование согласно `columnsDef`). Сигнатура функции `f({value, columnDef})`. Будет вызвана в момент формирования ячейки таблицы (если ранее для ячейки `dataCellRender` не вернул специального представления) и в моммент формирования фильтра для ячейки. Должна возвращать отформатированное значение ячейки или React-компонент для её представления.\
|
||||
`containerComponent` - необязательный, функциональный React-компонент или строка с именем HTML-тэга, будет применён для формирования в иерархии DOM элемента-обёртки (контейнера) таблицы (по умолчанию используется компонет библиотеки MUI - Paper)\
|
||||
`containerComponentProps` - необязательный, объект, содержит свойства, которые будут переданы компоненту-контейнеру (`TableContainer`) таблицы (является дочерним для корневого `div`, контейнера всего компонента)\
|
||||
`containerComponentProps` - необязательный, объект, содержит свойства, которые будут переданы компоненту-контейнеру таблицы\
|
||||
`onOrderChanged` - необязательный, функция, будет вызвана при изменении пользователем состояния сортировок таблицы. Сигнатура функции `f({orders})`, результат функции не интерпретируется. В функцию передаётся объект, поле `orders` которого, содержит текущее состояние сортировок таблицы. Объект `orders` - массив, содержащий элементы вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, direction: <ASC|DESC>}`. Функция применяется для инициации обновления данных в таблице.\
|
||||
`onFilterChanged` - необязательный, функция, будет вызвана при изменении пользователем состояния фильтров таблицы. Сигнатура функции `f({filters})`, результат функции не интерпретируется. В функцию передаётся объект, поле `filters` которого, содержит текущее состояние фильтров таблицы. Объект `filters` - массив, содержащий элементы вида `{name: <НАИМЕНОВАНИЕ_КОЛОНКИ>, from: <ЗНАЧЕНИЕ_НАЧАЛА_ДИАПАЗОНА_ОТБОРА>, to: <ЗНАЧЕНИЕ_ОКОНЧАНИЯ_ДИАПАЗОНА_ОТБОРА>}`. Функция применяется для инициации обновления данных в таблице.\
|
||||
`onPagesCountChanged` - необязательный, функция, будет вызвана при изменении пользователем количества отображаемых страниц данных таблицы. Сигнатура функции `f()`, результат функции не интерпретируется. Функция применяется для инициации обновления данных в таблице.\
|
||||
`onPageChanged` - необязательный, функция, будет вызвана при изменении пользователем страницы в области отображения страниц (при `pagesCount` > 0). Сигнатура функции `f({page})`, результат функции не интерпретируется. В функцию передается объект, поле `page` которого, содержит номер выбранной страницы. Значение `page` - число. Функция применяется для инициации обновления данных в таблице.
|
||||
`objectsCopier` - обязательный, функция глубокого копирования объектов (применяется при обслуживании собственного состояния таблицы данных). Сигнатура функции `f(Object)`, должна возвращать глубокую копию объекта.
|
||||
|
||||
Некоторые параметры таблицы данных вынесены в свойства компонента `P8PDataGrid` для минимизации его связи с фреймворком и поддержания возможности стороннего использования (например, свойства `orderAscMenuItemCaption`, `okFilterBtnCaption`, `objectsCopier` и т.п.) . Тем не менее, в настройках фреймворка и его окружении уже есть реализации для данных свойств. Например, в "app.text.js" уже содержатся объявления типовых констант для текстов подписей кнопок и пунктов меню, в "app/core/utils.js" реализована элементарная функция глубокого копиования `deepCopyObject`. Поэтому, в "app/config_wrapper.js" для привязки свойств `P8PDataGrid` к контексту фреймворка реализованы специальные декораторы и объекты-шаблоны, облегчающие подключение экземпляра `P8PDataGrid` к панели и снимающие с разработчика необходимость указывать некоторые из перечисленных выше обязательных свойств. В предложенном ниже примере, из модуля "config_wrapper" в панель импортируется объект `P8P_DATA_GRID_CONFIG_PROPS`, который уже содержт преднастроенное описание свойств `orderAscMenuItemCaption`, `orderDescMenuItemCaption`, `filterMenuItemCaption`, `valueFilterCaption`, `valueFromFilterCaption`, `valueToFilterCaption`, `okFilterBtnCaption`, `clearFilterBtnCaption`, `cancelFilterBtnCaption`, `morePagesBtnCaption`, `noDataFoundText` и `objectsCopier`, полученное из окружения фреймворка. Таким образом, прикладной разработчик может не указывать их значения при использовании `P8PDataGrid` (если по каким-то причинам не хочет их переопределить, конечно).
|
||||
@ -1445,7 +1346,6 @@ const MyPanel = () => {
|
||||
|
||||
Для таблицы данных это (см. детальные описания программных интерфейсов в пакете `PKG_P8PANELS_VISUAL`):
|
||||
`PKG_P8PANELS_VISUAL.TDG_MAKE` - функция, инициализация таблицы данных, возвращает объект для хранения описания таблицы\
|
||||
`PKG_P8PANELS_VISUAL.TDG_PAGES_COUNT_SET` - процедура, служит для обновления значения количества отображаемых страниц\
|
||||
`PKG_P8PANELS_VISUAL.TDG_ADD_COL_DEF` - процедура, добавление описания колонки в таблицу, принимает на вход объект с описанием таблицы и параметры, описывающие добавляемую колонку (её имя, заголовок, тип данных, видимость, доступность отбора и сортировки, набор предопределённых значений и т.д.)\
|
||||
`PKG_P8PANELS_VISUAL.TDG_COL_VALS_ADD` - процедура, служит для формирования коллекции предопределённых значений колонки таблицы (подготовленная коллекция передаётся в `RCOL_VALS` вызова `TDG_ADD_COL_DEF`, если необходимо)\
|
||||
`PKG_P8PANELS_VISUAL.TDG_ADD_GROUP` - процедура, служит для добавления описания группы в таблицу данных, на вход принимает объект для хранения описания таблицы и параметры добавляемой группы\
|
||||
@ -2000,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"
|
||||
|
||||
@ -2053,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` в диалоге редактора задачи\
|
||||
@ -2963,61 +2862,6 @@ export { Cyclogram };
|
||||
|
||||
Полные актуальные исходные коды примеров можно увидеть в "db/PKG_P8PANELS_SAMPLES.pck" и "app/panels/samples/cyclogram.js" данного репозитория соответственно.
|
||||
|
||||
##### Индикатор "P8PIndicator"
|
||||
|
||||
Компонент предназначен для отображения данных в виде индикатора. Поддерживается:
|
||||
|
||||
- Цветовая индикация предопределёнными цветами в зависимости от состояния (не определено, позитивное, негативное, пограничное)
|
||||
- Цветовая индикация пользовательскими цветами
|
||||
- Обработка нажатий
|
||||
- Отображение иконки
|
||||
- Упрвление внешним видом (парение, рамка)
|
||||
- Интерактивные подсказки
|
||||
|
||||

|
||||
|
||||
**Подключение**
|
||||
|
||||
Клиентская часть индикатора реализована в компоненте `P8PIndicator`, объявленном в "app/components/p8p_indicator". Для использования компонента на панели его необходимо импортировать:
|
||||
|
||||
```
|
||||
import { P8PIndicator } from "../../components/p8p_indicator";
|
||||
|
||||
const MyPanel = () => {
|
||||
return (
|
||||
<div>
|
||||
<P8PIndicator .../>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Свойства**
|
||||
|
||||
`caption` - обязательный, строка, подпись индикатора\
|
||||
`value` - обязательный, строка, значение индикатора\
|
||||
`icon` - необязательный, строка, код иконки индикатора из символов шрифта [Google Material Icons](https://fonts.google.com/icons?icon.set=Material+Icons) (по умолчанию - не указана, если указана - отображается в левой части области индикатора)\
|
||||
`state` - необязательный, строка, состояние индикатора, принимает значения `UNDEFINED|OK|ERR|WARN` (по умолчанию - `UNDEFINED`, см. константу `P8P_INDICATOR_STATE` в исходном коде компонента), определяет цвет заливки индикатора, если не указаны пользовательские цвета (см. ниже свойства `backgroundColor`и`color`)\
|
||||
`square` - необязательный, логический, определяет необходимость скругления углов области индикатора (по умолчанию - `false`)\
|
||||
`elevation` - необязательный, число, высота парения индикатора (по умолчанию - 3, используется только при `variant = 'elevation'`)\
|
||||
`variant` - необязательный, строка, вариант исполнения, принимает значения `elevation|outlined` (по умолчанию - `elevation`, см. константу `P8P_INDICATOR_VARIANT` в исходном коде компонента), определяет внешний вид индикатора - парящая область или область с рамкой\
|
||||
`hint` - необязательный, строка, текст подсказки для индикатора (если указан - слева от значения индикатора формируется кнопка открытия диалога с текстом подсказки, поддерживается HTML-форматирование)\
|
||||
`onClick` - необязательный, функция, будет вызвана при нажатии пользователем на индикатор (если указана - индикатор формируется в виде кнопки), сигнатура функции `f()`, результат функции не интерпретируется\
|
||||
`backgroundColor` - необязательный, строка, HTML-код пользовательского цвета фона, если указан - будет использован (вне зависимости от `state`) для заливки области индикатора (по умолчанию - не указан) \
|
||||
`color` - необязательный, строка, HTML-код пользовательского цвета шрифта, если указан - будет использован (вне зависимости от `state`) для значения, подписи и иконки индикатора (по умолчанию - не указан)
|
||||
|
||||
**API на сервере БД**
|
||||
|
||||
Компонент `P8PIndicator` требует от разработчика передачи данных в определённом формате. С целью снижения трудозатрат на приведение собранных хранимым объектом данных Системы к форматам, потребляемым `P8PIndicator`, реализован специальный API на стороне сервера БД.
|
||||
|
||||
Для индикатора это (см. детальные описания программных интерфейсов в пакете `PKG_P8PANELS_VISUAL`):
|
||||
`PKG_P8PANELS_VISUAL.TINDICATOR_MAKE` - функция, инициализация индикатора, возвращает объект для хранения его описания\
|
||||
`PKG_P8PANELS_VISUAL.TINDICATOR_TO_XML` - функция, производит сериализацию объекта, описывающего индикатор, в специальный XML-формат, корректно интерпретируемый клиентским компонентом `P8PIndicator` при передаче в WEB-приложение
|
||||
|
||||
**Пример**
|
||||
|
||||
Полный актуальный исходный код примера можно увидеть в "app/panels/samples/indicator.js" данного репозитория.
|
||||
|
||||
### Ограничения дизайна пользовательского интерфейса
|
||||
|
||||
Фреймворк позволяет реализовать любые пользовательские интерфейсы, вёрстка которых не противоречит возможностям современного HTML. Тем не менее, при разработке пользовательских интерфейсов панелей важно придерживаться предложенных ниже правил. Это позволит создавать их в едином ключе и упростит работу конечного пользователя при их освоении.
|
||||
|
||||
@ -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: {
|
||||
|
||||
38
app.text.js
38
app.text.js
@ -12,21 +12,13 @@ export const TITLES = {
|
||||
INFO: "Информация", //Информационный блок
|
||||
WARN: "Предупреждение", //Блок предупреждения
|
||||
ERR: "Ошибка", //Информация об ошибке
|
||||
DEFAULT_PANELS_GROUP: "Без привязки к группе", //Заголовок группы панелей по умолчанию
|
||||
DATA_SOURCE_CONFIG: "Настройка источника данных", //Заголовок для настройки источника данных
|
||||
INSERT: "Добавление", //Заголовок для диалогов/форм добавления
|
||||
UPDATE: "Исправление", //Заголовок для диалогов/форм исправления
|
||||
CONFIG: "Настройка" //Заголовок для диалога настройки
|
||||
DEFAULT_PANELS_GROUP: "Без привязки к группе" //Заголовок группы панелей по умолчанию
|
||||
};
|
||||
|
||||
//Текст
|
||||
export const TEXTS = {
|
||||
LOADING: "Ожидайте...", //Ожидание завершения процесса
|
||||
NO_DATA_FOUND: "Данных не найдено", //Отсутствие данных
|
||||
NO_DATA_FOUND_SHORT: "Н.Д.", //Отсутствие данных (кратко)
|
||||
NO_SETTINGS: "Настройки не определены", //Отстутсвие настроек
|
||||
UNKNOWN_SOURCE_TYPE: "Неизвестный тип источника", //Отсуствие типа источника
|
||||
UNNAMED_SOURCE: "Источник без наименования" //Отсутствие наименования источника
|
||||
NO_DATA_FOUND: "Данных не найдено" //Отсутствие данных
|
||||
};
|
||||
|
||||
//Текст кнопок
|
||||
@ -37,19 +29,11 @@ export const BUTTONS = {
|
||||
OK: "ОК", //Ок
|
||||
CANCEL: "Отмена", //Отмена
|
||||
CLOSE: "Закрыть", //Сокрытие
|
||||
DETAIL: "Подробнее", //Отображение подробностей/детализации
|
||||
HIDE: "Скрыть", //Скрытие информации
|
||||
CLEAR: "Очистить", //Очистка
|
||||
ORDER_ASC: "По возрастанию", //Сортировка по возрастанию
|
||||
ORDER_DESC: "По убыванию", //Сортировка по убыванию
|
||||
FILTER: "Фильтр", //Фильтрация
|
||||
MORE: "Ещё", //Догрузка данных
|
||||
APPLY: "Применить", //Сохранение без закрытия интерфейса ввода
|
||||
SAVE: "Сохранить", //Сохранение
|
||||
CONFIG: "Настроить", //Настройка
|
||||
INSERT: "Добавить", //Добавление
|
||||
UPDATE: "Исправить", //Исправление
|
||||
DELETE: "Удалить" //Удаление
|
||||
MORE: "Ещё" //Догрузка данных
|
||||
};
|
||||
|
||||
//Метки атрибутов, сопроводительные надписи
|
||||
@ -62,9 +46,7 @@ export const CAPTIONS = {
|
||||
START: "Начало",
|
||||
END: "Окончание",
|
||||
PROGRESS: "Прогресс",
|
||||
LEGEND: "Легенда",
|
||||
USER_PROC: "Пользовательская процедура",
|
||||
QUERY: "Запрос"
|
||||
LEGEND: "Легенда"
|
||||
};
|
||||
|
||||
//Типовые сообщения об ошибках
|
||||
@ -72,20 +54,10 @@ export const ERRORS = {
|
||||
UNDER_CONSTRUCTION: "Панель в разработке",
|
||||
P8O_API_UNAVAILABLE: '"ПАРУС 8 Онлайн" недоступен',
|
||||
P8O_API_UNSUPPORTED: 'Функция "ПАРУС 8 Онлайн" не поддерживается',
|
||||
DEFAULT: "Неожиданная ошибка",
|
||||
DATA_SOURCE_NO_REQ_ARGS: "Не заданы обязательные параметры источника данных"
|
||||
DEFAULT: "Неожиданная ошибка"
|
||||
};
|
||||
|
||||
//Типовые сообщения для ошибок HTTP
|
||||
export const ERRORS_HTTP = {
|
||||
404: "Адрес не найден"
|
||||
};
|
||||
|
||||
//Типовые статусы
|
||||
export const STATE = {
|
||||
UNDEFINED: "UNDEFINED",
|
||||
INFO: "INFORMATION",
|
||||
OK: "OK",
|
||||
ERR: "ERR",
|
||||
WARN: "WARN"
|
||||
};
|
||||
|
||||
@ -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}
|
||||
>
|
||||
|
||||
@ -1,121 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Действия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PCAEditor } from "./p8p_component_action/editor"; //Редактор действия
|
||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL, P8P_CA_TYPE } from "./p8p_component_action/common"; //Общие ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
const getActionChipText = (type, params) => {
|
||||
//Определяем от типа
|
||||
switch (type) {
|
||||
//Открыть раздел
|
||||
case P8P_CA_TYPE.openUnit.code:
|
||||
return params.unitName;
|
||||
//Открыть панель
|
||||
case P8P_CA_TYPE.openPanel.code:
|
||||
return params.panelValue.value;
|
||||
//Установить переменную
|
||||
case P8P_CA_TYPE.setVariable.code:
|
||||
return params.reduce((prev, cur) => [...prev, cur.variableSource], []).join(", ");
|
||||
//Для всех остальных
|
||||
default:
|
||||
return "Не определен";
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Действия
|
||||
const P8PActions = ({ actions = P8P_CAS_INITIAL, valueProviders = {}, areas = [], valueTypes = [], onChange = null } = {}) => {
|
||||
//Собственное состояние - редактор действий
|
||||
const [actionEditor, setActionEditor] = useState({ display: false, index: null });
|
||||
|
||||
//При изменении действий
|
||||
const handleActionsChange = actions => onChange && onChange(actions);
|
||||
|
||||
//При добавлении действия
|
||||
const handleActionAdd = () => setActionEditor({ display: true, index: null });
|
||||
|
||||
//При нажатии на действие
|
||||
const handleActionClick = i => setActionEditor({ display: true, index: i });
|
||||
|
||||
//При удалении действия
|
||||
const handleActionDelete = i => {
|
||||
const newActions = [...actions];
|
||||
newActions.splice(i, 1);
|
||||
handleActionsChange(newActions);
|
||||
};
|
||||
|
||||
//При отмене сохранения изменений действия
|
||||
const handleActionCancel = () => setActionEditor({ display: false, index: null });
|
||||
|
||||
//При сохранении изменений действия
|
||||
const handleActionSave = action => {
|
||||
const newActions = [...actions];
|
||||
actionEditor.index == null ? newActions.push({ ...action }) : (newActions[actionEditor.index] = { ...action });
|
||||
handleActionsChange(newActions);
|
||||
setActionEditor({ display: false, index: null });
|
||||
};
|
||||
|
||||
//Определяем структуру действий для отображения
|
||||
const actionChips = actions.map(item => {
|
||||
//Собираем текст действия
|
||||
let text = getActionChipText(item.type, item.params);
|
||||
//Формируем структуру для отображения карточки действия
|
||||
return { text: text, title: text, icon: P8P_CA_TYPE[item.type]?.icon, iconTitle: P8P_CA_TYPE[item.type]?.name };
|
||||
});
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{actionEditor.display && (
|
||||
<P8PCAEditor
|
||||
areas={areas}
|
||||
valueTypes={valueTypes}
|
||||
action={actionEditor.index !== null ? { ...actions[actionEditor.index] } : null}
|
||||
onCancel={handleActionCancel}
|
||||
onOk={handleActionSave}
|
||||
valueProviders={valueProviders}
|
||||
/>
|
||||
)}
|
||||
<P8PChipList items={actionChips} onClick={handleActionClick} onDelete={handleActionDelete} />
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handleActionAdd}>
|
||||
Добавить действие
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - действия
|
||||
P8PActions.propTypes = {
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
valueProviders: PropTypes.object,
|
||||
areas: PropTypes.array,
|
||||
valueTypes: PropTypes.array,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PActions };
|
||||
@ -1,88 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Список элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Chip, Typography, Stack, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||
import { STYLES as COMMON_STYLES } from "./p8p_editors_common"; //Общие ресурсы редакторов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CHIP_ITEM: { ...COMMON_STYLES.CHIP(true, false) },
|
||||
STACK_ITEM: { alignItems: "center" },
|
||||
TYPOGRAPHY_INFO: { overflow: "hidden" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Список элементов
|
||||
const P8PChipList = ({ items, labelRender, onClick, onDelete }) => {
|
||||
//При нажатии на элемент
|
||||
const handleClick = index => onClick && onClick(index);
|
||||
|
||||
//При удалении элемента
|
||||
const handleDelete = index => onDelete && onDelete(index);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{Array.isArray(items) &&
|
||||
items.length > 0 &&
|
||||
items.map((item, i) => (
|
||||
<Chip
|
||||
key={i}
|
||||
label={
|
||||
labelRender ? (
|
||||
labelRender(item)
|
||||
) : (
|
||||
<Stack direction="row" spacing={1} sx={STYLES.STACK_ITEM}>
|
||||
{item.icon ? (
|
||||
<>
|
||||
<Icon sx={{ color: "grey" }} title={item?.iconTitle}>
|
||||
{item.icon}
|
||||
</Icon>
|
||||
<Typography variant="body2">-</Typography>
|
||||
</>
|
||||
) : null}
|
||||
<Typography variant="body2" title={item?.title} sx={STYLES.TYPOGRAPHY_INFO}>
|
||||
{item.text}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
variant={"outlined"}
|
||||
onClick={onClick && !item.disableClick ? () => handleClick(i) : null}
|
||||
onDelete={onDelete && !item.disableDelete ? () => handleDelete(i) : null}
|
||||
deleteIcon={item.deleteIcon ? <Icon>{item.deleteIcon}</Icon> : null}
|
||||
sx={STYLES.CHIP_ITEM}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - список элементов
|
||||
P8PChipList.propTypes = {
|
||||
items: PropTypes.array,
|
||||
labelRender: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PChipList };
|
||||
@ -1,148 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Общие ресурсы компонента "Редактор действия"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Значения типа по умолчанию
|
||||
const P8P_CA_DEF_TYPE_VALUE = {
|
||||
TEXT_VALUE: "Значение",
|
||||
VARIABLE: "Переменная"
|
||||
};
|
||||
|
||||
//Типы значений по умолчанию
|
||||
const P8P_CA_DEF_VALUE_TYPES = [...Object.values(P8P_CA_DEF_TYPE_VALUE)];
|
||||
|
||||
//Типы действия
|
||||
const P8P_CA_TYPE = {
|
||||
openUnit: { code: "openUnit", name: "Открыть раздел", icon: "article" },
|
||||
openPanel: { code: "openPanel", name: "Открыть панель", icon: "storage" },
|
||||
setVariable: { code: "setVariable", name: "Установить переменную", icon: "mediation" }
|
||||
};
|
||||
|
||||
//Список полей связываемых с проводником значений
|
||||
const P8P_CA_SOURCE_FIELDS = ["variableSource"];
|
||||
|
||||
//Список полей хранящих объект типа {"Тип", "Значение"}, где может храниться связь с проводником значений
|
||||
const P8P_CA_OBJECT_SOURCE_FIELDS = ["resultValue", "tabValue", "panelValue"];
|
||||
|
||||
//Структура значения с типом
|
||||
const P8P_CA_OBJECT_VALUE_SHAPE = PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
value: PropTypes.string
|
||||
});
|
||||
|
||||
//Структура входных параметров метода вызова
|
||||
const P8P_CA_INPUT_PARAM_SHAPE = PropTypes.shape({
|
||||
parameter: PropTypes.string,
|
||||
inputParameter: PropTypes.string,
|
||||
resultValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||
});
|
||||
|
||||
//Структура параметров действия "Открыть раздел"
|
||||
const P8P_CA_OPEN_UNIT_PARAMS_SHAPE = PropTypes.shape({
|
||||
unitCode: PropTypes.string,
|
||||
unitName: PropTypes.string,
|
||||
showMethod: PropTypes.string,
|
||||
showMethodName: PropTypes.string,
|
||||
inputParams: PropTypes.arrayOf(P8P_CA_INPUT_PARAM_SHAPE),
|
||||
modal: PropTypes.bool
|
||||
});
|
||||
|
||||
//Структура параметров действия "Открыть панель"
|
||||
const P8P_CA_OPEN_PANEL_PARAMS_SHAPE = PropTypes.shape({
|
||||
tabValue: P8P_CA_OBJECT_VALUE_SHAPE,
|
||||
panelValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||
});
|
||||
|
||||
//Структура связи переменной действия "Установить переменную"
|
||||
const P8P_CA_VARIABLE_LINK_SHAPE = PropTypes.shape({
|
||||
variableSource: PropTypes.string,
|
||||
resultValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||
});
|
||||
|
||||
//Структура действия
|
||||
const P8P_CA_SHAPE = PropTypes.shape({
|
||||
type: PropTypes.oneOf(Object.keys(P8P_CA_TYPE)).isRequired,
|
||||
area: PropTypes.string,
|
||||
element: PropTypes.string,
|
||||
params: PropTypes.oneOfType([P8P_CA_OPEN_UNIT_PARAMS_SHAPE, P8P_CA_OPEN_PANEL_PARAMS_SHAPE, PropTypes.arrayOf(P8P_CA_VARIABLE_LINK_SHAPE)])
|
||||
});
|
||||
|
||||
//Начальное состояние объекта значения с типом
|
||||
const P8P_CA_OBJECT_VALUE_INITIAL = {
|
||||
type: P8P_CA_DEF_TYPE_VALUE.TEXT_VALUE,
|
||||
value: ""
|
||||
};
|
||||
|
||||
//Начальное состояние входного параметра метода вызова
|
||||
const P8P_CA_INPUT_PARAM_INITIAL = {
|
||||
parameter: "",
|
||||
inputParameter: "",
|
||||
resultValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||
};
|
||||
|
||||
//Начальное состояние параметров действия "Открыть раздел"
|
||||
const P8P_CA_OPEN_UNIT_INITIAL = {
|
||||
unitCode: "",
|
||||
unitName: "",
|
||||
showMethod: "",
|
||||
showMethodName: "",
|
||||
inputParams: [],
|
||||
modal: true
|
||||
};
|
||||
|
||||
//Начальное состояние параметров действия "Открыть панель"
|
||||
const P8P_CA_OPEN_PANEL_INITIAL = {
|
||||
tabValue: { ...P8P_CA_OBJECT_VALUE_INITIAL },
|
||||
panelValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||
};
|
||||
|
||||
//Начальное состояние параметра связи действия "Установить переменную"
|
||||
const P8P_CA_VARIABLE_LINK_INITIAL = {
|
||||
variableSource: "",
|
||||
resultValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||
};
|
||||
|
||||
//Начальное состояние действия
|
||||
const P8P_CA_INITIAL = {
|
||||
type: P8P_CA_TYPE.openUnit.code,
|
||||
area: "",
|
||||
element: "",
|
||||
params: { ...P8P_CA_OPEN_UNIT_INITIAL }
|
||||
};
|
||||
|
||||
//Начальное состояние действий
|
||||
const P8P_CAS_INITIAL = [];
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export {
|
||||
P8P_CA_DEF_TYPE_VALUE,
|
||||
P8P_CA_DEF_VALUE_TYPES,
|
||||
P8P_CA_TYPE,
|
||||
P8P_CA_SOURCE_FIELDS,
|
||||
P8P_CA_OBJECT_SOURCE_FIELDS,
|
||||
P8P_CA_OBJECT_VALUE_SHAPE,
|
||||
P8P_CA_OPEN_UNIT_PARAMS_SHAPE,
|
||||
P8P_CA_OPEN_PANEL_PARAMS_SHAPE,
|
||||
P8P_CA_VARIABLE_LINK_SHAPE,
|
||||
P8P_CA_SHAPE,
|
||||
P8P_CA_INPUT_PARAM_INITIAL,
|
||||
P8P_CA_OPEN_UNIT_INITIAL,
|
||||
P8P_CA_OPEN_PANEL_INITIAL,
|
||||
P8P_CA_VARIABLE_LINK_INITIAL,
|
||||
P8P_CA_INITIAL,
|
||||
P8P_CAS_INITIAL
|
||||
};
|
||||
@ -1,159 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор действия "Открыть панель"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8P_CA_DEF_TYPE_VALUE, P8P_CA_OPEN_PANEL_PARAMS_SHAPE } from "./common"; //Общие ресурсы действий
|
||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||
import { PanelsManager } from "../../../panels/panels_editor/components/panels_manager/panels_manager"; //Менеджер панелей
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER_UNIT: { display: "flex", flexDirection: "column", gap: "10px" },
|
||||
BOX_SETTINGS: { display: "flex", flexDirection: "row", gap: "15px" }
|
||||
};
|
||||
|
||||
//Ключ значений о странице
|
||||
const SPROP_TAB = "tabValue";
|
||||
//Ключа значений о панели
|
||||
const SPROP_PANEL = "panelValue";
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор действия "Открыть панель"
|
||||
const P8PCAPanelOpenOptions = ({ panel, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||
//Собственное состояние - Отображение менеджера панелей
|
||||
const [openPanelsManager, setOpenPanelsManager] = useState(false);
|
||||
|
||||
//При открытии менеджера панелей
|
||||
const handlePanelsManagerOpen = () => setOpenPanelsManager(true);
|
||||
|
||||
//При закрытии менеджера панелей
|
||||
const handlePanelsManagerClose = () => setOpenPanelsManager(false);
|
||||
|
||||
//При выборе панели в менеджере панелей
|
||||
const handlePanelSelect = selectedPanel => {
|
||||
//Устанавливаем мнемокод выбранной панели
|
||||
onStateChange({ ...panel, panelValue: { ...panel.panelValue, value: selectedPanel.code } });
|
||||
//Закрываем менеджер панелей
|
||||
handlePanelsManagerClose();
|
||||
};
|
||||
|
||||
//При изменении типа значения
|
||||
const handleValueTypeChange = (value, keyProp) =>
|
||||
onStateChange({
|
||||
[keyProp]: { type: value, value: "" }
|
||||
});
|
||||
|
||||
//При изменении значения
|
||||
const handleValueChange = (value, keyProp) =>
|
||||
onStateChange({
|
||||
[keyProp]: { ...panel[keyProp], value: value }
|
||||
});
|
||||
|
||||
//При очистке значения
|
||||
const handleClearValueClick = keyProp =>
|
||||
onStateChange({
|
||||
[keyProp]: { ...panel[keyProp], value: "" }
|
||||
});
|
||||
|
||||
//При изменении переменной
|
||||
const handleValueSourceChange = (valueSource, keyProp) =>
|
||||
onStateChange({
|
||||
[keyProp]: { ...panel[keyProp], value: valueSource }
|
||||
});
|
||||
|
||||
//При нажатии на выбор переменной
|
||||
const handleValueSourceSelect = (e, keyProp) =>
|
||||
isValues
|
||||
? onValueSourceMenuClick &&
|
||||
onValueSourceMenuClick(e, valueSource => {
|
||||
handleValueSourceChange(valueSource, keyProp);
|
||||
})
|
||||
: null;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER_UNIT}>
|
||||
{openPanelsManager && (
|
||||
<PanelsManager
|
||||
current={panel.panelValue.value}
|
||||
isEditable={false}
|
||||
onPanelSelect={handlePanelSelect}
|
||||
onCancel={handlePanelsManagerClose}
|
||||
/>
|
||||
)}
|
||||
<Box sx={STYLES.BOX_SETTINGS}>
|
||||
<P8PCAFieldWithType
|
||||
valueTypes={valueTypes}
|
||||
name={SPROP_TAB}
|
||||
valueLabel={"Наименование вкладки"}
|
||||
groupLabel={"Настройки вкладки"}
|
||||
item={panel.tabValue}
|
||||
isValueReadOnly={panel.tabValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||
onTypeChange={e => handleValueTypeChange(e.target.value, SPROP_TAB)}
|
||||
onValueChange={e => handleValueChange(e.target.value, SPROP_TAB)}
|
||||
endAdornments={[
|
||||
{ icon: "clear", onClick: () => handleClearValueClick(SPROP_TAB), isDisabled: false },
|
||||
{
|
||||
icon: "settings_ethernet",
|
||||
onClick: e => handleValueSourceSelect(e, SPROP_TAB),
|
||||
isDisabled: panel.tabValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<P8PCAFieldWithType
|
||||
valueTypes={valueTypes}
|
||||
name={SPROP_PANEL}
|
||||
valueLabel={"Панель"}
|
||||
groupLabel={"Настройки панели"}
|
||||
item={panel.panelValue}
|
||||
isValueReadOnly={panel.panelValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||
onTypeChange={e => handleValueTypeChange(e.target.value, SPROP_PANEL)}
|
||||
onValueChange={e => handleValueChange(e.target.value, SPROP_PANEL)}
|
||||
endAdornments={[
|
||||
{ icon: "clear", onClick: () => handleClearValueClick(SPROP_PANEL), isDisabled: false },
|
||||
{
|
||||
icon: "list",
|
||||
onClick: handlePanelsManagerOpen,
|
||||
isDisabled: panel.panelValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
},
|
||||
{
|
||||
icon: "settings_ethernet",
|
||||
onClick: e => handleValueSourceSelect(e, SPROP_PANEL),
|
||||
isDisabled: panel.panelValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - редактор действия "Открыть панель"
|
||||
P8PCAPanelOpenOptions.propTypes = {
|
||||
panel: P8P_CA_OPEN_PANEL_PARAMS_SHAPE,
|
||||
valueTypes: PropTypes.array,
|
||||
isValues: PropTypes.bool.isRequired,
|
||||
onStateChange: PropTypes.func.isRequired,
|
||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAPanelOpenOptions };
|
||||
@ -1,285 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор действия "Открыть раздел"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, Box, InputAdornment, IconButton, Icon, FormControlLabel, Checkbox } from "@mui/material"; //Интерфейсные элементы
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { P8P_CA_OPEN_UNIT_PARAMS_SHAPE, P8P_CA_INPUT_PARAM_INITIAL, P8P_CA_DEF_TYPE_VALUE } from "./common"; //Общие ресурсы действий
|
||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||
import { P8PCATablePropValues } from "./table_prop_values"; //Таблица значений свойств действия
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER_UNIT: { display: "flex", flexDirection: "column", gap: "10px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор действия "Открыть раздел"
|
||||
const P8PCAUnitOpenOptions = ({ unit, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//При изменении параметра открытия
|
||||
const handleModalChange = () => onStateChange({ modal: !unit.modal });
|
||||
|
||||
//При нажатии на очистку раздела
|
||||
const handleClearUnitClick = () =>
|
||||
onStateChange({
|
||||
unitCode: "",
|
||||
unitName: "",
|
||||
showMethod: ""
|
||||
});
|
||||
|
||||
//При нажатии на выбор раздела
|
||||
const handleSelectUnitClick = () => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "Units",
|
||||
showMethod: "methods",
|
||||
inputParameters: [
|
||||
{ name: "pos_unit_name", value: unit.unitName },
|
||||
{ name: "pos_method_name", value: unit.showMethodName }
|
||||
],
|
||||
callBack: res =>
|
||||
res.success &&
|
||||
onStateChange({
|
||||
unitCode: res.outParameters.unit_code,
|
||||
unitName: res.outParameters.unit_name,
|
||||
showMethod: res.outParameters.method_code,
|
||||
showMethodName: res.outParameters.method_name
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
//При добавлении связи входного параметра
|
||||
const handleInputParameAdd = () => {
|
||||
onStateChange({
|
||||
inputParams: [...unit.inputParams, { ...P8P_CA_INPUT_PARAM_INITIAL }]
|
||||
});
|
||||
};
|
||||
|
||||
//При удалении связи входного параметра
|
||||
const handleInputParamDelete = index => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Удаляем по индексу
|
||||
newInputParams.splice(index, 1);
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При очистке входного параметра
|
||||
const handleClearInputParameterClick = index => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index]["parameter"] = "";
|
||||
newInputParams[index]["inputParameter"] = "";
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При нажатии на выбор параметра метода вызова
|
||||
const handleSelectUnitParameterClick = index => {
|
||||
unit.unitCode &&
|
||||
unit.showMethod &&
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "UnitParams",
|
||||
showMethod: "main",
|
||||
inputParameters: [
|
||||
{ name: "in_UNITCODE", value: unit.unitCode },
|
||||
{ name: "in_PARENT_METHOD_CODE", value: unit.showMethod },
|
||||
{ name: "in_PARAMNAME", value: unit.inputParams[index].parameter }
|
||||
],
|
||||
callBack: res => {
|
||||
if (res.success) {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index]["parameter"] = res.outParameters.out_PARAMNAME;
|
||||
newInputParams[index]["inputParameter"] = res.outParameters.out_IN_CODE;
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//При изменении типа значения входного параметра
|
||||
const handleInputParamValueTypeChange = (value, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, type: value, value: "" };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При очистке значения параметра
|
||||
const handleClearInputParamValueClick = index => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: "" };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При изменении значения входного параметра
|
||||
const handleInputParamValueChange = (value, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: value };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При связывании значения входного параметра с переменной
|
||||
const handleInputParamValueSourceChange = (valueSource, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newInputParams = [...unit.inputParams];
|
||||
//Очищаем значение по индексу
|
||||
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: valueSource };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange({ inputParams: [...newInputParams] });
|
||||
};
|
||||
|
||||
//При нажатии на выбор переменной
|
||||
const handleValueSourceSelect = (e, index) =>
|
||||
isValues
|
||||
? onValueSourceMenuClick &&
|
||||
onValueSourceMenuClick(e, valueSource => {
|
||||
handleInputParamValueSourceChange(valueSource, index);
|
||||
})
|
||||
: null;
|
||||
|
||||
//Рендер отображения колонки свойства
|
||||
const handlePropertyCellRender = (item, index) => {
|
||||
//Значение первой колонки
|
||||
return (
|
||||
<TextField
|
||||
id={`parameter_${index}`}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={item.parameter}
|
||||
name={"parameter"}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleClearInputParameterClick(index)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={() => handleSelectUnitParameterClick(index)} disabled={!unit.showMethodName}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Рендер отображения колонки значения
|
||||
const handleValueCellRender = (item, index) => {
|
||||
//Формирование представления колонки значения
|
||||
return (
|
||||
<P8PCAFieldWithType
|
||||
valueTypes={valueTypes}
|
||||
name={`inputValue_${index}`}
|
||||
item={item.resultValue}
|
||||
isValueReadOnly={item.resultValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||
onTypeChange={e => handleInputParamValueTypeChange(e.target.value, index)}
|
||||
onValueChange={e => handleInputParamValueChange(e.target.value, index)}
|
||||
endAdornments={[
|
||||
{ icon: "clear", onClick: () => handleClearInputParamValueClick(index), isDisabled: false },
|
||||
{
|
||||
icon: "settings_ethernet",
|
||||
onClick: e => handleValueSourceSelect(e, index),
|
||||
isDisabled: item.resultValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER_UNIT}>
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={unit.unitName}
|
||||
label={"Раздел"}
|
||||
InputLabelProps={{ shrink: unit.unitName ? true : false }}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleClearUnitClick}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleSelectUnitClick}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
required={true}
|
||||
/>
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={unit.showMethodName}
|
||||
label={"Метод вызова"}
|
||||
InputLabelProps={{ shrink: unit.showMethodName ? true : false }}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
required={true}
|
||||
/>
|
||||
<P8PCATablePropValues
|
||||
name={"Входные параметры"}
|
||||
items={unit.inputParams}
|
||||
propertyCellName={"Входной параметр"}
|
||||
valueCellName={"Значение"}
|
||||
onPropertyCellRender={(item, index) => handlePropertyCellRender(item, index)}
|
||||
onValueCellRender={(item, index) => handleValueCellRender(item, index)}
|
||||
onAddRow={handleInputParameAdd}
|
||||
onRowDelete={index => handleInputParamDelete(index)}
|
||||
/>
|
||||
<FormControlLabel control={<Checkbox checked={unit.modal} onChange={handleModalChange} />} label="Открывать модально" />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - редактор действия "Открыть раздел"
|
||||
P8PCAUnitOpenOptions.propTypes = {
|
||||
unit: P8P_CA_OPEN_UNIT_PARAMS_SHAPE,
|
||||
valueTypes: PropTypes.array,
|
||||
isValues: PropTypes.bool.isRequired,
|
||||
onStateChange: PropTypes.func.isRequired,
|
||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAUnitOpenOptions };
|
||||
@ -1,202 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор действия "Установить переменную"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8P_CA_VARIABLE_LINK_INITIAL, P8P_CA_VARIABLE_LINK_SHAPE, P8P_CA_DEF_TYPE_VALUE } from "./common"; //Общие ресурсы действий
|
||||
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||
import { P8PCATablePropValues } from "./table_prop_values"; //Таблица значений свойств действия
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор действия "Установить переменную"
|
||||
const P8PCAVarSetOptions = ({ variables, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||
//При добавлении связи переменной
|
||||
const handleVariableAdd = () => {
|
||||
onStateChange([...variables, { ...P8P_CA_VARIABLE_LINK_INITIAL }]);
|
||||
};
|
||||
|
||||
//При удалении связи переменной
|
||||
const handleVariableDelete = index => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Удаляем по индексу
|
||||
newVariables.splice(index, 1);
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При очистке значения переменной
|
||||
const handleClearVariableClick = index => {
|
||||
//Копируем текущее состояние связываемых переменных
|
||||
let newParams = [...variables];
|
||||
//Очищаем значения у элемента по индексу
|
||||
newParams[index].variableSource = "";
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newParams]);
|
||||
};
|
||||
|
||||
//При связывании переменной
|
||||
const handleVariablesSourceChange = (valueSource, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значение по индексу
|
||||
newVariables[index].variableSource = valueSource;
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При изменении типа значения
|
||||
const handleResultValueTypeChange = (value, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значение по индексу
|
||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, type: value, value: "" };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При очистке значения
|
||||
const handleClearResultValueClick = index => {
|
||||
//Копируем текущее состояние связываемых переменных
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значения у элемента по индексу
|
||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: "" };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При ручном изменении значения
|
||||
const handleResultValueChange = (value, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значение по индексу
|
||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: value };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При связывании значения
|
||||
const handleResultValueSourceChange = (valueSource, index) => {
|
||||
//Копируем связываемые переменные
|
||||
let newVariables = [...variables];
|
||||
//Очищаем значение по индексу
|
||||
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: valueSource };
|
||||
//Устанавливаем новый массив
|
||||
onStateChange([...newVariables]);
|
||||
};
|
||||
|
||||
//При нажатии на выбор переменной (значение)
|
||||
const handleValueSourceSelect = (e, index) =>
|
||||
isValues
|
||||
? onValueSourceMenuClick &&
|
||||
onValueSourceMenuClick(e, valueSource => {
|
||||
handleResultValueSourceChange(valueSource, index);
|
||||
})
|
||||
: null;
|
||||
|
||||
//При нажатии на выбор переменной (переменная)
|
||||
const handleVariableSourceSelect = (e, index) =>
|
||||
isValues
|
||||
? onValueSourceMenuClick &&
|
||||
onValueSourceMenuClick(e, valueSource => {
|
||||
handleVariablesSourceChange(valueSource, index);
|
||||
})
|
||||
: null;
|
||||
|
||||
//Рендер отображения колонки свойства
|
||||
const handlePropertyCellRender = (item, index) => {
|
||||
//Формирование представления колонки свойства
|
||||
return (
|
||||
<TextField
|
||||
id={`variableSource_${index}`}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={item.variableSource}
|
||||
name={"variableSource"}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleClearVariableClick(index)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
{isValues && (
|
||||
<IconButton onClick={e => handleVariableSourceSelect(e, index)}>
|
||||
<Icon>settings_ethernet</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Рендер отображения колонки значения
|
||||
const handleValueCellRender = (item, index) => {
|
||||
//Формирование представления колонки значения
|
||||
return (
|
||||
<P8PCAFieldWithType
|
||||
valueTypes={valueTypes}
|
||||
name={`resultValue_${index}`}
|
||||
item={item.resultValue}
|
||||
isValueReadOnly={item.resultValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||
onTypeChange={e => handleResultValueTypeChange(e.target.value, index)}
|
||||
onValueChange={e => handleResultValueChange(e.target.value, index)}
|
||||
endAdornments={[
|
||||
{ icon: "clear", onClick: () => handleClearResultValueClick(index), isDisabled: false },
|
||||
{
|
||||
icon: "settings_ethernet",
|
||||
onClick: e => handleValueSourceSelect(e, index),
|
||||
isDisabled: item.resultValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PCATablePropValues
|
||||
name={"Список параметров"}
|
||||
items={variables}
|
||||
propertyCellName={"Переменная"}
|
||||
valueCellName={"Значение"}
|
||||
onPropertyCellRender={(item, index) => handlePropertyCellRender(item, index)}
|
||||
onValueCellRender={(item, index) => handleValueCellRender(item, index)}
|
||||
onAddRow={handleVariableAdd}
|
||||
onRowDelete={index => handleVariableDelete(index)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - редактор действия "Установить переменную"
|
||||
P8PCAVarSetOptions.propTypes = {
|
||||
variables: PropTypes.arrayOf(P8P_CA_VARIABLE_LINK_SHAPE),
|
||||
valueTypes: PropTypes.array,
|
||||
isValues: PropTypes.bool.isRequired,
|
||||
onStateChange: PropTypes.func.isRequired,
|
||||
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAVarSetOptions };
|
||||
@ -1,250 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор действия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, Select, Menu, MenuItem, FormControl, InputLabel, Stack, Box } from "@mui/material"; //Интерфейсные элементы
|
||||
import { TITLES } from "../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { P8PConfigDialog } from "../p8p_config_dialog"; //Диалог настройки
|
||||
import { deepCopyObject } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { P8P_CA_DEF_VALUE_TYPES, P8P_CA_TYPE, P8P_CA_SHAPE, P8P_CA_INPUT_PARAM_INITIAL, P8P_CA_OPEN_UNIT_INITIAL, P8P_CA_INITIAL } from "./common"; //Общие ресурсы действий
|
||||
import { P8PCAUnitOpenOptions } from "./config_unit_open"; //Компонент редактора действия "Открыть раздел"
|
||||
import { P8PCAPanelOpenOptions } from "./config_panel_open"; //Компонент редактора действия "Открыть панель"
|
||||
import { P8PCAVarSetOptions } from "./config_var_set"; //Компонент редактора действия "Установить переменную"
|
||||
import { isActionOkDisabled, getActionTypeParams } from "./util"; //Вспомогательные ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex", flexDirection: "column", gap: "10px", minWidth: "300px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор действия
|
||||
const P8PCAEditor = ({ areas = [], valueTypes = [], action = null, onOk = null, onCancel = null, valueProviders = {} }) => {
|
||||
//Собственное состояние - признак инициализиации
|
||||
const [init, setInit] = useState(true);
|
||||
|
||||
//Собственное состояние - параметры действия
|
||||
const [state, setState] = useState({});
|
||||
|
||||
//Собственное состояние - доступные типы значений компонента
|
||||
const [availableValueTypes, setAvailableValueTypes] = useState([]);
|
||||
|
||||
//Собственное состояние - доступные области компонента
|
||||
const [availableAreas, setAvailableAreas] = useState([]);
|
||||
|
||||
//Собственное состояние - доступность поля "Элемент"
|
||||
const [hasElement, setHasElement] = useState(false);
|
||||
|
||||
//Собственное состояние - элемент привязки меню выбора источника
|
||||
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState({
|
||||
target: null,
|
||||
onChange: null
|
||||
});
|
||||
|
||||
//Открытие/сокрытие меню выбора источника
|
||||
const toggleValueProvidersMenu = (target, onChange) =>
|
||||
setValueProvidersMenuAnchorEl(target instanceof Element ? { target, onChange } : { target: null, onChange: null });
|
||||
|
||||
//При отображении меню связывания значения с поставщиком данных
|
||||
const handleValueSourceLinkMenuClick = (e, onChange) => setValueProvidersMenuAnchorEl({ target: e.currentTarget, onChange });
|
||||
|
||||
//При закрытии редактора с сохранением
|
||||
const handleOk = () => onOk && onOk({ ...state });
|
||||
|
||||
//При закрытии редактора с отменой
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//При изменении типа действия
|
||||
const handleTypeChange = e => {
|
||||
//Иницилизируем параметры типа
|
||||
let newParams = getActionTypeParams(e.target.value);
|
||||
//Изменяем тип действия с изменение структуры параметров
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
type: e.target.value,
|
||||
params: Array.isArray(newParams) ? [...newParams] : { ...newParams }
|
||||
}));
|
||||
};
|
||||
|
||||
//При ручном изменении общего параметра действия
|
||||
const handleChange = e => setState(pv => ({ ...pv, [e.target.name]: e.target.value }));
|
||||
|
||||
//При изменении области действия
|
||||
const handleAreaChange = e => {
|
||||
//Устанавливаем значение
|
||||
setState(pv => ({ ...pv, [e.target.name]: e.target.value, name: "" }));
|
||||
//Устанавливаем доступность "Элемент"
|
||||
setHasElement(availableAreas.find(item => item.area === e.target.value).hasElement);
|
||||
};
|
||||
|
||||
//При изменении полей параметров действия
|
||||
const handleParamsChange = params => {
|
||||
setState(pv => ({ ...pv, params: { ...pv.params, ...params } }));
|
||||
};
|
||||
|
||||
//При изменении переменных параметров действия "Установить переменную"
|
||||
const handleVariablesParamsChange = variables => {
|
||||
setState(pv => ({ ...pv, params: [...variables] }));
|
||||
};
|
||||
|
||||
//Список значений
|
||||
const values = Object.keys(valueProviders);
|
||||
|
||||
//Наличие значений
|
||||
const isValues = values && values.length > 0 ? true : false;
|
||||
|
||||
//Меню привязки к поставщикам значений
|
||||
const valueProvidersMenu = isValues && (
|
||||
<Menu anchorEl={valueProvidersMenuAnchorEl.target} open={Boolean(valueProvidersMenuAnchorEl.target)} onClose={toggleValueProvidersMenu}>
|
||||
{values.map((value, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
onClick={() => {
|
||||
//Выполняем выбор параметра
|
||||
valueProvidersMenuAnchorEl.onChange(value);
|
||||
//Закрываем меню выбора переменной
|
||||
toggleValueProvidersMenu();
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
//При инициализации действия
|
||||
useEffect(() => {
|
||||
//Если это иницализация
|
||||
if (init) {
|
||||
//Если это открытие действия - берем его параметры, иначе - инициализируем изначальными
|
||||
const initAction = action
|
||||
? action
|
||||
: {
|
||||
...P8P_CA_INITIAL,
|
||||
area: areas[0].area,
|
||||
params: { ...P8P_CA_OPEN_UNIT_INITIAL, inputParams: [{ ...P8P_CA_INPUT_PARAM_INITIAL }] }
|
||||
};
|
||||
//Устанавливаем параметры действия
|
||||
setState(deepCopyObject(initAction));
|
||||
//Заполняем доступные области
|
||||
setAvailableAreas([...areas]);
|
||||
//Определяем доступность "Элемент"
|
||||
setHasElement(areas.find(item => item.area === initAction.area)?.hasElement || false);
|
||||
//Заполняем доступные типы значений
|
||||
setAvailableValueTypes([...P8P_CA_DEF_VALUE_TYPES, ...valueTypes]);
|
||||
//Сбрасываем признак инициализации
|
||||
setInit(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [init]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{!init ? (
|
||||
<P8PConfigDialog
|
||||
title={`${action ? TITLES.UPDATE : TITLES.INSERT} действия`}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={"xl"}
|
||||
okDisabled={isActionOkDisabled(state)}
|
||||
>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{valueProvidersMenu}
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{availableAreas.length !== 0 ? (
|
||||
<FormControl variant={"standard"} fullWidth>
|
||||
<InputLabel id={"areaLabel-label"}>Область</InputLabel>
|
||||
<Select name={"area"} value={state.area} labelId={"area-label"} label={"Область"} onChange={handleAreaChange}>
|
||||
{availableAreas.map((item, index) => (
|
||||
<MenuItem value={item.area} key={index}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
) : null}
|
||||
{hasElement && (
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.element}
|
||||
label={"Элемент"}
|
||||
name={"element"}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
<FormControl variant={"standard"} fullWidth>
|
||||
<InputLabel id={"type-label"}>Тип</InputLabel>
|
||||
<Select name={"type"} value={state.type} labelId={"type-label"} label={"Тип"} onChange={handleTypeChange}>
|
||||
{Object.keys(P8P_CA_TYPE).map((item, index) => (
|
||||
<MenuItem value={P8P_CA_TYPE[item].code} key={index}>
|
||||
{P8P_CA_TYPE[item].name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{state.type === P8P_CA_TYPE.openUnit.code && (
|
||||
<P8PCAUnitOpenOptions
|
||||
unit={state.params}
|
||||
valueTypes={availableValueTypes}
|
||||
isValues={isValues}
|
||||
onStateChange={handleParamsChange}
|
||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||
/>
|
||||
)}
|
||||
{state.type === P8P_CA_TYPE.openPanel.code && (
|
||||
<P8PCAPanelOpenOptions
|
||||
panel={state.params}
|
||||
valueTypes={availableValueTypes}
|
||||
isValues={isValues}
|
||||
onStateChange={handleParamsChange}
|
||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||
/>
|
||||
)}
|
||||
{state.type === P8P_CA_TYPE.setVariable.code && (
|
||||
<P8PCAVarSetOptions
|
||||
variables={state.params}
|
||||
valueTypes={availableValueTypes}
|
||||
isValues={isValues}
|
||||
onStateChange={handleVariablesParamsChange}
|
||||
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</P8PConfigDialog>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Редактор условия
|
||||
P8PCAEditor.propTypes = {
|
||||
areas: PropTypes.array,
|
||||
valueTypes: PropTypes.array,
|
||||
action: P8P_CA_SHAPE,
|
||||
onOk: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
valueProviders: PropTypes.object
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAEditor };
|
||||
@ -1,105 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Поле с выбором типа значения
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, Select, MenuItem, FormControl, Box, InputAdornment, IconButton, Icon, FormLabel } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8P_CA_OBJECT_VALUE_SHAPE } from "./common"; //Общие ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { display: "flex", flexDirection: "column", gap: "10px", alignItems: "center" },
|
||||
LEGEND_SETTINGS: { paddingTop: "10px", marginBottom: "-5px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Поле с выбором типа значения
|
||||
const P8PCAFieldWithType = ({
|
||||
valueTypes,
|
||||
item,
|
||||
name,
|
||||
valueLabel = null,
|
||||
groupLabel = null,
|
||||
isValueReadOnly = false,
|
||||
onTypeChange,
|
||||
onValueChange = null,
|
||||
endAdornments = []
|
||||
}) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{groupLabel ? (
|
||||
<FormLabel component="legend" sx={STYLES.LEGEND_SETTINGS}>
|
||||
{groupLabel}
|
||||
</FormLabel>
|
||||
) : null}
|
||||
<FormControl variant={"standard"} fullWidth>
|
||||
<Select name={`${name}_type`} value={item.type} labelId={`${name}-type-label`} onChange={onTypeChange} fullWidth>
|
||||
{valueTypes.map((type, index) => (
|
||||
<MenuItem value={type} key={index}>
|
||||
{type}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
id={`${name}_value`}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={item.value}
|
||||
name={`${name}`}
|
||||
label={valueLabel}
|
||||
onChange={e => {
|
||||
onValueChange && onValueChange(e);
|
||||
}}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: isValueReadOnly,
|
||||
endAdornment:
|
||||
endAdornments.length !== 0 ? (
|
||||
<InputAdornment position="end">
|
||||
{endAdornments.map((item, index) => (
|
||||
<IconButton onClick={e => item.onClick(e)} key={index} disabled={item.isDisabled}>
|
||||
<Icon>{item.icon}</Icon>
|
||||
</IconButton>
|
||||
))}
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - поле с выбором типа значения
|
||||
P8PCAFieldWithType.propTypes = {
|
||||
valueTypes: PropTypes.array.isRequired,
|
||||
item: P8P_CA_OBJECT_VALUE_SHAPE.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
valueLabel: PropTypes.string,
|
||||
groupLabel: PropTypes.string,
|
||||
isValueReadOnly: PropTypes.bool,
|
||||
onTypeChange: PropTypes.func.isRequired,
|
||||
onValueChange: PropTypes.func,
|
||||
endAdornments: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCAFieldWithType };
|
||||
@ -1,99 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Таблица значений свойств действия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, IconButton, Icon, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER_VARIABLES: { maxHeight: "240px", overflow: "auto", ...APP_STYLES.SCROLL },
|
||||
BOX_VARIABLES_INFO: { display: "flex", justifyContent: "space-between", alignItems: "center", paddingTop: "10px" },
|
||||
TABLE_HEAD_CELL_PROPERTY: { width: "45%", padding: "8px" },
|
||||
TABLE_HEAD_CELL_VALUE: { width: "45%", padding: "8px" },
|
||||
TABLE_HEAD_CELL_DELETE: { width: "10%", padding: "8px" },
|
||||
TABLE_VARIABLES: { maxWidth: "700px", minWidth: "700px" },
|
||||
TABLE_ROW_VARIABLES: { "&:last-child td, &:last-child th": { border: 0 } },
|
||||
TABLE_CELL_VARIABLES: { padding: "8px 16px" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Таблица значений свойств действия
|
||||
const P8PCATablePropValues = ({ name, items, propertyCellName, valueCellName, onPropertyCellRender, onValueCellRender, onAddRow, onRowDelete }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
<Box sx={STYLES.BOX_VARIABLES_INFO}>
|
||||
<Typography pl={1}>{name}</Typography>
|
||||
<IconButton onClick={onAddRow}>
|
||||
<Icon>add</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<TableContainer component={Paper} sx={STYLES.CONTAINER_VARIABLES}>
|
||||
<Table sx={STYLES.TABLE_VARIABLES} aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_PROPERTY}>
|
||||
{propertyCellName}
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_VALUE}>
|
||||
{valueCellName}
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_DELETE}></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Array.isArray(items) &&
|
||||
items.map((item, i) => (
|
||||
<TableRow key={i} sx={STYLES.TABLE_ROW_VARIABLES}>
|
||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||
{onPropertyCellRender ? onPropertyCellRender(item, i) : null}
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||
{onValueCellRender ? onValueCellRender(item, i) : null}
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||
<IconButton onClick={() => onRowDelete(i)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - таблица значений свойств действия
|
||||
P8PCATablePropValues.propTypes = {
|
||||
name: PropTypes.string,
|
||||
items: PropTypes.array,
|
||||
propertyCellName: PropTypes.string,
|
||||
valueCellName: PropTypes.string,
|
||||
onPropertyCellRender: PropTypes.func,
|
||||
onValueCellRender: PropTypes.func,
|
||||
onAddRow: PropTypes.func,
|
||||
onRowDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCATablePropValues };
|
||||
@ -1,212 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Вспомогательные ресурсы компонента "Редактор действия"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { genUID } from "../../../core/utils"; //Вспомогательные функции
|
||||
import {
|
||||
P8P_CA_DEF_TYPE_VALUE,
|
||||
P8P_CA_TYPE,
|
||||
P8P_CA_SOURCE_FIELDS,
|
||||
P8P_CA_OBJECT_SOURCE_FIELDS,
|
||||
P8P_CA_OPEN_UNIT_INITIAL,
|
||||
P8P_CA_INPUT_PARAM_INITIAL,
|
||||
P8P_CA_OPEN_PANEL_INITIAL,
|
||||
P8P_CA_VARIABLE_LINK_INITIAL
|
||||
} from "./common"; //Общие ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Доступность сохранения настроек действия
|
||||
const isActionOkDisabled = action => {
|
||||
//Если область или тип не указаны
|
||||
if (!action.area || !action.type) return true;
|
||||
//Исходим от типа
|
||||
switch (action.type) {
|
||||
//Открыть раздел
|
||||
case P8P_CA_TYPE.openUnit.code:
|
||||
return !action.params.unitCode || !action.params.showMethod;
|
||||
//Открыть панель
|
||||
case P8P_CA_TYPE.openPanel.code:
|
||||
return !action.params.tabValue.type || !action.params.tabValue.value || !action.params.panelValue.type || !action.params.panelValue.value;
|
||||
//Установить переменную
|
||||
case P8P_CA_TYPE.setVariable.code:
|
||||
return action.params.length === 0 || action.params.some(item => !item.variableSource);
|
||||
//Для всех остальных
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//Считывание параметров типа действия
|
||||
const getActionTypeParams = type => {
|
||||
//Определяем от типа
|
||||
switch (type) {
|
||||
//Открыть раздел
|
||||
case P8P_CA_TYPE.openUnit.code:
|
||||
return { ...P8P_CA_OPEN_UNIT_INITIAL, inputParams: [{ ...P8P_CA_INPUT_PARAM_INITIAL }] };
|
||||
//Открыть панель
|
||||
case P8P_CA_TYPE.openPanel.code:
|
||||
return { ...P8P_CA_OPEN_PANEL_INITIAL };
|
||||
//Установить переменную
|
||||
case P8P_CA_TYPE.setVariable.code:
|
||||
return [{ ...P8P_CA_VARIABLE_LINK_INITIAL }];
|
||||
//Для всех остальных
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
//Определение значения действия по типу
|
||||
const getActionValueByType = (type, value, values, prms = {}, getCustomTypeValue = null) => {
|
||||
//Исходим от типа действия
|
||||
switch (type) {
|
||||
//Значение (стандартное)
|
||||
case P8P_CA_DEF_TYPE_VALUE.TEXT_VALUE:
|
||||
return value;
|
||||
//Переменная (стандартное)
|
||||
case P8P_CA_DEF_TYPE_VALUE.VARIABLE:
|
||||
return values[value];
|
||||
//Кастомный тип
|
||||
default:
|
||||
return getCustomTypeValue ? getCustomTypeValue({ type, value, values, prms }) : null;
|
||||
}
|
||||
};
|
||||
|
||||
//Определения функции действия
|
||||
const getActionFunction = (item, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue = null) => {
|
||||
//Выполняем исходя из типа действия
|
||||
switch (item.type) {
|
||||
//Открыть раздел
|
||||
case P8P_CA_TYPE.openUnit.code: {
|
||||
return ({ event, values, prms }) => {
|
||||
onOpenUnit({
|
||||
unitCode: item.params.unitCode,
|
||||
showMethod: item.params.showMethod,
|
||||
inputParameters: item.params.inputParams.reduce(
|
||||
(prev, cur) =>
|
||||
cur.inputParameter
|
||||
? [
|
||||
...prev,
|
||||
{
|
||||
name: cur.inputParameter,
|
||||
value: getActionValueByType(cur.resultValue.type, cur.resultValue.value, values, prms, getCustomTypeValue)
|
||||
}
|
||||
]
|
||||
: [...prev],
|
||||
[]
|
||||
),
|
||||
modal: item.params.modal
|
||||
});
|
||||
event?.stopPropagation && event.stopPropagation();
|
||||
};
|
||||
}
|
||||
//Открыть панель
|
||||
case P8P_CA_TYPE.openPanel.code: {
|
||||
return ({ event, values, prms }) => {
|
||||
onOpenPanel({
|
||||
id: genUID(),
|
||||
url: `${configUrlBase}panels_editor?SCODE=${getActionValueByType(
|
||||
item.params.panelValue.type,
|
||||
item.params.panelValue.value,
|
||||
values,
|
||||
prms,
|
||||
getCustomTypeValue
|
||||
)}`,
|
||||
caption:
|
||||
getActionValueByType(item.params.tabValue.type, item.params.tabValue.value, values, prms, getCustomTypeValue) ||
|
||||
"Редактор панелей"
|
||||
});
|
||||
event?.stopPropagation && event.stopPropagation();
|
||||
};
|
||||
}
|
||||
//Установить переменную
|
||||
case P8P_CA_TYPE.setVariable.code: {
|
||||
//Устанавливаем новые значения проводников
|
||||
return ({ event, values, prms }) => {
|
||||
//Собираем объект новых значений проводников
|
||||
let changedValues = item.params.reduce(
|
||||
(prev, cur) => ({
|
||||
...prev,
|
||||
...{
|
||||
[cur.variableSource]: getActionValueByType(cur.resultValue.type, cur.resultValue.value, values, prms, getCustomTypeValue)
|
||||
}
|
||||
}),
|
||||
{}
|
||||
);
|
||||
onProviderChange({ ...changedValues });
|
||||
event?.stopPropagation && event.stopPropagation();
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//Выполнение действий компонента
|
||||
const getHandlersByActions = (actions, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue = null) => {
|
||||
//Инициализируем обработчики
|
||||
const handlers = actions.reduce((prev, cur) => {
|
||||
//Ключ обработчика
|
||||
let key = `${cur.area}.${cur.element}`;
|
||||
//Если уже был добавлен - не добавляем, иначе добавляем
|
||||
return !prev[key] ? { ...prev, [key]: { area: cur.area, element: cur.element, fn: null } } : { ...prev };
|
||||
}, {});
|
||||
//Обходим уникальные обработчики
|
||||
for (const handler in handlers) {
|
||||
//Считываем действия области
|
||||
let areaActions = actions.filter(item => item.area === handlers[handler].area && item.element === handlers[handler].element);
|
||||
//Собираем массив обработчиков области
|
||||
let areaFunctions = areaActions.reduce(
|
||||
(fns, action) => [...fns, getActionFunction(action, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue)],
|
||||
[]
|
||||
);
|
||||
//Устанавливаем области последовательный вызов обработчиков
|
||||
handlers[handler].fn = ({ event, values, prms }) => {
|
||||
areaFunctions.map(fn => fn({ event, values, prms }));
|
||||
};
|
||||
}
|
||||
//Возвращаем обработчики
|
||||
return handlers;
|
||||
};
|
||||
|
||||
//Считывание всех связанных переменных рекурсивно
|
||||
const getActionsVariablesRecursive = (objValue, propName = null) => {
|
||||
//Если это значение
|
||||
if (!(typeof objValue === "object") && !Array.isArray(objValue)) {
|
||||
//Если поле из списка хранящих ссылку - берем его
|
||||
return P8P_CA_SOURCE_FIELDS.includes(propName) && objValue ? [objValue] : [];
|
||||
}
|
||||
//Если это объект хранящий тип и значение - может хранить ссылку на переменную
|
||||
if (P8P_CA_OBJECT_SOURCE_FIELDS.includes(propName)) {
|
||||
//Если тип "Переменная" - указываем значение
|
||||
return objValue?.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE && objValue?.value ? [objValue.value] : [];
|
||||
}
|
||||
//Если это массив
|
||||
if (Array.isArray(objValue)) {
|
||||
return objValue.reduce((prev, cur) => [...prev, ...getActionsVariablesRecursive(cur)], []);
|
||||
} else {
|
||||
//Если это объект
|
||||
return Object.keys(objValue).reduce((prev, cur) => [...prev, ...getActionsVariablesRecursive(objValue[cur], cur)], []);
|
||||
}
|
||||
};
|
||||
|
||||
//Считывание всех связанных переменных действий
|
||||
const getActionsVariables = actions => {
|
||||
//Считываем связанные переменные действий рекурсивно
|
||||
return getActionsVariablesRecursive(actions);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { isActionOkDisabled, getActionTypeParams, getActionsVariables, getHandlersByActions };
|
||||
@ -1,82 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Общие ресурсы компонента "Редактор условия"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Доступные операторы условия
|
||||
const P8P_CC_OPERATORS = [
|
||||
{ name: "==", value: "equal" },
|
||||
{ name: "!=", value: "notEqual" },
|
||||
{ name: "<=", value: "lessEqual" },
|
||||
{ name: "<", value: "less" },
|
||||
{ name: ">=", value: "greaterEqual" },
|
||||
{ name: ">", value: "greater" },
|
||||
{ name: "in", value: "in" }
|
||||
];
|
||||
|
||||
//Структура параметра поля условия
|
||||
const P8P_CC_FIELD_PRM_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
hasElement: PropTypes.bool,
|
||||
icon: PropTypes.string
|
||||
});
|
||||
|
||||
//Структура поля условия
|
||||
const P8P_CC_FIELD_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired
|
||||
});
|
||||
|
||||
//Структура оператора условия
|
||||
const P8P_CC_OPERATOR_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired
|
||||
});
|
||||
|
||||
//Структура условия
|
||||
const P8P_CC_SHAPE = PropTypes.shape({
|
||||
condField: P8P_CC_FIELD_SHAPE.isRequired,
|
||||
condOperator: P8P_CC_OPERATOR_SHAPE.isRequired,
|
||||
condElement: PropTypes.string, //Пока
|
||||
condValue: PropTypes.string.isRequired,
|
||||
resField: P8P_CC_FIELD_SHAPE.isRequired,
|
||||
resElement: PropTypes.string, //Пока
|
||||
resValue: PropTypes.string.isRequired
|
||||
});
|
||||
|
||||
//Начальное состояние поля условия
|
||||
const P8P_CC_FIELD_INITIAL = {
|
||||
name: "",
|
||||
value: ""
|
||||
};
|
||||
|
||||
//Начальное состояние условия
|
||||
const P8P_CC_INITIAL = {
|
||||
condField: { ...P8P_CC_FIELD_INITIAL },
|
||||
condOperator: { ...P8P_CC_OPERATORS[0] },
|
||||
condElement: "",
|
||||
condValue: "",
|
||||
resField: { ...P8P_CC_FIELD_INITIAL },
|
||||
resElement: "",
|
||||
resValue: ""
|
||||
};
|
||||
|
||||
//Начальное состояние условий
|
||||
const P8P_CCS_INITIAL = [];
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CC_OPERATORS, P8P_CC_FIELD_PRM_SHAPE, P8P_CC_FIELD_SHAPE, P8P_CC_SHAPE, P8P_CC_INITIAL, P8P_CCS_INITIAL };
|
||||
@ -1,239 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Редактор условия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { TextField, Select, MenuItem, FormControl, InputLabel, Stack, Box } from "@mui/material"; //Интерфейсные элементы
|
||||
import { TITLES } from "../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { P8PConfigDialog } from "../p8p_config_dialog"; //Диалог настройки
|
||||
import { P8P_CC_INITIAL, P8P_CC_SHAPE, P8P_CC_OPERATORS, P8P_CC_FIELD_PRM_SHAPE } from "./common"; //Общие ресурсы условий
|
||||
import { deepCopyObject } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { isConditionOkDisabled } from "./util"; //Вспомогательные ресурсы условий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
BOX_COND_GROUP: { display: "flex", flexDirection: "row", gap: "10px", paddingBottom: "10px" },
|
||||
BOX_COND_ELEMENT: width => ({ minWidth: width })
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор условия
|
||||
const P8PCCEditor = ({ condition = null, onOk = null, onCancel = null, condFields = [], resFields = [] }) => {
|
||||
//Собственное состояние - признак инициализиации
|
||||
const [init, setInit] = useState(true);
|
||||
|
||||
//Собственное состояние - параметры элемента формы
|
||||
const [state, setState] = useState({});
|
||||
|
||||
//При закрытии редактора с сохранением
|
||||
const handleOk = () => onOk && onOk({ ...state });
|
||||
|
||||
//Собственное состояние - доступность поля "Элемент условия"
|
||||
const [hasCondElement, setHasCondElement] = useState(false);
|
||||
|
||||
//Собственное состояние - доступность поля "Элемент результата"
|
||||
const [hasResElement, setHasResElement] = useState(false);
|
||||
|
||||
//При закрытии редактора с отменой
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//При изменении параметра элемента
|
||||
const handleChange = e => setState(pv => ({ ...pv, [e.target.name]: e.target.value }));
|
||||
|
||||
//При изменении поля условия
|
||||
const handleCondFieldChange = e => {
|
||||
//Считываем нужный элемент
|
||||
const newCondField = condFields.find(item => item.name === e.target.value);
|
||||
//Обновляем поле (объект)
|
||||
setState(pv => ({ ...pv, [e.target.name]: { name: newCondField.name, value: newCondField.value } }));
|
||||
//Определяем доступность поля "Элемент условия"
|
||||
setHasCondElement(newCondField?.hasElement || false);
|
||||
};
|
||||
|
||||
//При изменении поля результата
|
||||
const handleResFieldChange = e => {
|
||||
//Считываем нужный элемент
|
||||
const newResField = resFields.find(item => item.name === e.target.value);
|
||||
//Обновляем поле результата
|
||||
setState(pv => ({ ...pv, [e.target.name]: { name: newResField.name, value: newResField.value } }));
|
||||
//Определяем доступность поля "Элемент результата"
|
||||
setHasResElement(newResField?.hasElement || false);
|
||||
};
|
||||
|
||||
//При изменении оператора условия
|
||||
const handleOperatorChange = e => {
|
||||
//Считываем нужный элемент
|
||||
const newOperator = P8P_CC_OPERATORS.find(item => item.name === e.target.value);
|
||||
//Обновляем оператор
|
||||
setState(pv => ({ ...pv, [e.target.name]: { ...newOperator } }));
|
||||
};
|
||||
|
||||
//При инициализации условия
|
||||
useEffect(() => {
|
||||
//Если это иницализация
|
||||
if (init) {
|
||||
//Если это открытие условия - берем его параметры, иначе - инициализируем изначальными
|
||||
const initCondition = condition
|
||||
? condition
|
||||
: {
|
||||
...P8P_CC_INITIAL,
|
||||
condField: { name: condFields[0].name, value: condFields[0].value },
|
||||
resField: { name: resFields[0].name, value: resFields[0].value }
|
||||
};
|
||||
//Устанавливаем параметры действия
|
||||
setState(deepCopyObject(initCondition));
|
||||
//Определяем доступность "Элемент условия"
|
||||
setHasCondElement(condFields.find(item => item.name === initCondition.condField.name)?.hasElement || false);
|
||||
//Определяем доступность "Элемент результата"
|
||||
setHasResElement(resFields.find(item => item.name === initCondition.resField.name)?.hasElement || false);
|
||||
//Сбрасываем признак инициализации
|
||||
setInit(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [init]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{!init ? (
|
||||
<P8PConfigDialog
|
||||
title={`${condition ? TITLES.UPDATE : TITLES.INSERT} условия`}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okDisabled={isConditionOkDisabled(state)}
|
||||
>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
<Box>
|
||||
<Box sx={STYLES.BOX_COND_GROUP}>
|
||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("160px")}>
|
||||
<InputLabel id={"condField-label"}>Поле</InputLabel>
|
||||
<Select
|
||||
name={"condField"}
|
||||
value={state.condField.name}
|
||||
labelId={"condField-label"}
|
||||
label={"Поле"}
|
||||
onChange={handleCondFieldChange}
|
||||
>
|
||||
{condFields.map((item, index) => (
|
||||
<MenuItem value={item.name} key={index}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("70px")}>
|
||||
<InputLabel id={"condOperator-label"}>Оператор</InputLabel>
|
||||
<Select
|
||||
name={"condOperator"}
|
||||
value={state.condOperator.name}
|
||||
labelId={"condOperator-label"}
|
||||
label={"Оператор"}
|
||||
onChange={handleOperatorChange}
|
||||
>
|
||||
{P8P_CC_OPERATORS.map((item, index) => (
|
||||
<MenuItem value={item.name} key={index}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.condValue}
|
||||
label={"Значение"}
|
||||
name={"condValue"}
|
||||
onChange={handleChange}
|
||||
sx={STYLES.BOX_COND_ELEMENT("160px")}
|
||||
/>
|
||||
</Box>
|
||||
{hasCondElement ? (
|
||||
<TextField
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{ pb: 1.25 }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.condElement}
|
||||
label={"Элемент условия"}
|
||||
name={"condElement"}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
) : null}
|
||||
<Box sx={STYLES.BOX_COND_GROUP}>
|
||||
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("160px")}>
|
||||
<InputLabel id={"resField-label"}>Поле результата</InputLabel>
|
||||
<Select
|
||||
name={"resField"}
|
||||
value={state.resField.name}
|
||||
labelId={"resField-label"}
|
||||
label={"Поле результата"}
|
||||
onChange={handleResFieldChange}
|
||||
>
|
||||
{resFields.map((item, index) => (
|
||||
<MenuItem value={item.name} key={index}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.resValue}
|
||||
label={"Результат"}
|
||||
name={"resValue"}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
</Box>
|
||||
{hasResElement ? (
|
||||
<TextField
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{ pb: 1.25 }}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.resElement}
|
||||
label={"Элемент результата"}
|
||||
name={"resElement"}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
</Stack>
|
||||
</P8PConfigDialog>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Редактор условия
|
||||
P8PCCEditor.propTypes = {
|
||||
condition: P8P_CC_SHAPE,
|
||||
onOk: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
condFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||
resFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PCCEditor };
|
||||
@ -1,89 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Вспомогательные ресурсы компонента "Редактор условия"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Функции операторов условий
|
||||
const P8P_CC_OPERATOR_FUNC = {
|
||||
equal: (objValue, value) => objValue == value,
|
||||
notEqual: (objValue, value) => objValue != value,
|
||||
lessEqual: (objValue, value) => objValue <= value,
|
||||
less: (objValue, value) => objValue < value,
|
||||
greaterEqual: (objValue, value) => objValue >= value,
|
||||
greater: (objValue, value) => objValue > value,
|
||||
in: (objValue, value) =>
|
||||
value
|
||||
.split(",")
|
||||
.map(item => item.trim())
|
||||
.includes(objValue + "")
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Загрузка условий в объект
|
||||
const loadConditionsToObject = (obj, conditions) => {
|
||||
//Инициализируем новый объект
|
||||
let newObj = { ...obj };
|
||||
//Инициализируем функцию оператора
|
||||
let operatorFunc;
|
||||
//Если изначальный индикатор загружен
|
||||
if (Object.keys(newObj).length !== 0) {
|
||||
//Обходим условия
|
||||
conditions.map(item => {
|
||||
//Функция оператора
|
||||
operatorFunc = P8P_CC_OPERATOR_FUNC[item.condOperator.value];
|
||||
//Проверяем условие
|
||||
if (operatorFunc && operatorFunc(newObj[item.condField.value], item.condValue)) {
|
||||
//Устанавливаем поле в новое значение
|
||||
newObj[item.resField.value] = item.resValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
//Возвращаем новый объект
|
||||
return newObj;
|
||||
};
|
||||
|
||||
//Считывание результатов условия
|
||||
const getConditionsValues = (obj, conditions, element = "") => {
|
||||
//Инициализируем функцию оператора
|
||||
let operatorFunc;
|
||||
//Инициализируем значение поля
|
||||
let condFieldValue = "";
|
||||
//Инициализируем результат
|
||||
let resObject = {};
|
||||
//Обходим условия
|
||||
conditions.map(item => {
|
||||
//Определяем значение поля условия
|
||||
condFieldValue = item.condElement ? obj[item.condElement] : obj[item.condField.value];
|
||||
//Функция оператора
|
||||
operatorFunc = P8P_CC_OPERATOR_FUNC[item.condOperator.value];
|
||||
//Проверяем условие
|
||||
if (operatorFunc && operatorFunc(condFieldValue, item.condValue)) {
|
||||
//Если в условии нет элемента результата или он равен текущему элементу
|
||||
if (!item.resElement || (element && item.resElement === element)) {
|
||||
resObject[item.resField.value] = item.resValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
//Возвращаем новый объект
|
||||
return resObject;
|
||||
};
|
||||
|
||||
//Доступность сохранения настроек условия
|
||||
const isConditionOkDisabled = condition => (!condition.condField.value || !condition.condOperator.value || !condition.resField.value ? true : false);
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { loadConditionsToObject, getConditionsValues, isConditionOkDisabled };
|
||||
@ -1,71 +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 P8P_COMPONENT_INLINE_MESSAGE_TYPE = {
|
||||
COMMON: "COMMON",
|
||||
ERROR: "ERROR"
|
||||
};
|
||||
|
||||
//Типовые сообщения компонентов
|
||||
const P8P_COMPONENT_INLINE_MESSAGE = {
|
||||
NO_DATA_FOUND: TEXTS.NO_DATA_FOUND,
|
||||
NO_SETTINGS: TEXTS.NO_SETTINGS
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Информационное сообщение внутри компонента
|
||||
const P8PComponentInlineMessage = ({ icon, name, message, type = P8P_COMPONENT_INLINE_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 != P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR ? "text.secondary" : "error.dark"}
|
||||
variant={"caption"}
|
||||
>
|
||||
{message}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Информационное сообщение внутри компонента
|
||||
P8PComponentInlineMessage.propTypes = {
|
||||
icon: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
message: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf(Object.values(P8P_COMPONENT_INLINE_MESSAGE_TYPE))
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_COMPONENT_INLINE_MESSAGE_TYPE, P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage };
|
||||
@ -1,41 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Дополнительные настройки источников
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Наименование параметра данных компонента, полученных с сервера
|
||||
const P8P_COMPONENT_SETTINGS_RESP_ARGS = {
|
||||
INDICATOR: "XINDICATOR",
|
||||
CHART: "XCHART",
|
||||
TABLE: "XDATA_GRID"
|
||||
};
|
||||
|
||||
//Наименование путей компонента
|
||||
const P8P_COMPONENT_SETTINGS_PATHS = {
|
||||
INDICATOR: "indicator",
|
||||
CHART: "chart",
|
||||
TABLE: "table",
|
||||
FORM: "form"
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_COMPONENT_SETTINGS_RESP_ARGS, P8P_COMPONENT_SETTINGS_PATHS };
|
||||
@ -1,99 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Условия
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PCCEditor } from "./p8p_component_condition/editor"; //Редактор условия
|
||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||
import { P8P_CCS_INITIAL, P8P_CC_SHAPE, P8P_CC_FIELD_PRM_SHAPE } from "./p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Условия
|
||||
const P8PConditions = ({ conditions = P8P_CCS_INITIAL, condFields, resFields, onChange = null } = {}) => {
|
||||
//Собственное состояние - редактор условий
|
||||
const [conditionEditor, setConditionEditor] = useState({ display: false, index: null });
|
||||
|
||||
//При изменении условий
|
||||
const handleConditionsChange = conditions => onChange && onChange(conditions);
|
||||
|
||||
//При добавлении нового условия
|
||||
const handleConditionAdd = () => setConditionEditor({ display: true, index: null });
|
||||
|
||||
//При нажатии на условие
|
||||
const handleConditionClick = i => setConditionEditor({ display: true, index: i });
|
||||
|
||||
//При удалении условия
|
||||
const handleConditionDelete = i => {
|
||||
const newConditions = [...conditions];
|
||||
newConditions.splice(i, 1);
|
||||
handleConditionsChange(newConditions);
|
||||
};
|
||||
|
||||
//При отмене сохранения изменений условия
|
||||
const handleConditionCancel = () => setConditionEditor({ display: false, index: null });
|
||||
|
||||
//При сохранении изменений условия
|
||||
const handleConditionSave = condition => {
|
||||
const newConditions = [...conditions];
|
||||
conditionEditor.index == null ? newConditions.push({ ...condition }) : (newConditions[conditionEditor.index] = { ...condition });
|
||||
handleConditionsChange(newConditions);
|
||||
setConditionEditor({ display: false, index: null });
|
||||
};
|
||||
|
||||
//Определяем структуру условий для отображения
|
||||
const conditionChips = conditions.map(item => {
|
||||
//Собираем текст условия
|
||||
let text = `${item.condField.name} ${item.condOperator.name} ${item.condValue}`;
|
||||
//Считываем поле результата
|
||||
let resField = resFields.find(field => field.name === item.resField.name);
|
||||
//Формируем структуру для отображения карточки действия
|
||||
return { text: text, title: text, icon: resField?.icon, iconTitle: resField?.name };
|
||||
});
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{conditionEditor.display && (
|
||||
<P8PCCEditor
|
||||
condition={conditionEditor.index !== null ? { ...conditions[conditionEditor.index] } : null}
|
||||
onCancel={handleConditionCancel}
|
||||
onOk={handleConditionSave}
|
||||
condFields={condFields}
|
||||
resFields={resFields}
|
||||
/>
|
||||
)}
|
||||
<P8PChipList items={conditionChips} onClick={handleConditionClick} onDelete={handleConditionDelete} />
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handleConditionAdd}>
|
||||
Добавить условие
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - условия
|
||||
P8PConditions.propTypes = {
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
condFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||
resFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PConditions };
|
||||
@ -1,42 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Диалог настройки
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PDialog, P8P_DIALOG_WIDTH } from "../p8p_dialog"; //Типовой диалог
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог настройки
|
||||
const P8PConfigDialog = ({ title, children, width, onOk, onCancel, okDisabled = false }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PDialog title={title} onOk={onOk} onCancel={onCancel} width={width} okDisabled={okDisabled}>
|
||||
{children}
|
||||
</P8PDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалог настройки
|
||||
P8PConfigDialog.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||
width: PropTypes.oneOf(Object.values(P8P_DIALOG_WIDTH)),
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
okDisabled: PropTypes.bool
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PConfigDialog };
|
||||
@ -1,105 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Источник данных
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, IconButton, Icon, Typography, Button, Card, CardContent, CardActions, CardActionArea } from "@mui/material"; //Интерфейсные элементы
|
||||
import { BUTTONS, TEXTS } from "../../../app.text"; //Общие текстовые ресурсы
|
||||
import { P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_TYPE_NAME, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
||||
import { P8PDataSourceConfigDialog } from "./p8p_data_source_config_dialog"; //Диалог настройки источника данных
|
||||
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Источник данных
|
||||
const P8PDataSource = ({ 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({ ...P8P_DATA_SOURCE_INITIAL });
|
||||
|
||||
//Расчет флага "настроенности"
|
||||
const configured = dataSource?.type ? true : false;
|
||||
|
||||
//Структура параметров для отображения
|
||||
const argChips =
|
||||
configured && dataSource.arguments.map(argument => ({ text: `:${argument.name} = ${argument.valueSource || argument.value || "NULL"}` }));
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{configDlg && (
|
||||
<P8PDataSourceConfigDialog
|
||||
dataSource={dataSource}
|
||||
valueProviders={valueProviders}
|
||||
onOk={handleSetupOk}
|
||||
onCancel={handleSetupCancel}
|
||||
/>
|
||||
)}
|
||||
{configured && (
|
||||
<Card variant={"outlined"}>
|
||||
<CardActionArea onClick={handleSetup}>
|
||||
<CardContent>
|
||||
<Typography variant={"subtitle1"} noWrap={true}>
|
||||
{dataSource.type === P8P_DATA_SOURCE_TYPE.USER_PROC ? dataSource.userProc : TEXTS.UNNAMED_SOURCE}
|
||||
</Typography>
|
||||
<Typography variant={"caption"} color={"text.secondary"} noWrap={true}>
|
||||
{P8P_DATA_SOURCE_TYPE_NAME[dataSource.type] || TEXTS.UNKNOWN_SOURCE_TYPE}
|
||||
</Typography>
|
||||
<Stack direction={"column"} spacing={1} pt={2}>
|
||||
<P8PChipList items={argChips} />
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions>
|
||||
<IconButton onClick={handleDelete}>
|
||||
<Icon>delete</Icon>
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
)}
|
||||
{!configured && (
|
||||
<Button startIcon={<Icon>build</Icon>} onClick={handleSetup}>
|
||||
{BUTTONS.CONFIG}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Источник данных
|
||||
P8PDataSource.propTypes = {
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PDataSource };
|
||||
@ -1,87 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Общие ресурсы компонента "Источник данных"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import client from "../../core/client"; //Клиент БД
|
||||
import { CAPTIONS } from "../../../app.text"; //Общие текстовые ресурсы
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Типы даных аргументов
|
||||
const P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE = {
|
||||
STR: client.SERV_DATA_TYPE_STR,
|
||||
NUMB: client.SERV_DATA_TYPE_NUMB,
|
||||
DATE: client.SERV_DATA_TYPE_DATE
|
||||
};
|
||||
|
||||
//Структура аргумента источника данных
|
||||
const P8P_DATA_SOURCE_ARGUMENT_SHAPE = PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
caption: PropTypes.string.isRequired,
|
||||
dataType: PropTypes.oneOf(Object.values(P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE)),
|
||||
req: PropTypes.bool.isRequired,
|
||||
value: PropTypes.any,
|
||||
valueSource: PropTypes.string
|
||||
});
|
||||
|
||||
//Начальное состояние аргумента источника данных
|
||||
const P8P_DATA_SOURCE_ARGUMENT_INITIAL = {
|
||||
name: "",
|
||||
caption: "",
|
||||
dataType: "",
|
||||
req: false,
|
||||
value: "",
|
||||
valueSource: ""
|
||||
};
|
||||
|
||||
//Типы источников данных
|
||||
const P8P_DATA_SOURCE_TYPE = {
|
||||
USER_PROC: "USER_PROC",
|
||||
QUERY: "QUERY"
|
||||
};
|
||||
|
||||
//Типы источников данных (наименования)
|
||||
const P8P_DATA_SOURCE_TYPE_NAME = {
|
||||
[P8P_DATA_SOURCE_TYPE.USER_PROC]: CAPTIONS.USER_PROC,
|
||||
[P8P_DATA_SOURCE_TYPE.QUERY]: CAPTIONS.QUERY
|
||||
};
|
||||
|
||||
//Структура источника данных
|
||||
const P8P_DATA_SOURCE_SHAPE = PropTypes.shape({
|
||||
type: PropTypes.oneOf([...Object.values(P8P_DATA_SOURCE_TYPE), ""]),
|
||||
userProc: PropTypes.string,
|
||||
stored: PropTypes.string,
|
||||
respArg: PropTypes.string,
|
||||
arguments: PropTypes.arrayOf(P8P_DATA_SOURCE_ARGUMENT_SHAPE)
|
||||
});
|
||||
|
||||
//Начальное состояние истоника данных
|
||||
const P8P_DATA_SOURCE_INITIAL = {
|
||||
type: "",
|
||||
userProc: "",
|
||||
query: "",
|
||||
stored: "",
|
||||
respArg: "",
|
||||
arguments: []
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export {
|
||||
P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE,
|
||||
P8P_DATA_SOURCE_ARGUMENT_INITIAL,
|
||||
P8P_DATA_SOURCE_SHAPE,
|
||||
P8P_DATA_SOURCE_TYPE,
|
||||
P8P_DATA_SOURCE_TYPE_NAME,
|
||||
P8P_DATA_SOURCE_INITIAL
|
||||
};
|
||||
@ -1,253 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Диалог настройки источника данных
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useEffect, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, IconButton, Icon, TextField, InputAdornment, MenuItem, Menu } from "@mui/material"; //Интерфейсные элементы
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { TITLES, CAPTIONS } from "../../../app.text"; //Общие текстовые ресурсы
|
||||
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_ARGUMENT_INITIAL, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
||||
import { useUserProcDesc } from "./p8p_data_source_hooks"; //Хуки источников данных
|
||||
import { P8PDataSourceQuerySelector } from "./p8p_data_source_query_selector"; //Диалог выбора записи редактора запросов
|
||||
import { hasValue } from "../../core/utils"; //Вспомогательные функции
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог настройки источника данных
|
||||
const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => {
|
||||
//Собственное состояние - параметры элемента формы
|
||||
const [state, setState] = useState({ ...P8P_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 [openQuerySelector, setOpenQuerySelector] = useState(false);
|
||||
|
||||
//Собственное состояние - доступность полей выбора источника
|
||||
const [disabledFields, setDisabledFields] = useState({ query: false, userProc: false });
|
||||
|
||||
//Подключение к контексту приложения
|
||||
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({ ...P8P_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: P8P_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, "");
|
||||
|
||||
//Открытие диалога выбора запроса
|
||||
const handleOpenQuerySelector = () => setOpenQuerySelector(true);
|
||||
|
||||
//Закрытие диалога выбора запроса
|
||||
const handleCancelQuerySelector = () => setOpenQuerySelector(false);
|
||||
|
||||
//При нажатии на очистку мнемокода запроса
|
||||
const handleQueryClearClick = () => setState({ ...P8P_DATA_SOURCE_INITIAL });
|
||||
|
||||
//При нажатии на выбор запроса в качестве источника данных
|
||||
const handleQuerySelectClick = query => {
|
||||
setState(pv => ({ ...pv, type: P8P_DATA_SOURCE_TYPE.QUERY, query: query.code }));
|
||||
handleCancelQuerySelector();
|
||||
};
|
||||
|
||||
//При изменении описания пользовательской процедуры
|
||||
useEffect(() => {
|
||||
if (userProcDesc)
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
stored: userProcDesc?.stored?.name,
|
||||
respArg: userProcDesc?.stored?.respArg,
|
||||
arguments: (userProcDesc?.arguments || []).map(argument => ({ ...P8P_DATA_SOURCE_ARGUMENT_INITIAL, ...argument }))
|
||||
}));
|
||||
}, [userProcDesc]);
|
||||
|
||||
//При изменении источника
|
||||
useEffect(() => {
|
||||
/*
|
||||
Если выбран запрос - блокируем выбор процедуры
|
||||
Если выбрана процедура - блокируем выбор запроса
|
||||
Ничего не выбрано - доступны все варианты
|
||||
*/
|
||||
hasValue(state.query)
|
||||
? setDisabledFields({ query: false, userProc: true })
|
||||
: hasValue(state.userProc)
|
||||
? setDisabledFields({ query: true, userProc: false })
|
||||
: setDisabledFields({ query: false, userProc: false });
|
||||
}, [state.query, state.userProc]);
|
||||
|
||||
//Список значений
|
||||
const values = Object.keys(valueProviders);
|
||||
|
||||
//Наличие значений
|
||||
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>
|
||||
);
|
||||
|
||||
//Доступность сохранения настройки источника данных
|
||||
const okDisabled = !state.query && !state.userProc ? true : false;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<>
|
||||
{openQuerySelector ? (
|
||||
<P8PDataSourceQuerySelector current={state.query} onSelect={handleQuerySelectClick} onCancel={handleCancelQuerySelector} />
|
||||
) : null}
|
||||
<P8PConfigDialog title={TITLES.DATA_SOURCE_CONFIG} onOk={handleOk} onCancel={handleCancel} okDisabled={okDisabled}>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{valueProvidersMenu}
|
||||
{/* ДОРАБАТЫВАТЬ ПОСЛЕ РЕАЛИЗАЦИИ ЗАПРОСОВ */}
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.query}
|
||||
label={CAPTIONS.QUERY}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleQueryClearClick} disabled={disabledFields.query}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleOpenQuerySelector} disabled={disabledFields.query}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
disabled={disabledFields.query}
|
||||
/>
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.userProc}
|
||||
label={CAPTIONS.USER_PROC}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleUserProcClearClick} disabled={disabledFields.userProc}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={handleUserProcSelectClick} disabled={disabledFields.userProc}>
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
disabled={disabledFields.userProc}
|
||||
/>
|
||||
{Array.isArray(state?.arguments) &&
|
||||
state.arguments.map((argument, i) => (
|
||||
<TextField
|
||||
key={i}
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={argument.value || argument.valueSource}
|
||||
label={argument.caption}
|
||||
onChange={e => handleArgumentChange(i, e.target.value)}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => handleArgumentClearClick(i)}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
{isValues && (
|
||||
<IconButton id={i} onClick={handleArgumentLinkMenuClick}>
|
||||
<Icon>settings_ethernet</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</P8PConfigDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалог настройки источника данных
|
||||
P8PDataSourceConfigDialog.propTypes = {
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PDataSourceConfigDialog };
|
||||
@ -1,229 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Пользовательские хуки компонента "Источник данных"
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect, useRef } from "react"; //Классы React
|
||||
import client from "../../core/client"; //Клиент взаимодействия с сервером приложений
|
||||
import { ERRORS } from "../../../app.text"; //Общие текстовые ресурсы
|
||||
import { formatErrorMessage } from "../../core/utils"; //Общие вспомогательные функции
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE } from "./p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import { getConditionsValues } from "./p8p_component_condition/util"; //Вспомогательные ресурсы условий
|
||||
import { getHandlersByActions } from "./p8p_component_action/util"; //Вспомогательные ресурсы действий
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Описание пользовательской процедуры
|
||||
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_PE.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 useDataSource = ({ dataSource, values, componentRespArg = "" }) => {
|
||||
//Контроллер для прерывания запросов
|
||||
const abortController = useRef(null);
|
||||
|
||||
//Собственное состояние - параметры исполнения
|
||||
const [state, setState] = useState({ stored: null, storedArgs: [], respArg: null, reqSet: false });
|
||||
|
||||
//Собственное состояние - флаг загрузки
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState({ componentData: {}, init: false });
|
||||
|
||||
//Собственное состояние - ошибка получения данных
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
//Собственное состояние - наличие настроек
|
||||
const [haveConfing, setHaveConfig] = useState(false);
|
||||
|
||||
//Собственное состояние - наличие данных
|
||||
const [haveData, setHaveData] = useState(false);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости обновление информации о наличии данных
|
||||
useEffect(() => {
|
||||
setHaveData(data.init === true && !error ? true : false);
|
||||
}, [data.init, error]);
|
||||
|
||||
//При необходимости обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
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({ componentData: { ...data[componentRespArg] }, init: true });
|
||||
} catch (e) {
|
||||
if (e.message !== client.ERR_ABORTED) {
|
||||
setError(formatErrorMessage(e.message).text);
|
||||
setData({ componentData: {}, init: false });
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
if (state.reqSet) {
|
||||
if (state.stored) loadData();
|
||||
} else setData({ componentData: {}, init: false });
|
||||
return () => abortController.current?.abort?.();
|
||||
}, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored, componentRespArg]);
|
||||
|
||||
//При изменении свойств
|
||||
useEffect(() => {
|
||||
//Устанавливаем признак наличия настроек
|
||||
setHaveConfig(dataSource?.stored ? true : false);
|
||||
//Устанавливаем параметры исполнения
|
||||
setState(pv => {
|
||||
if (dataSource?.type == P8P_DATA_SOURCE_TYPE.USER_PROC) {
|
||||
const { stored, respArg } = dataSource;
|
||||
let reqSet = true;
|
||||
const storedArgs = {};
|
||||
dataSource.arguments.forEach(argument => {
|
||||
let v = argument.valueSource ? values[argument.valueSource] : argument.value;
|
||||
storedArgs[argument.name] =
|
||||
argument.dataType == P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE.NUMB
|
||||
? isNaN(parseFloat(v))
|
||||
? null
|
||||
: parseFloat(v)
|
||||
: argument.dataType == P8P_DATA_SOURCE_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(ERRORS.DATA_SOURCE_NO_REQ_ARGS);
|
||||
setData({ componentData: {}, init: false });
|
||||
}
|
||||
return { stored, respArg, storedArgs, reqSet };
|
||||
} else return pv;
|
||||
} else return pv;
|
||||
});
|
||||
}, [dataSource, values]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data.componentData, error, haveConfing, haveData, isLoading];
|
||||
};
|
||||
|
||||
//Изменение данных компонента с учетом условий
|
||||
const useConditions = ({ componentData, conditions }) => {
|
||||
//Собственное состояние - текущие условия компонента
|
||||
const [currentConditions, setCurrentConditions] = useState([]);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState();
|
||||
|
||||
//При обновлении условий компонента
|
||||
useEffect(() => {
|
||||
//Если условия изменились
|
||||
if (JSON.stringify(currentConditions) != JSON.stringify(conditions)) {
|
||||
//Устанавливаем новые условия
|
||||
setCurrentConditions(conditions);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [conditions]);
|
||||
|
||||
//При обновлении данных или условий компонента
|
||||
useEffect(() => {
|
||||
//Если есть текущие условия
|
||||
if (currentConditions.length !== 0) {
|
||||
//Устанавливаем данные с учетом условий
|
||||
setData({ ...componentData, ...getConditionsValues(componentData, currentConditions) });
|
||||
} else {
|
||||
//Оставляем данные компонента
|
||||
setData({ ...componentData });
|
||||
}
|
||||
}, [currentConditions, componentData]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data];
|
||||
};
|
||||
|
||||
//Получение обработчиков компонента
|
||||
const useComponentHandlers = ({ actions = [], onValuesChange = null, getCustomTypeValue = null }) => {
|
||||
//Контроллер для текущего состояния действий
|
||||
const currentActions = useRef([]);
|
||||
|
||||
//Собственное состояние - обработчики компонента
|
||||
const [handlers, setHandlers] = useState({});
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { configUrlBase, pOnlineShowTab, pOnlineShowUnit } = useContext(ApplicationСtx);
|
||||
|
||||
//При необходимости обновления информации об обработчиках
|
||||
useEffect(() => {
|
||||
//Если изменились действия или параметры
|
||||
if (JSON.stringify(currentActions.current) != JSON.stringify(actions)) {
|
||||
//Считываем обработчики компонента
|
||||
setHandlers(getHandlersByActions(actions, pOnlineShowUnit, configUrlBase, pOnlineShowTab, onValuesChange, getCustomTypeValue));
|
||||
//Устанавливаем контроллер текущих действий
|
||||
currentActions.current = actions;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [actions]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [handlers];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useUserProcDesc, useDataSource, useConditions, useComponentHandlers };
|
||||
@ -1,75 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Диалог выбора записи редактора запросов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, List, ListItem, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
LIST: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог выбора записи редактора запросов
|
||||
const P8PDataSourceQuerySelector = ({ current = null, onSelect, onCancel }) => {
|
||||
//!!! НА РЕАЛИЗАЦИИ
|
||||
const test = [{ code: "TestCode", name: "TestName" }];
|
||||
//При выборе элемента списка
|
||||
const handleSelectClick = query => {
|
||||
onSelect && onSelect(query);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PConfigDialog title={"Запросы"} onCancel={onCancel}>
|
||||
<List sx={STYLES.LIST}>
|
||||
{test.map((query, i) => {
|
||||
const selected = query.code === current;
|
||||
return (
|
||||
<ListItem key={i}>
|
||||
<ListItemButton onClick={() => handleSelectClick(query)} selected={selected}>
|
||||
<ListItemText
|
||||
primary={query.code}
|
||||
secondaryTypographyProps={{ component: "div" }}
|
||||
secondary={
|
||||
<Stack direction={"column"}>
|
||||
<Typography variant={"caption"}>{`${query.code}, ${query.name}`}</Typography>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</P8PConfigDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалог выбора записи редактора запросов
|
||||
P8PDataSourceQuerySelector.propTypes = {
|
||||
current: PropTypes.string,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PDataSourceQuerySelector };
|
||||
@ -1,62 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Контейнер редактора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Divider, IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { BUTTONS } from "../../../app.text"; //Общие текстовые ресурсы
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Контейнер редактора
|
||||
const P8PEditorBox = ({ title, children, onSave, allowClose = true }) => {
|
||||
//При нажатии на "Сохранить"
|
||||
const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor);
|
||||
|
||||
//Флаг отображения кнопок сохранения
|
||||
const showSaveBar = onSave ? true : false;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box p={2}>
|
||||
<Divider>{title}</Divider>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{children}
|
||||
</Stack>
|
||||
{showSaveBar && (
|
||||
<Stack direction={"row"} justifyContent={"right"} p={1}>
|
||||
<IconButton onClick={() => handleSaveClick(false)} title={BUTTONS.APPLY}>
|
||||
<Icon>done</Icon>
|
||||
</IconButton>
|
||||
{allowClose ? (
|
||||
<IconButton onClick={() => handleSaveClick(true)} title={BUTTONS.SAVE}>
|
||||
<Icon>done_all</Icon>
|
||||
</IconButton>
|
||||
) : null}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Контейнер редактора
|
||||
P8PEditorBox.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||
onSave: PropTypes.func,
|
||||
allowClose: PropTypes.bool
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PEditorBox };
|
||||
@ -1,49 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Заголовок раздела редактора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Divider, Chip } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIVIDER: pt => ({ paddingTop: pt || pt === 0 ? `${pt}px` : "20px" }),
|
||||
CHIP: maxWidth => ({ cursor: "default", ...(maxWidth ? { maxWidth } : {}) })
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Заголовок раздела редактора
|
||||
const P8PEditorSubHeader = ({ title, paddingTop, maxWidth }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<Divider sx={STYLES.DIVIDER(paddingTop)}>
|
||||
<Chip label={title} size={"small"} title={title} sx={STYLES.CHIP(maxWidth)} />
|
||||
</Divider>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Заголовок раздела редактора
|
||||
P8PEditorSubHeader.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
paddingTop: PropTypes.number,
|
||||
maxWidth: PropTypes.string
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PEditorSubHeader };
|
||||
@ -1,62 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Компонент: Панель инструментов редактора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { IconButton, Icon, Stack, Grid } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Структура элемента панели инструментов редактора
|
||||
const P8P_EDITOR_TOOL_BAR_ITEM_SHAPE = PropTypes.shape({
|
||||
icon: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
customRenderer: PropTypes.func
|
||||
});
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Панель инструментов редактора
|
||||
const P8PEditorToolBar = ({ items = [] }) => {
|
||||
//Формирование представления
|
||||
return (
|
||||
<Stack direction={"row"} p={1}>
|
||||
<Grid container columns={items.length}>
|
||||
{items.map((item, i) => (
|
||||
<Grid item size={{ xs: 2, sm: 4, md: 4 }} key={i}>
|
||||
{item.customRenderer ? (
|
||||
item.customRenderer({ icon: item.icon, title: item.title, disabled: item?.disabled === true, onClick: item.onClick })
|
||||
) : (
|
||||
<IconButton onClick={item.onClick} title={item.title} disabled={item?.disabled === true}>
|
||||
<Icon>{item.icon}</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Панель инструментов редактора
|
||||
P8PEditorToolBar.propTypes = {
|
||||
items: PropTypes.arrayOf(P8P_EDITOR_TOOL_BAR_ITEM_SHAPE)
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PEditorToolBar };
|
||||
@ -1,39 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||
Общие ресурсы редакторов
|
||||
*/
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CHIP: (fullWidth = false, multiLine = false) => ({
|
||||
...(multiLine ? { height: "auto" } : {}),
|
||||
"& .MuiChip-label": {
|
||||
...(multiLine
|
||||
? {
|
||||
display: "block",
|
||||
whiteSpace: "normal"
|
||||
}
|
||||
: {}),
|
||||
...(fullWidth ? { width: "100%" } : {})
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Проверка корректности наименования элемента формы
|
||||
const isElementNameCorrect = elementName => {
|
||||
return new RegExp(/^[\w\d_]*$/).test(elementName);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { STYLES, isElementNameCorrect };
|
||||
@ -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={e => (onOk ? onOk(e) : null)}>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<div dangerouslySetInnerHTML={{ __html: hint }}></div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={e => (onOk ? onOk(e) : 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
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -9,16 +9,7 @@
|
||||
|
||||
import React, { useState, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {
|
||||
P8PTable,
|
||||
P8P_TABLE_SIZE,
|
||||
P8P_TABLE_DATA_TYPE,
|
||||
P8P_TABLE_FILTER_SHAPE,
|
||||
P8P_TABLE_MORE_HEIGHT,
|
||||
P8P_TABLE_FILTERS_HEIGHT,
|
||||
P8P_TABLE_PAGINATOR_ALIGN,
|
||||
P8P_TABLE_PAGINATOR_POSITION
|
||||
} from "./p8p_table"; //Таблица
|
||||
import { P8PTable, P8P_TABLE_SIZE, P8P_TABLE_DATA_TYPE, P8P_TABLE_FILTER_SHAPE, P8P_TABLE_MORE_HEIGHT, P8P_TABLE_FILTERS_HEIGHT } from "./p8p_table"; //Таблица
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -39,29 +30,17 @@ const P8P_DATA_GRID_MORE_HEIGHT = P8P_TABLE_MORE_HEIGHT;
|
||||
//Высота фильтров таблицы
|
||||
const P8P_DATA_GRID_FILTERS_HEIGHT = P8P_TABLE_FILTERS_HEIGHT;
|
||||
|
||||
//Размещение области страниц по вертикали
|
||||
const P8P_DATA_GRID_PAGINATOR_ALIGN = P8P_TABLE_PAGINATOR_ALIGN;
|
||||
|
||||
//Размещение области страниц по горизонтали
|
||||
const P8P_DATA_GRID_PAGINATOR_POSITION = P8P_TABLE_PAGINATOR_POSITION;
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Таблица данных
|
||||
const P8PDataGrid = ({
|
||||
style = {},
|
||||
tableStyle = {},
|
||||
columnsDef = [],
|
||||
filtersInitial,
|
||||
groups = [],
|
||||
rows = [],
|
||||
size,
|
||||
pageNumber = 1,
|
||||
pagesCount = 0,
|
||||
pagesAlign = P8P_DATA_GRID_PAGINATOR_ALIGN.RIGHT,
|
||||
pagesPosition = P8P_DATA_GRID_PAGINATOR_POSITION.BOTTOM,
|
||||
fixedHeader = false,
|
||||
fixedColumns = 0,
|
||||
morePages = false,
|
||||
@ -89,7 +68,6 @@ const P8PDataGrid = ({
|
||||
onOrderChanged,
|
||||
onFilterChanged,
|
||||
onPagesCountChanged,
|
||||
onPageChanged,
|
||||
objectsCopier
|
||||
}) => {
|
||||
//Собственное состояние - сортировки
|
||||
@ -128,9 +106,6 @@ const P8PDataGrid = ({
|
||||
if (onPagesCountChanged) onPagesCountChanged();
|
||||
};
|
||||
|
||||
//При изменении номера страницы
|
||||
const handlePageChange = ({ page }) => onPageChanged && onPageChanged({ page });
|
||||
|
||||
//При изменении списка установленных извне фильтров
|
||||
useEffect(() => {
|
||||
setFilters(filtersInitial || []);
|
||||
@ -139,18 +114,12 @@ const P8PDataGrid = ({
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PTable
|
||||
style={style}
|
||||
tableStyle={tableStyle}
|
||||
columnsDef={columnsDef}
|
||||
groups={groups}
|
||||
rows={rows}
|
||||
orders={orders}
|
||||
filters={filters}
|
||||
size={size || P8P_DATA_GRID_SIZE.MEDIUM}
|
||||
pageNumber={pageNumber}
|
||||
pagesCount={pagesCount}
|
||||
pagesAlign={pagesAlign}
|
||||
pagesPosition={pagesPosition}
|
||||
fixedHeader={fixedHeader}
|
||||
fixedColumns={fixedColumns}
|
||||
morePages={morePages}
|
||||
@ -179,24 +148,17 @@ const P8PDataGrid = ({
|
||||
onOrderChanged={handleOrderChanged}
|
||||
onFilterChanged={handleFilterChanged}
|
||||
onPagesCountChanged={handlePagesCountChanged}
|
||||
onPageChanged={handlePageChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Таблица данных
|
||||
P8PDataGrid.propTypes = {
|
||||
style: PropTypes.object,
|
||||
tableStyle: PropTypes.object,
|
||||
columnsDef: PropTypes.array,
|
||||
filtersInitial: PropTypes.arrayOf(P8P_DATA_GRID_FILTER_SHAPE),
|
||||
groups: PropTypes.array,
|
||||
rows: PropTypes.array,
|
||||
size: PropTypes.string,
|
||||
pageNumber: PropTypes.number,
|
||||
pagesCount: PropTypes.number,
|
||||
pagesAlign: PropTypes.string,
|
||||
pagesPosition: PropTypes.string,
|
||||
fixedHeader: PropTypes.bool,
|
||||
fixedColumns: PropTypes.number,
|
||||
morePages: PropTypes.bool,
|
||||
@ -224,7 +186,6 @@ P8PDataGrid.propTypes = {
|
||||
onOrderChanged: PropTypes.func,
|
||||
onFilterChanged: PropTypes.func,
|
||||
onPagesCountChanged: PropTypes.func,
|
||||
onPageChanged: PropTypes.func,
|
||||
objectsCopier: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@ -238,7 +199,5 @@ export {
|
||||
P8P_DATA_GRID_FILTER_SHAPE,
|
||||
P8P_DATA_GRID_MORE_HEIGHT,
|
||||
P8P_DATA_GRID_FILTERS_HEIGHT,
|
||||
P8P_DATA_GRID_PAGINATOR_ALIGN,
|
||||
P8P_DATA_GRID_PAGINATOR_POSITION,
|
||||
P8PDataGrid
|
||||
};
|
||||
|
||||
@ -1,136 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга
|
||||
Компонент: Диалог
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { BUTTONS } from "../../app.text"; //Общие текстовые ресурсы
|
||||
import { P8P_INPUT, P8PInput } from "./p8p_input"; //Поле ввода
|
||||
import { APP_STYLES } from "../../app.styles"; //Типовые стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Типовая ширина диалога
|
||||
const P8P_DIALOG_WIDTH = {
|
||||
XS: "xs",
|
||||
SM: "sm",
|
||||
MD: "md",
|
||||
LG: "lg",
|
||||
XL: "xl"
|
||||
};
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
SCROLL: display =>
|
||||
display === true ? { overflow: "auto", ...APP_STYLES.SCROLL } : { overflow: "hidden", display: "flex", flexDirection: "column" }
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
|
||||
//Формирование объекта вида {ключ: значение} из текущего состояния элементов ввода формы
|
||||
const buildFormValues = inputsState =>
|
||||
inputsState.reduce((res, input) => ({ ...res, [input.name]: input.value == undefined ? null : input.value }), {});
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог
|
||||
const P8PDialog = ({
|
||||
title,
|
||||
width,
|
||||
fullWidth,
|
||||
inputs,
|
||||
children,
|
||||
okDisabled = false,
|
||||
scrollContent = true,
|
||||
onOk,
|
||||
onCancel,
|
||||
onClose,
|
||||
onInputChange
|
||||
}) => {
|
||||
//Состояние элементов ввода диалога
|
||||
const [inputsState, setInputsState] = useState([]);
|
||||
|
||||
//При изменении элемента ввода
|
||||
const handleInputChange = (name, value) => {
|
||||
//Если есть функция пересчета формы - вызовем её
|
||||
const doNotChangeInputsState = onInputChange ? onInputChange(name, value, inputsState) : false;
|
||||
//И ориентируясь на то, пересчитала ли она элементы ввода обновим собственное состояние.
|
||||
//Если функция пересчета вернула "true", значит она пересчитала что-то, тогда новые настройки элементов придут через свойство inputs и будут обработаны в useEffect ниже.
|
||||
//Следовательно, и нам здесь не надо состояние выставлять, т.к. всё будет перезаписано useEffectом.
|
||||
if (!doNotChangeInputsState)
|
||||
setInputsState(pv => pv.reduce((accum, cur) => [...accum, { ...cur, value: cur.name === name ? value : cur.value }], []));
|
||||
};
|
||||
|
||||
//При нажатии на "ОК" диалога
|
||||
const handleOk = () => onOk && onOk(buildFormValues(inputsState));
|
||||
|
||||
//При нажатии на "Отмена" диалога
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//При нажатии на "Закрыть" диалога
|
||||
const handleClose = () => (onClose ? onClose() : onCancel ? onCancel() : null);
|
||||
|
||||
//При изменении полей для ввода
|
||||
useEffect(() => {
|
||||
if (inputs && Array.isArray(inputs) && inputs.length > 0) setInputsState(inputs.map(input => ({ ...input })));
|
||||
}, [inputs]);
|
||||
|
||||
//Расчет объектного представления текущих значений формы
|
||||
const formValues = buildFormValues(inputsState);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Dialog onClose={handleClose} open {...{ ...(width ? { maxWidth: width } : {}), ...(fullWidth === true ? { fullWidth: true } : {}) }}>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent sx={STYLES.SCROLL(scrollContent)}>
|
||||
{inputsState.map((input, i) => (
|
||||
<P8PInput key={i} {...input} formValues={formValues} onChange={handleInputChange} />
|
||||
))}
|
||||
|
||||
{children}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{onOk && (
|
||||
<Button disabled={okDisabled} onClick={handleOk}>
|
||||
{BUTTONS.OK}
|
||||
</Button>
|
||||
)}
|
||||
{onCancel && <Button onClick={handleCancel}>{BUTTONS.CANCEL}</Button>}
|
||||
{onClose && <Button onClick={handleClose}>{BUTTONS.CLOSE}</Button>}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог
|
||||
P8PDialog.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
width: PropTypes.oneOf(Object.values(P8P_DIALOG_WIDTH)),
|
||||
fullWidth: PropTypes.bool,
|
||||
inputs: PropTypes.arrayOf(PropTypes.shape(P8P_INPUT)),
|
||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||
okDisabled: PropTypes.bool,
|
||||
scrollContent: PropTypes.bool,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
onInputChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8PDialog, P8P_DIALOG_WIDTH };
|
||||
@ -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,
|
||||
|
||||
@ -1,229 +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": { filter: "brightness(0.92) !important" },
|
||||
"&: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: clickable => ({
|
||||
width: "99cqw",
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
}),
|
||||
VALUE_TYPOGRAPHY: clickable => ({
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//-----------------------
|
||||
//Вспомогательные функции
|
||||
//-----------------------
|
||||
|
||||
//Подбор цвета заливки
|
||||
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,
|
||||
onValueClick = null,
|
||||
onCaptionClick = 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 = e => {
|
||||
setShowHint(false);
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
//При нажатии на заголовок
|
||||
const handleCaptionClick = event => {
|
||||
//Если есть функция нажатия на заголовок
|
||||
onCaptionClick && onCaptionClick(event);
|
||||
};
|
||||
|
||||
//При нажатии на значение
|
||||
const handleValueClick = event => {
|
||||
//Если есть функция нажатия на заголовок
|
||||
onValueClick && onValueClick(event);
|
||||
};
|
||||
|
||||
//Представление текста значения индикатора
|
||||
const valueTextView = (
|
||||
<Typography variant={"h4"} sx={STYLES.VALUE_TYPOGRAPHY(onValueClick ? true : false)} onClick={handleValueClick}>
|
||||
{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
//Представление текста подписи индикатора
|
||||
const captionView = (
|
||||
<Typography
|
||||
align={"left"}
|
||||
noWrap={true}
|
||||
sx={STYLES.CAPTION_TYPOGRAPHY(onCaptionClick ? true : false)}
|
||||
title={caption}
|
||||
onClick={handleCaptionClick}
|
||||
>
|
||||
{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,
|
||||
onValueClick: PropTypes.func,
|
||||
onCaptionClick: PropTypes.func,
|
||||
backgroundColor: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_INDICATOR_VARIANT, P8P_INDICATOR_STATE, P8PIndicator };
|
||||
@ -1,135 +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, Autocomplete, TextField } from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Формат свойств поля ввода
|
||||
const P8P_INPUT = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.instanceOf(Date)]),
|
||||
label: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
dictionary: PropTypes.func,
|
||||
list: PropTypes.array,
|
||||
type: PropTypes.string,
|
||||
freeSolo: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
formValues: PropTypes.object
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Поле ввода
|
||||
const P8PInput = ({ name, value, label, onChange, dictionary, list, type, freeSolo = false, disabled = false, formValues, ...other }) => {
|
||||
//Значение и тип элемента
|
||||
const [current, setCurrent] = useState({ type: undefined, value: "" });
|
||||
|
||||
//При получении нового значения или типа из вне
|
||||
useEffect(() => setCurrent({ value, type }), [type, value]);
|
||||
|
||||
//Выбор значения из словаря
|
||||
const handleDictionaryClick = () => dictionary && dictionary(formValues, res => (res ? res.map(i => handleChangeByName(i.name, i.value)) : null));
|
||||
|
||||
//Изменение значения элемента (по событию)
|
||||
const handleChange = e => {
|
||||
setCurrent(pv => ({ ...pv, value: e.target.value }));
|
||||
if (onChange) onChange(e.target.name, e.target.value);
|
||||
};
|
||||
|
||||
//Изменение значения элемента (по имени и значению)
|
||||
const handleChangeByName = (targetName, value) => {
|
||||
if (targetName === name) setCurrent(pv => ({ ...pv, value }));
|
||||
if (onChange) onChange(targetName, value);
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box p={1}>
|
||||
<FormControl variant={"standard"} fullWidth {...other}>
|
||||
{list ? (
|
||||
freeSolo ? (
|
||||
<Autocomplete
|
||||
id={name}
|
||||
name={name}
|
||||
freeSolo
|
||||
disabled={disabled}
|
||||
inputValue={current.value ? current.value : ""}
|
||||
onChange={(event, newValue) => handleChangeByName(name, newValue)}
|
||||
onInputChange={(event, newInputValue) => handleChangeByName(name, newInputValue)}
|
||||
options={list}
|
||||
renderInput={params => <TextField {...params} label={label} name={name} variant={"standard"} />}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<InputLabel id={`${name}Lable`} shrink>
|
||||
{label}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`${name}Lable`}
|
||||
id={name}
|
||||
name={name}
|
||||
label={label}
|
||||
value={[undefined, null].includes(current.value) ? "" : current.value}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
displayEmpty
|
||||
>
|
||||
{list.map((item, i) => (
|
||||
<MenuItem key={i} value={[undefined, null].includes(item.value) ? "" : item.value}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<InputLabel {...(current.type == "date" ? { shrink: true } : {})} htmlFor={name}>
|
||||
{label}
|
||||
</InputLabel>
|
||||
<Input
|
||||
id={name}
|
||||
name={name}
|
||||
value={current.value ? current.value : ""}
|
||||
endAdornment={
|
||||
dictionary ? (
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label={`${name} select`} onClick={handleDictionaryClick} edge="end">
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}
|
||||
{...(current.type ? { type: current.type } : {})}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Поле ввода
|
||||
P8PInput.propTypes = P8P_INPUT;
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_INPUT, P8PInput };
|
||||
@ -16,7 +16,6 @@ import {
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Pagination,
|
||||
Paper,
|
||||
IconButton,
|
||||
Icon,
|
||||
@ -35,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"; //Редьюсер состояния
|
||||
|
||||
//---------
|
||||
@ -82,20 +81,6 @@ const P8P_TABLE_FILTER_SHAPE = PropTypes.shape({
|
||||
to: PropTypes.any
|
||||
});
|
||||
|
||||
//Размещение области страниц по вертикали
|
||||
const P8P_TABLE_PAGINATOR_ALIGN = {
|
||||
LEFT: "left",
|
||||
RIGHT: "right",
|
||||
CENTER: "center"
|
||||
};
|
||||
|
||||
//Размещение области страниц по горизонтали
|
||||
const P8P_TABLE_PAGINATOR_POSITION = {
|
||||
TOP: "top",
|
||||
BOTTOM: "bottom",
|
||||
BOTH: "both"
|
||||
};
|
||||
|
||||
//Высота кнопки догрузки данных
|
||||
const P8P_TABLE_MORE_HEIGHT = "49px";
|
||||
|
||||
@ -104,7 +89,9 @@ const P8P_TABLE_FILTERS_HEIGHT = "48px";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
TABLE: {},
|
||||
TABLE: {
|
||||
with: "100%"
|
||||
},
|
||||
TABLE_HEAD_STICKY: {
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
@ -151,16 +138,6 @@ const STYLES = {
|
||||
FILTER_CHIP: {
|
||||
alignItems: "center"
|
||||
},
|
||||
PAGINATION: (pagesAlign, position) => ({
|
||||
display: "flex",
|
||||
justifyContent:
|
||||
pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.LEFT
|
||||
? "flex-start"
|
||||
: pagesAlign === P8P_TABLE_PAGINATOR_ALIGN.CENTER
|
||||
? "space-around"
|
||||
: "flex-end",
|
||||
...(position === P8P_TABLE_PAGINATOR_POSITION.TOP ? { paddingBottom: "10px" } : { paddingTop: "10px" })
|
||||
}),
|
||||
MORE_BUTTON_CONTAINER: {
|
||||
with: "100%",
|
||||
textAlign: "center",
|
||||
@ -313,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,
|
||||
@ -489,18 +488,12 @@ P8PTableFiltersChips.propTypes = {
|
||||
|
||||
//Таблица
|
||||
const P8PTable = ({
|
||||
style = {},
|
||||
tableStyle = {},
|
||||
columnsDef = [],
|
||||
groups = [],
|
||||
rows = [],
|
||||
orders,
|
||||
filters,
|
||||
size,
|
||||
pageNumber = 1,
|
||||
pagesCount = 0,
|
||||
pagesAlign = P8P_TABLE_PAGINATOR_ALIGN.RIGHT,
|
||||
pagesPosition = P8P_TABLE_PAGINATOR_POSITION.BOTTOM,
|
||||
fixedHeader = false,
|
||||
fixedColumns = 0,
|
||||
morePages = false,
|
||||
@ -526,7 +519,6 @@ const P8PTable = ({
|
||||
onOrderChanged,
|
||||
onFilterChanged,
|
||||
onPagesCountChanged,
|
||||
onPageChanged,
|
||||
objectsCopier,
|
||||
containerComponent,
|
||||
containerComponentProps
|
||||
@ -664,9 +656,6 @@ const P8PTable = ({
|
||||
else setExpanded(pv => ({ ...pv, [rowIndex]: true }));
|
||||
};
|
||||
|
||||
//Отработка изменения страницы
|
||||
const handlePageChange = (e, page) => onPageChanged && onPageChanged({ page });
|
||||
|
||||
//При перезагрузке данных
|
||||
useEffect(() => {
|
||||
if (reloading) setExpanded({});
|
||||
@ -711,35 +700,12 @@ const P8PTable = ({
|
||||
));
|
||||
};
|
||||
|
||||
//Генерация области страниц
|
||||
const renderPagination = position => {
|
||||
//Признак отображения в конкретной области
|
||||
const isVisible = [
|
||||
position === P8P_TABLE_PAGINATOR_POSITION.TOP ? P8P_TABLE_PAGINATOR_POSITION.TOP : P8P_TABLE_PAGINATOR_POSITION.BOTTOM,
|
||||
P8P_TABLE_PAGINATOR_POSITION.BOTH
|
||||
].includes(pagesPosition);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<>
|
||||
{pagesCount && pagesCount > 0 && isVisible ? (
|
||||
<Pagination
|
||||
sx={STYLES.PAGINATION(pagesAlign, position)}
|
||||
count={pagesCount}
|
||||
defaultPage={1}
|
||||
page={pageNumber}
|
||||
size="medium"
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
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}
|
||||
@ -768,9 +734,8 @@ const P8PTable = ({
|
||||
valueFormatter={valueFormatter}
|
||||
/>
|
||||
) : null}
|
||||
{renderPagination(P8P_TABLE_PAGINATOR_POSITION.TOP)}
|
||||
<TableContainer component={containerComponent ? containerComponent : Paper} {...(containerComponentProps ? containerComponentProps : {})}>
|
||||
<Table stickyHeader={fixedHeader} sx={{ ...STYLES.TABLE, ...(tableStyle || {}) }} size={size || P8P_TABLE_SIZE.MEDIUM}>
|
||||
<Table stickyHeader={fixedHeader} sx={STYLES.TABLE} size={size || P8P_TABLE_SIZE.MEDIUM}>
|
||||
<TableHead sx={fixedHeader ? STYLES.TABLE_HEAD_STICKY : {}}>
|
||||
{header.displayLevels.map((level, i) => (
|
||||
<TableRow key={level}>
|
||||
@ -922,8 +887,7 @@ const P8PTable = ({
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{renderPagination(P8P_TABLE_PAGINATOR_POSITION.BOTTOM)}
|
||||
{morePages && (!pagesCount || pagesCount <= 0) ? (
|
||||
{morePages ? (
|
||||
<Container style={STYLES.MORE_BUTTON_CONTAINER}>
|
||||
<Button fullWidth onClick={handleMorePagesBtnClick} {...(morePagesBtnProps ? morePagesBtnProps : {})}>
|
||||
{morePagesBtnCaption}
|
||||
@ -936,8 +900,6 @@ const P8PTable = ({
|
||||
|
||||
//Контроль свойств - Таблица
|
||||
P8PTable.propTypes = {
|
||||
style: PropTypes.object,
|
||||
tableStyle: PropTypes.object,
|
||||
columnsDef: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
@ -970,10 +932,6 @@ P8PTable.propTypes = {
|
||||
).isRequired,
|
||||
filters: PropTypes.arrayOf(P8P_TABLE_FILTER_SHAPE).isRequired,
|
||||
size: PropTypes.string,
|
||||
pageNumber: PropTypes.number,
|
||||
pagesCount: PropTypes.number,
|
||||
pagesAlign: PropTypes.string,
|
||||
pagesPosition: PropTypes.string,
|
||||
fixedHeader: PropTypes.bool,
|
||||
fixedColumns: PropTypes.number,
|
||||
morePages: PropTypes.bool,
|
||||
@ -999,7 +957,6 @@ P8PTable.propTypes = {
|
||||
onOrderChanged: PropTypes.func,
|
||||
onFilterChanged: PropTypes.func,
|
||||
onPagesCountChanged: PropTypes.func,
|
||||
onPageChanged: PropTypes.func,
|
||||
objectsCopier: PropTypes.func.isRequired,
|
||||
containerComponent: PropTypes.oneOfType([PropTypes.elementType, PropTypes.string]),
|
||||
containerComponentProps: PropTypes.object
|
||||
@ -1009,13 +966,4 @@ P8PTable.propTypes = {
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export {
|
||||
P8P_TABLE_DATA_TYPE,
|
||||
P8P_TABLE_SIZE,
|
||||
P8P_TABLE_FILTER_SHAPE,
|
||||
P8P_TABLE_MORE_HEIGHT,
|
||||
P8P_TABLE_FILTERS_HEIGHT,
|
||||
P8P_TABLE_PAGINATOR_ALIGN,
|
||||
P8P_TABLE_PAGINATOR_POSITION,
|
||||
P8PTable
|
||||
};
|
||||
export { P8P_TABLE_DATA_TYPE, P8P_TABLE_SIZE, P8P_TABLE_FILTER_SHAPE, P8P_TABLE_MORE_HEIGHT, P8P_TABLE_FILTERS_HEIGHT, P8PTable };
|
||||
|
||||
@ -56,9 +56,6 @@ export const ApplicationContext = ({ errors, displaySizeGetter, guidGenerator, c
|
||||
//Установка списка панелей
|
||||
const setPanels = panels => dispatch({ type: APP_AT.LOAD_PANELS, payload: panels });
|
||||
|
||||
//Установка заголовка в шапке приложения
|
||||
const setAppBarTitle = useCallback(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,
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
}),
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -102,17 +102,14 @@ const getDisplaySize = () => {
|
||||
};
|
||||
|
||||
//Глубокое копирование объекта
|
||||
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj)));
|
||||
const deepCopyObject = obj => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
//Конвертация объекта в Base64 XML
|
||||
const object2XML = (obj, builderOptions) => {
|
||||
const object2Base64XML = (obj, builderOptions) => {
|
||||
const builder = new XMLBuilder(builderOptions);
|
||||
return builder.build(obj);
|
||||
return btoa(unescape(encodeURIComponent(builder.build(obj))));
|
||||
};
|
||||
|
||||
//Конвертация объекта в Base64 XML
|
||||
const object2Base64XML = (obj, builderOptions) => btoa(unescape(encodeURIComponent(object2XML(obj, builderOptions))));
|
||||
|
||||
//Конвертация XML в JSON
|
||||
const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attributeValueProcessor, useDefaultPatterns = true }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -161,43 +158,12 @@ 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 =>
|
||||
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
||||
);
|
||||
|
||||
//Формироваеие уникального идентификатора по текущему времени
|
||||
const genUID = prefix => (prefix ? `${prefix}_${Date.now()}` : `${Date.now()}`);
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
@ -206,14 +172,11 @@ export {
|
||||
hasValue,
|
||||
getDisplaySize,
|
||||
deepCopyObject,
|
||||
object2XML,
|
||||
object2Base64XML,
|
||||
xml2JSON,
|
||||
formatDateRF,
|
||||
formatDateTimeRF,
|
||||
formatDateJSONDateOnly,
|
||||
formatNumberRFCurrency,
|
||||
formatErrorMessage,
|
||||
genGUID,
|
||||
genUID
|
||||
genGUID
|
||||
};
|
||||
|
||||
@ -1,297 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Панель мониторинга: Корневая панель доски задач
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState, useCallback } from "react"; //Классы React
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd"; //Работа с drag&drop
|
||||
import { Stack, Box, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { StatusCard } from "./components/status_card.js";
|
||||
import { TaskDialog } from "./task_dialog.js"; //Компонент формы события
|
||||
import { Filter } from "./filter.js"; //Компонент фильтров
|
||||
import { useExtraData, useColorRules, useStatuses } from "./hooks/hooks.js"; //Вспомогательные хуки
|
||||
import { useTasks } from "./hooks/tasks_hooks.js"; //Хук событий
|
||||
import { useFilters } from "./hooks/filter_hooks.js"; //Вспомогательные хуки фильтра
|
||||
import { NoteDialog } from "./components/note_dialog.js"; //Диалог примечания
|
||||
import { SettingsDialog } from "./components/settings_dialog.js"; //Диалог дополнительных настроек
|
||||
import { deepCopyObject } from "../../core/utils.js"; //Вспомогательные функции
|
||||
import { COMMON_STYLES } from "./styles"; //Общие стили
|
||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Высота фильтра
|
||||
const FILTER_HEIGHT = "56px";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { width: "100%", padding: 0 },
|
||||
BOX_FILTER: { display: "flex", alignItems: "center" },
|
||||
ICON_BUTTON_SETTINGS: { marginLeft: "auto" },
|
||||
STACK_STATUSES: { maxWidth: "99vw", paddingBottom: "5px", overflowX: "auto", ...COMMON_STYLES.SCROLL },
|
||||
BOX_STATUSES: { position: "fixed", left: "8px", top: `calc(${APP_BAR_HEIGHT} + ${FILTER_HEIGHT})` }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Корневая панель доски задач
|
||||
const ClntTaskBoard = () => {
|
||||
//Состояние фильтров
|
||||
const [filters, handleFiltersChange] = useFilters();
|
||||
|
||||
//Состояние текущего загруженного фильтра
|
||||
const [filterTypeLoaded, setFilterTypeLoaded] = useState(filters.values.sType);
|
||||
|
||||
//Состояние вспомогательных диалогов
|
||||
const [dialogsState, setDialogsState] = useState({
|
||||
filterDialogIsOpen: filters.isSetByUser,
|
||||
settingsDialogIsOpen: false,
|
||||
noteDialog: { isOpen: false, callback: null },
|
||||
taskDialogIsOpen: false
|
||||
});
|
||||
|
||||
//Состояние сортировок
|
||||
const [orders, setOrders] = useState([]);
|
||||
|
||||
//Состояние дополнительных данных
|
||||
const [extraData, setExtraData, handleDocLinksLoad] = useExtraData(filters.values.sType);
|
||||
|
||||
//Состояние статусов событий
|
||||
const [statuses, statusesState, setStatuses, setStatusesState] = useStatuses(filters.values.sType);
|
||||
|
||||
//Состояние пользовательских настроек заливки событий
|
||||
const [colorRules, setColorRules] = useColorRules();
|
||||
|
||||
//Состояние событий
|
||||
const [tasks, setTasks, onDragEnd] = useTasks(filters.values, orders);
|
||||
|
||||
//Состояние доступных маршрутов события
|
||||
const [availableRoutes, setAvailableRoutes] = useState({ source: "", routes: [] });
|
||||
|
||||
//При открытии/закрытии диалога фильтра
|
||||
const handleFilterOpen = isOpen => {
|
||||
setDialogsState(pv => ({ ...pv, filterDialogIsOpen: isOpen }));
|
||||
};
|
||||
|
||||
//При открытии/закрытии диалога дополнительных настроек
|
||||
const handleSettingsOpen = () => setDialogsState(pv => ({ ...pv, settingsDialogIsOpen: !pv.settingsDialogIsOpen }));
|
||||
|
||||
//При открытии/закрытии диалога примечания
|
||||
const handleNoteOpen = (cb = null) => {
|
||||
setDialogsState(pv => ({ ...pv, noteDialog: { isOpen: !dialogsState.noteDialog.isOpen, callback: cb ? v => cb(v) : null } }));
|
||||
};
|
||||
|
||||
//При открытии/закрытии диалога события
|
||||
const handleTaskDialogOpen = () => setDialogsState(pv => ({ ...pv, taskDialogIsOpen: !dialogsState.taskDialogIsOpen }));
|
||||
|
||||
//При необходимости обновить дополнительные данные
|
||||
const handleExtraDataReload = useCallback(() => {
|
||||
setExtraData(pv => ({ ...pv, reload: true }));
|
||||
}, [setExtraData]);
|
||||
|
||||
//При необходимости обновить информацию о событиях
|
||||
const handleTasksReload = useCallback(
|
||||
(bAccountsReload = true) => {
|
||||
setTasks(pv => ({ ...pv, reload: true, accountsReload: bAccountsReload }));
|
||||
},
|
||||
[setTasks]
|
||||
);
|
||||
|
||||
//При необходимости обновить состояние статусов
|
||||
const handleStatusesStateReload = useCallback(() => {
|
||||
setStatusesState(pv => ({ ...pv, reload: true, sorted: false }));
|
||||
}, [setStatusesState]);
|
||||
|
||||
//При изменении дополнительных настроек
|
||||
const handleSettingsChange = (newSettings, statusesState) => {
|
||||
setColorRules(pv => ({ ...pv, selectedColorRule: newSettings.selectedColorRule }));
|
||||
setStatusesState({ ...statusesState, sorted: false });
|
||||
};
|
||||
|
||||
//При изменении цвета карточки статуса
|
||||
const handleSettingStatusColorChange = (changedStatus, newColor) => {
|
||||
//Считываем массив статусов
|
||||
let newStatuses = [...statuses];
|
||||
//Изменяем цвет нужного статуса
|
||||
newStatuses.find(status => status.ID === changedStatus.ID).color = newColor;
|
||||
//Обновляем состояние
|
||||
setStatuses([...newStatuses]);
|
||||
};
|
||||
|
||||
//При изменении сортировки
|
||||
const handleOrderChanged = columnName => {
|
||||
//Копируем состояние сортировки
|
||||
let newOrders = deepCopyObject(orders);
|
||||
//Находим сортируемую колонку
|
||||
const orderedColumn = newOrders.find(o => o.name == columnName);
|
||||
//Определяем направление сортировки
|
||||
const newDirection = orderedColumn?.direction == "ASC" ? "DESC" : orderedColumn?.direction == "DESC" ? null : "ASC";
|
||||
//Если сортировка отключается - очищаем информацию о сортировке
|
||||
if (newDirection == null && orderedColumn) newOrders.splice(newOrders.indexOf(orderedColumn), 1);
|
||||
//Если сортировки не было - устанавливаем
|
||||
if (newDirection != null && !orderedColumn) newOrders.push({ name: columnName, direction: newDirection });
|
||||
//Если сортировка была и не отключается - изменяем
|
||||
if (newDirection != null && orderedColumn) orderedColumn.direction = newDirection;
|
||||
//Устанавливаем новую сортировку
|
||||
setOrders(newOrders);
|
||||
};
|
||||
|
||||
//При необходимости очистки доступных маршрутов события
|
||||
const handleAvailableRoutesStateClear = () => {
|
||||
setAvailableRoutes({ source: "", routes: [] });
|
||||
};
|
||||
|
||||
//Проверка доступности карточки события
|
||||
const isCardAvailable = code => {
|
||||
return availableRoutes.source === code || availableRoutes.routes.find(r => r.SDESTINATION === code) || !availableRoutes.source ? true : false;
|
||||
};
|
||||
|
||||
//При изменении фильтра
|
||||
useEffect(() => {
|
||||
//Если изменился тип
|
||||
if (filters.loaded && filters.values.sType) {
|
||||
//Если тип события изменился
|
||||
if (filterTypeLoaded !== filters.values.sType) {
|
||||
//Обновляем информацию о дополнительных данных
|
||||
handleExtraDataReload();
|
||||
//Обновляем информацию о статусах
|
||||
handleStatusesStateReload();
|
||||
//Обновляем текущий загруженный тип события
|
||||
setFilterTypeLoaded(filters.values.sType);
|
||||
}
|
||||
//Обновляем информацию о событиях
|
||||
handleTasksReload();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filters.loaded, filters.values]);
|
||||
|
||||
//При изменении сортировки
|
||||
useEffect(() => {
|
||||
//Если есть все данные для загрузки событий
|
||||
if (filters.loaded && filters.values.sType) {
|
||||
//Обновляем информацию о событиях без обновления контрагентов
|
||||
handleTasksReload(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [orders]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{dialogsState.settingsDialogIsOpen ? (
|
||||
<SettingsDialog
|
||||
initial={{ colorRules: colorRules, statusesState: statusesState }}
|
||||
onSettingsChange={handleSettingsChange}
|
||||
onClose={handleSettingsOpen}
|
||||
/>
|
||||
) : null}
|
||||
{dialogsState.taskDialogIsOpen ? (
|
||||
<TaskDialog taskType={filters.values.sType} onTasksReload={() => handleTasksReload(true)} onClose={handleTaskDialogOpen} />
|
||||
) : null}
|
||||
<Box sx={STYLES.BOX_FILTER}>
|
||||
<Stack direction="row">
|
||||
<Box>
|
||||
<Stack direction="row" pl={1} pt={1}>
|
||||
<IconButton onClick={handleTaskDialogOpen} title={"Добавить событие"}>
|
||||
<Icon>add</Icon>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Filter
|
||||
isFilterDialogOpen={dialogsState.filterDialogIsOpen}
|
||||
filter={filters.values}
|
||||
docLinks={extraData.docLinks}
|
||||
selectedDocLink={filters.values.sDocLink ? extraData.docLinks.find(d => d.NRN === filters.values.sDocLink) : null}
|
||||
onFilterChange={handleFiltersChange}
|
||||
onDocLinksLoad={handleDocLinksLoad}
|
||||
onFilterOpen={() => handleFilterOpen(true)}
|
||||
onFilterClose={() => handleFilterOpen(false)}
|
||||
onTasksReload={handleTasksReload}
|
||||
orders={orders}
|
||||
onOrderChanged={handleOrderChanged}
|
||||
/>
|
||||
</Stack>
|
||||
<IconButton title="Настройки" onClick={handleSettingsOpen} sx={STYLES.ICON_BUTTON_SETTINGS}>
|
||||
<Icon>settings</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
{dialogsState.noteDialog.isOpen ? (
|
||||
<NoteDialog noteTypes={extraData.noteTypes} onCallback={note => dialogsState.noteDialog.callback(note)} onClose={handleNoteOpen} />
|
||||
) : null}
|
||||
{filters.loaded && filters.values.sType && extraData.dataLoaded && tasks.loaded ? (
|
||||
<DragDropContext
|
||||
onDragStart={path => {
|
||||
//Поиск кода текущего статуса задачи
|
||||
let sourceCode = statuses.find(status => status.ID == path.source.droppableId).SEVNSTAT_CODE;
|
||||
//Устанавливаем доступные маршруты события
|
||||
setAvailableRoutes({ source: sourceCode, routes: [...extraData.evRoutes.filter(route => route.SSOURCE === sourceCode)] });
|
||||
}}
|
||||
onDragEnd={path => {
|
||||
//Если есть статус назначения
|
||||
if (path.destination) {
|
||||
//Определяем мнемокод статуса назначения
|
||||
let destCode = statuses.find(status => status.ID == path.destination.droppableId).SEVNSTAT_CODE;
|
||||
//Переносим событие
|
||||
onDragEnd({ path: path, eventPoints: extraData.evPoints, openNoteDialog: handleNoteOpen, destCode: destCode });
|
||||
}
|
||||
//Очищаем информацию о доступных маршрутах события
|
||||
handleAvailableRoutesStateClear();
|
||||
}}
|
||||
>
|
||||
<Box sx={STYLES.BOX_STATUSES}>
|
||||
<Droppable droppableId="Statuses" type="droppableTask">
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<Stack direction="row" spacing={2} sx={STYLES.STACK_STATUSES}>
|
||||
{statusesState.sorted
|
||||
? statuses.map((status, index) => (
|
||||
<div key={index}>
|
||||
<Droppable
|
||||
isDropDisabled={!isCardAvailable(status.SEVNSTAT_CODE)}
|
||||
droppableId={status.ID.toString()}
|
||||
>
|
||||
{provided => (
|
||||
<div ref={provided.innerRef}>
|
||||
<StatusCard
|
||||
tasks={tasks}
|
||||
status={status}
|
||||
statusTitle={status[statusesState.attr] || status.SEVNSTAT_NAME}
|
||||
colorRules={colorRules}
|
||||
extraData={extraData}
|
||||
isCardAvailable={isCardAvailable}
|
||||
onTasksReload={handleTasksReload}
|
||||
onNoteDialogOpen={handleNoteOpen}
|
||||
onStatusColorChange={handleSettingStatusColorChange}
|
||||
placeholder={provided.placeholder}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
</Stack>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</Box>
|
||||
</DragDropContext>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { ClntTaskBoard };
|
||||
@ -1,174 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Кастомное поле ввода
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
HELPER_TEXT: { color: "red" },
|
||||
SELECT_MENU: width => {
|
||||
return { ...COMMON_STYLES.SCROLL, width: width ? width + 24 : null };
|
||||
}
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Кастомное поле ввода
|
||||
const CustomInputField = ({
|
||||
elementCode,
|
||||
elementValue,
|
||||
labelText,
|
||||
onChange,
|
||||
required = false,
|
||||
items = null,
|
||||
emptyItem = null,
|
||||
dictionary,
|
||||
menuItemRender,
|
||||
...other
|
||||
}) => {
|
||||
//Значение элемента
|
||||
const [value, setValue] = useState(elementValue);
|
||||
|
||||
//Состояние элемента HTML (для оптимизации ширины MenuItems)
|
||||
const [anchorEl, setAnchorEl] = useState();
|
||||
|
||||
//При открытии меню заливки событий
|
||||
const handleMenuOpen = e => {
|
||||
//Устанавливаем элемент меню
|
||||
setAnchorEl(e.target);
|
||||
};
|
||||
|
||||
//При получении нового значения из вне
|
||||
useEffect(() => {
|
||||
setValue(elementValue);
|
||||
}, [elementValue]);
|
||||
|
||||
//Изменение значения элемента
|
||||
const handleChange = e => {
|
||||
setValue(e.target.value);
|
||||
if (onChange) onChange(e.target.name, e.target.value);
|
||||
};
|
||||
|
||||
//Выбор значения из словаря
|
||||
const handleDictionaryClick = () => {
|
||||
dictionary ? dictionary(res => (res ? handleChange({ target: { name: elementCode, value: res } }) : null)) : null;
|
||||
};
|
||||
|
||||
//Генерация поля с выбором из словаря Парус
|
||||
const renderInput = validationError => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Input
|
||||
error={validationError}
|
||||
id={elementCode}
|
||||
name={elementCode}
|
||||
value={value}
|
||||
endAdornment={
|
||||
dictionary ? (
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
|
||||
<Icon>list</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}
|
||||
aria-describedby={`${elementCode}-helper-text`}
|
||||
label={labelText}
|
||||
onChange={handleChange}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Генерация поля с выпадающим списком
|
||||
const renderSelect = (items, anchorEl, handleMenuOpen, validationError) => {
|
||||
//Формируем общий список элементов меню
|
||||
const menuItems = emptyItem ? [emptyItem, ...items] : [...items];
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Select
|
||||
error={validationError}
|
||||
id={elementCode}
|
||||
name={elementCode}
|
||||
//!!!Пересмотреть момент. При изменении типа происходит ререндер со старым значением учетного документа:
|
||||
//1. Изменяется тип
|
||||
//2. Очищается items (список учетных документов)
|
||||
//3. Рисуется компонент со старым value и пустым items, из-за чего ошибка "You have provided an out-of-range value"
|
||||
//4. Вызывается useEffect, меняется значение value на новое (пустое значение)
|
||||
value={value}
|
||||
aria-describedby={`${elementCode}-helper-text`}
|
||||
label={labelText}
|
||||
MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU(anchorEl?.offsetWidth) } } }}
|
||||
onChange={handleChange}
|
||||
onOpen={handleMenuOpen}
|
||||
{...other}
|
||||
>
|
||||
{menuItems
|
||||
? menuItems.map((item, index) => {
|
||||
let customRender = null;
|
||||
if (menuItemRender) customRender = menuItemRender({ item: item, key: item?.key ?? index }) || null;
|
||||
return customRender ? (
|
||||
customRender
|
||||
) : (
|
||||
<MenuItem key={item?.key ?? index} value={item.id}>
|
||||
<Typography variant="inherit" noWrap title={item.caption} component="div">
|
||||
{item.caption}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
//Признак ошибки валидации
|
||||
const validationError = !value && required ? true : false;
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<FormControl fullWidth variant="standard">
|
||||
<InputLabel htmlFor={elementCode}>{labelText}</InputLabel>
|
||||
{items ? renderSelect(items, anchorEl, handleMenuOpen, validationError) : renderInput(validationError)}
|
||||
{validationError ? (
|
||||
<FormHelperText id={`${elementCode}-helper-text`} sx={STYLES.HELPER_TEXT}>
|
||||
*Обязательное поле
|
||||
</FormHelperText>
|
||||
) : null}
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Кастомное поле ввода
|
||||
CustomInputField.propTypes = {
|
||||
elementCode: PropTypes.string.isRequired,
|
||||
elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
labelText: PropTypes.string.isRequired,
|
||||
required: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(PropTypes.object),
|
||||
emptyItem: PropTypes.object,
|
||||
dictionary: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
menuItemRender: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { CustomInputField };
|
||||
@ -1,336 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Диалог фильтра отбора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Icon,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Box,
|
||||
Stack,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup
|
||||
} from "@mui/material"; //Интерфейсные компоненты
|
||||
import { CustomInputField } from "./custom_input_field"; //Кастомное поле ввода
|
||||
import { hasValue } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { EVENT_STATES } from "../layouts"; //Перечисление состояний события
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
SELECT: { width: "450px" }
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Диалог фильтра отбора
|
||||
const FilterDialog = ({ initial, onFilterChange, onFilterClose, onDocLinksLoad }) => {
|
||||
//Собственное состояние
|
||||
const [filter, setFilter] = useState(initial.filter);
|
||||
|
||||
//Состояние текущих учётных документов
|
||||
const [curDocLinks, setCurDocLinks] = useState({ loaded: true, docLinks: initial.docLinks });
|
||||
|
||||
//Вспомогательные функции открытия раздела
|
||||
const { handleCatalogTreeOpen, handleEventTypesOpen, handleAgnlistOpen, handleInsDepartmentOpen, handleCostStaffGroupsOpen } = useDictionary();
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При изменении типа события фильтра
|
||||
const handleTypeChange = callBack =>
|
||||
handleEventTypesOpen({
|
||||
sCode: filter.sType,
|
||||
callBack: res => {
|
||||
callBack(res.outParameters.eventtypecode);
|
||||
}
|
||||
});
|
||||
|
||||
//При изменении каталога фильтра
|
||||
const handleCrnChange = callBack =>
|
||||
handleCatalogTreeOpen({
|
||||
sUnitName: "ClientEvents",
|
||||
sName: filter.sCrnName,
|
||||
callBack: res => {
|
||||
callBack(res.outParameters.out_NAME);
|
||||
}
|
||||
});
|
||||
|
||||
//При изменении исполнителя фильтра
|
||||
const handleSendPersonChange = callBack =>
|
||||
handleAgnlistOpen({
|
||||
sMnemo: filter.sSendPerson,
|
||||
callBack: res => {
|
||||
callBack(res.outParameters.agnmnemo);
|
||||
}
|
||||
});
|
||||
|
||||
//При изменении подразделения фильтра
|
||||
const handleSendDivisionChange = callBack =>
|
||||
handleInsDepartmentOpen({
|
||||
sCode: filter.sSendDivision,
|
||||
callBack: res => {
|
||||
callBack(res.outParameters.out_CODE);
|
||||
}
|
||||
});
|
||||
|
||||
//При изменении группы пользователей фильтра
|
||||
const handleSendUsrGrpChange = callBack =>
|
||||
handleCostStaffGroupsOpen({
|
||||
sCode: filter.sSendUsrGrp,
|
||||
callBack: res => {
|
||||
callBack(res.outParameters.out_CODE);
|
||||
}
|
||||
});
|
||||
|
||||
//Считывание подкаталогов
|
||||
const getSubCatalogs = useCallback(async () => {
|
||||
//Считываем каталоги
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SUBCATALOGS_GET",
|
||||
args: {
|
||||
SCRN_NAME: filter.sCrnName,
|
||||
NSUBCAT: filter.bSubcatalogs ? 1 : 0
|
||||
}
|
||||
});
|
||||
//Возвращаем список каталогов
|
||||
return data.SRESULT;
|
||||
}, [executeStored, filter.sCrnName, filter.bSubcatalogs]);
|
||||
|
||||
//При закрытии диалога с изменением фильтра
|
||||
const handleDialogOk = async () => {
|
||||
//Если указано имя каталога, но не загружен список рег. номеров
|
||||
if (filter.sCrnName && !filter.sCrnRnList) {
|
||||
//Загружаем список рег. номеров каталогов
|
||||
const crns = await getSubCatalogs();
|
||||
//Устанавливаем новый фильтр
|
||||
onFilterChange({ ...filter, ...(crns ? { sCrnRnList: crns } : {}) });
|
||||
//Закрываем диалог фильтра
|
||||
onFilterClose();
|
||||
} else {
|
||||
//Устанавливаем новый фильтр
|
||||
onFilterChange(filter);
|
||||
//Закрываем диалог фильтра
|
||||
onFilterClose();
|
||||
}
|
||||
};
|
||||
|
||||
//При очистке фильтра
|
||||
const handleFilterClear = () => {
|
||||
setFilter({
|
||||
sState: EVENT_STATES[1],
|
||||
sType: "",
|
||||
sCrnName: "",
|
||||
sCrnRnList: "",
|
||||
bSubcatalogs: false,
|
||||
sSendPerson: "",
|
||||
sSendDivision: "",
|
||||
sSendUsrGrp: "",
|
||||
sDocLink: ""
|
||||
});
|
||||
};
|
||||
|
||||
//При изменении значения элемента
|
||||
const handleFilterItemChange = (item, value) => {
|
||||
//Если это изменение типа
|
||||
if (item === "sType") {
|
||||
//Указываем тип с очисткой информации об учетных документах
|
||||
setFilter(pv => ({ ...pv, [item]: value, sDocLink: "" }));
|
||||
setCurDocLinks(pv => ({ ...pv, loaded: false, docLinks: [] }));
|
||||
} else {
|
||||
//Обновляем значение поля
|
||||
setFilter(pv => ({ ...pv, [item]: value }));
|
||||
}
|
||||
};
|
||||
|
||||
//При очистке учётного документа
|
||||
const handleDocLinkClear = () => setFilter(pv => ({ ...pv, sDocLink: "" }));
|
||||
|
||||
//Обработка изменений с каталогами
|
||||
useEffect(() => {
|
||||
//Если каталог не указан, но галка подкаталогов установлена - снимаем её
|
||||
if (!filter.sCrnName && filter.bSubcatalogs) setFilter(pv => ({ ...pv, bSubcatalogs: false }));
|
||||
//Если изменился каталог и остался список рег. номеров каталогов - очищаем его
|
||||
if (filter.sCrnName !== initial.sCrnName && filter.sCrnRnList) setFilter(pv => ({ ...pv, sCrnRnList: "" }));
|
||||
//Если каталог равен изначальному
|
||||
if (filter.sCrnName === initial.sCrnName) {
|
||||
//Если признак подкаталогов равен изначальному, но список рег. номеров каталогов не соответствует - загружаем изначальный
|
||||
if (filter.bSubcatalogs === initial.bSubcatalogs && filter.sCrnRnList !== initial.sCrnRnList) {
|
||||
setFilter(pv => ({ ...pv, sCrnRnList: initial.sCrnRnList }));
|
||||
}
|
||||
//Если признак подкаталогов не равен изначальному
|
||||
if (filter.bSubcatalogs !== initial.bSubcatalogs) {
|
||||
//Если не установлен - считываем первый из списка рег. номеров изначальных каталогов
|
||||
if (!filter.bSubcatalogs) {
|
||||
setFilter(pv => ({
|
||||
...pv,
|
||||
sCrnRnList: initial.sCrnRnList.split(";")[0]
|
||||
}));
|
||||
} else {
|
||||
//Если установлен - очищаем список рег. номеров каталогов для последующей загрузки
|
||||
setFilter(pv => ({ ...pv, sCrnRnList: "" }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [filter.sCrnName, filter.sCrnRnList, filter.bSubcatalogs, initial.sCrnName, initial.sCrnRnList, initial.bSubcatalogs]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
<Dialog open onClose={onFilterClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Фильтр отбора</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={onFilterClose} sx={COMMON_STYLES.DIALOG_CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent sx={COMMON_STYLES.SCROLL}>
|
||||
<Box sx={COMMON_STYLES.BOX_WITH_LEGEND} component="fieldset">
|
||||
<legend style={COMMON_STYLES.LEGEND}>Состояние</legend>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-labelledby="sState-label"
|
||||
id="sState"
|
||||
name="sState"
|
||||
value={filter.sState}
|
||||
onChange={e => handleFilterItemChange(e.target.name, e.target.value)}
|
||||
>
|
||||
{Object.keys(EVENT_STATES).map(function (k) {
|
||||
return <FormControlLabel key={k} value={EVENT_STATES[k]} control={<Radio />} label={EVENT_STATES[k]} />;
|
||||
})}
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<CustomInputField
|
||||
elementCode="sType"
|
||||
elementValue={filter.sType}
|
||||
labelText="Тип"
|
||||
dictionary={callBack => handleTypeChange(callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<CustomInputField
|
||||
elementCode="sCrnName"
|
||||
elementValue={filter.sCrnName}
|
||||
labelText="Каталог"
|
||||
dictionary={callBack => handleCrnChange(callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
id="bSubcatalogs"
|
||||
name="bSubcatalogs"
|
||||
checked={filter.bSubcatalogs}
|
||||
disabled={filter.sCrnName ? false : true}
|
||||
onChange={e => handleFilterItemChange(e.target.name, e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Включая подкаталоги"
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<CustomInputField
|
||||
elementCode="sSendPerson"
|
||||
elementValue={filter.sSendPerson}
|
||||
labelText="Исполнитель"
|
||||
dictionary={callBack => handleSendPersonChange(callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<CustomInputField
|
||||
elementCode="sSendDivision"
|
||||
elementValue={filter.sSendDivision}
|
||||
labelText="Подразделение"
|
||||
dictionary={callBack => handleSendDivisionChange(callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<CustomInputField
|
||||
elementCode="sSendUsrGrp"
|
||||
elementValue={filter.sSendUsrGrp}
|
||||
labelText="Группа пользователей"
|
||||
dictionary={callBack => handleSendUsrGrpChange(callBack)}
|
||||
onChange={handleFilterItemChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<Stack direction="row" sx={COMMON_STYLES.STACK_DOCLINKS}>
|
||||
<CustomInputField
|
||||
elementCode="sDocLink"
|
||||
elementValue={filter.sDocLink}
|
||||
labelText="Учётный документ"
|
||||
items={[...(curDocLinks.docLinks || [])].reduce((prev, cur) => [...prev, { id: cur.NRN, caption: cur.SDESCR }], [])}
|
||||
disabled={!curDocLinks.docLinks.length ? true : false}
|
||||
onChange={handleFilterItemChange}
|
||||
sx={STYLES.SELECT}
|
||||
/>
|
||||
<IconButton title="Очистить" disabled={!filter.sDocLink} onClick={handleDocLinkClear}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
title="Обновить"
|
||||
disabled={curDocLinks.loaded}
|
||||
onClick={() => {
|
||||
//Очищаем учетный документ
|
||||
handleDocLinkClear();
|
||||
//Загружаем учетные документы типа
|
||||
onDocLinksLoad(filter.sType).then(dl => setCurDocLinks(pv => ({ ...pv, loaded: true, docLinks: dl })));
|
||||
}}
|
||||
>
|
||||
<Icon>refresh</Icon>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={COMMON_STYLES.DIALOG_ACTIONS}>
|
||||
<Button disabled={!hasValue(filter.sType)} variant="text" onClick={handleDialogOk}>
|
||||
ОК
|
||||
</Button>
|
||||
<Button variant="text" onClick={handleFilterClear}>
|
||||
Очистить
|
||||
</Button>
|
||||
<Button variant="text" onClick={onFilterClose}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалог фильтра отбора
|
||||
FilterDialog.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
onFilterClose: PropTypes.func.isRequired,
|
||||
onDocLinksLoad: PropTypes.func
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { FilterDialog };
|
||||
@ -1,99 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Диалог примечания
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, TextField } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
DIALOG_CONTENT: { paddingTop: 0, paddingBottom: 0 }
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Диалог примечания
|
||||
const NoteDialog = ({ noteTypes, onCallback, onClose }) => {
|
||||
//Собственное состояние
|
||||
const [note, setNote] = useState({ noteTypeIndex: 0, text: "" });
|
||||
|
||||
//При изменении примечания
|
||||
const handleNoteChange = value => setNote(pv => ({ ...pv, text: value }));
|
||||
|
||||
//При изменении заголовка примечания
|
||||
const handleNoteHeaderChange = (name, value) => {
|
||||
setNote(pv => ({ ...pv, noteTypeIndex: value }));
|
||||
};
|
||||
|
||||
//При закрытии диалога с изменением примечания
|
||||
const handleDialogOk = () => {
|
||||
//Передаем информацию о примечание в callback
|
||||
onCallback({ header: noteTypes[note.noteTypeIndex], text: note.text });
|
||||
onClose();
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Dialog open onClose={onClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Примечание</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={onClose} sx={COMMON_STYLES.DIALOG_CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent sx={STYLES.DIALOG_CONTENT}>
|
||||
<CustomInputField
|
||||
elementCode="noteHeader"
|
||||
elementValue={note.noteTypeIndex}
|
||||
labelText="Заголовок примечания"
|
||||
items={noteTypes.reduce((prev, cur) => [...prev, { id: prev.length, caption: cur }], [])}
|
||||
onChange={handleNoteHeaderChange}
|
||||
margin="dense"
|
||||
/>
|
||||
<TextField
|
||||
id="note"
|
||||
label="Описание"
|
||||
variant="standard"
|
||||
fullWidth
|
||||
required
|
||||
multiline
|
||||
minRows={7}
|
||||
maxRows={7}
|
||||
value={note.text}
|
||||
margin="normal"
|
||||
inputProps={{ sx: COMMON_STYLES.SCROLL }}
|
||||
onChange={e => handleNoteChange(e.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions sx={COMMON_STYLES.DIALOG_ACTIONS}>
|
||||
<Button disabled={!note.text} variant="text" onClick={handleDialogOk}>
|
||||
ОК
|
||||
</Button>
|
||||
<Button variant="text" onClick={onClose}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог примечания
|
||||
NoteDialog.propTypes = {
|
||||
noteTypes: PropTypes.array,
|
||||
onCallback: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { NoteDialog };
|
||||
@ -1,149 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Диалог дополнительных настроек
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Icon, Button, Box, Stack, Typography } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода
|
||||
import { sortAttrs, sortDest } from "../layouts.js"; //Допустимые значение поля и направления сортировки
|
||||
import { hasValue } from "../../../core/utils.js"; //Проверка наличия значения
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
SELECT: { width: "100%" }
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Диалог дополнительных настроек
|
||||
const SettingsDialog = ({ initial, onSettingsChange, onClose, ...other }) => {
|
||||
//Состояние дополнительных настроек
|
||||
const [colorRules, seColorRules] = useState(initial.colorRules);
|
||||
|
||||
//Состояние статусов
|
||||
const [statusesState, setStatusesState] = useState(initial.statusesState);
|
||||
|
||||
//Изменение поля сортировки
|
||||
const handleSortAttrChange = (item, value) => setStatusesState(pv => ({ ...pv, [item]: value }));
|
||||
|
||||
//Изменение направления сортировки
|
||||
const handleSortDestChange = newDirection => setStatusesState(pv => ({ ...pv, direction: newDirection }));
|
||||
|
||||
//При изменении правила заливки событий
|
||||
const handleColorRuleChange = (item, value) => {
|
||||
//Определяем новое правило заливки
|
||||
let newColorRule = colorRules.rules[value];
|
||||
//Обновляем в основных настройках
|
||||
seColorRules(pv => ({ ...pv, selectedColorRule: newColorRule ? newColorRule : {} }));
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div {...other}>
|
||||
<Dialog open onClose={onClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Настройки</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={onClose} sx={COMMON_STYLES.DIALOG_CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent sx={COMMON_STYLES.SCROLL}>
|
||||
<Box component="section" p={1}>
|
||||
<CustomInputField
|
||||
elementCode="clrRules"
|
||||
elementValue={hasValue(colorRules.selectedColorRule.id) && colorRules.length !== 0 ? colorRules.selectedColorRule.id : -1}
|
||||
labelText="Заливка событий*"
|
||||
items={colorRules.rules.reduce(
|
||||
(prev, cur) => [
|
||||
...prev,
|
||||
{
|
||||
id: cur.id,
|
||||
caption:
|
||||
`${cur.SDP_NAME}` +
|
||||
(cur.STYPE == "string"
|
||||
? `${cur.fromValue ? `, значение "${cur.fromValue}"` : ""}`
|
||||
: `${cur.fromValue ? `, с ${cur.fromValue}` : ""}` + `${cur.toValue ? `, по ${cur.toValue}` : ""}`) +
|
||||
`${cur.SCOLOR ? `, ${cur.SCOLOR}` : ""}`
|
||||
}
|
||||
],
|
||||
[]
|
||||
)}
|
||||
emptyItem={{ key: -1, id: -1, caption: "Нет" }}
|
||||
onChange={handleColorRuleChange}
|
||||
sx={STYLES.SELECT}
|
||||
/>
|
||||
</Box>
|
||||
<Box component="section" p={1}>
|
||||
<Stack direction="row" sx={COMMON_STYLES.STACK_DOCLINKS}>
|
||||
<CustomInputField
|
||||
elementCode="attr"
|
||||
elementValue={statusesState.attr}
|
||||
labelText="Порядок сортировки колонок"
|
||||
items={sortAttrs.reduce((prev, cur) => [...prev, { id: cur.id, caption: cur.descr }], [])}
|
||||
onChange={handleSortAttrChange}
|
||||
sx={STYLES.SELECT}
|
||||
/>
|
||||
<IconButton
|
||||
title={statusesState.direction === "asc" ? "По возрастанию" : "По убыванию"}
|
||||
onClick={() => handleSortDestChange(sortDest[sortDest.indexOf(statusesState.direction) * -1])}
|
||||
>
|
||||
<Icon>{statusesState.direction === "asc" ? "arrow_upward" : "arrow_downward"}</Icon>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Typography variant={"caption"}>
|
||||
*Поддерживаются правила заливки, базирующиеся на дополнительных свойствах типа "Строка" или "Число", из
|
||||
профиля пользователя, настроенного для раздела "События" в WEB-интерфейсе данного приложения.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions sx={COMMON_STYLES.DIALOG_ACTIONS}>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
onSettingsChange(colorRules, statusesState);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
ОК
|
||||
</Button>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
seColorRules(pv => ({ ...pv, selectedColorRule: {} }));
|
||||
setStatusesState(pv => ({ ...pv, attr: "SEVNSTAT_NAME", direction: "asc" }));
|
||||
}}
|
||||
>
|
||||
Очистить
|
||||
</Button>
|
||||
<Button variant="text" onClick={onClose}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Диалог дополнительных настроек
|
||||
SettingsDialog.propTypes = {
|
||||
initial: PropTypes.object.isRequired,
|
||||
onSettingsChange: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { SettingsDialog };
|
||||
@ -1,166 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Карточка статуса событий
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Card, CardHeader, CardContent, IconButton, Icon, Typography, Stack } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { TaskCard } from "./task_card.js"; //Компонент Карточка события
|
||||
import { StatusCardSettings } from "./status_card_settings.js"; //Компонент Диалог настройки карточки событий
|
||||
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||
import { COLORS } from "../layouts.js"; //Цвета статусов
|
||||
import { APP_BAR_HEIGHT } from "../../../components/p8p_app_workspace"; //Заголовок страницы
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Нижний отступ заголовка
|
||||
const TITLE_PADDING_BOTTOM = "16px";
|
||||
|
||||
//Высота фильтра
|
||||
const FILTER_HEIGHT = "56px";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
STATUS_BLOCK: statusColor => {
|
||||
return {
|
||||
width: "350px",
|
||||
height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 8px)`,
|
||||
backgroundColor: statusColor,
|
||||
padding: "8px"
|
||||
};
|
||||
},
|
||||
BLOCK_OPACITY: isAvailable => {
|
||||
return isAvailable ? { opacity: 1 } : { opacity: 0.5 };
|
||||
},
|
||||
CARD_HEADER_TITLE: {
|
||||
textAlign: "left",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
display: "-webkit-box",
|
||||
hyphens: "auto",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 1,
|
||||
maxWidth: "calc(300px)",
|
||||
width: "-webkit-fill-available",
|
||||
fontSize: "1.2rem",
|
||||
cursor: "default"
|
||||
},
|
||||
CARD_HEADER: { padding: 0 },
|
||||
CARD_CONTENT: {
|
||||
padding: 0,
|
||||
paddingRight: "5px",
|
||||
paddingBottom: "5px !important",
|
||||
overflowY: "auto",
|
||||
maxHeight: `calc(100vh - ${APP_BAR_HEIGHT} - ${TITLE_PADDING_BOTTOM} - ${FILTER_HEIGHT} - 55px)`,
|
||||
...APP_STYLES.SCROLL
|
||||
}
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Карточка статуса события
|
||||
const StatusCard = ({
|
||||
tasks,
|
||||
status,
|
||||
statusTitle,
|
||||
colorRules,
|
||||
extraData,
|
||||
isCardAvailable,
|
||||
onTasksReload,
|
||||
onNoteDialogOpen,
|
||||
onStatusColorChange,
|
||||
placeholder
|
||||
}) => {
|
||||
//Состояние диалога настройки
|
||||
const [statusCardSettingsOpen, setStatusCardSettingsOpen] = useState(false);
|
||||
|
||||
//Открыть/закрыть диалог настройки
|
||||
const handleStatusCardSettingsOpen = () => setStatusCardSettingsOpen(!statusCardSettingsOpen);
|
||||
|
||||
//При изменении цвета статуса
|
||||
const handleStatusColorChange = newColor => {
|
||||
onStatusColorChange(status, newColor);
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
{statusCardSettingsOpen ? (
|
||||
<StatusCardSettings
|
||||
statusColor={status.color}
|
||||
availableColors={COLORS.includes(status.color) ? COLORS : [status.color, ...COLORS]}
|
||||
onClose={handleStatusCardSettingsOpen}
|
||||
onColorChange={handleStatusColorChange}
|
||||
/>
|
||||
) : null}
|
||||
<Card
|
||||
className="statusId-card"
|
||||
sx={{
|
||||
...STYLES.STATUS_BLOCK(status.color),
|
||||
...STYLES.BLOCK_OPACITY(isCardAvailable(status.SEVNSTAT_CODE))
|
||||
}}
|
||||
>
|
||||
<CardHeader
|
||||
action={
|
||||
<IconButton aria-label="settings" onClick={handleStatusCardSettingsOpen}>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
}
|
||||
title={
|
||||
<Typography sx={STYLES.CARD_HEADER_TITLE} title={statusTitle} variant="h5">
|
||||
{statusTitle}
|
||||
</Typography>
|
||||
}
|
||||
sx={STYLES.CARD_HEADER}
|
||||
/>
|
||||
<CardContent sx={STYLES.CARD_CONTENT}>
|
||||
<Stack spacing={1}>
|
||||
{tasks.rows
|
||||
.filter(item => item.sStatus === status.SEVNSTAT_NAME)
|
||||
.map((item, index) => (
|
||||
<TaskCard
|
||||
task={item}
|
||||
index={index}
|
||||
onTasksReload={onTasksReload}
|
||||
key={item.id}
|
||||
colorRule={colorRules.selectedColorRule}
|
||||
pointSettings={extraData.evPoints.find(p => p.SEVPOINT === status.SEVNSTAT_CODE)}
|
||||
onOpenNoteDialog={onNoteDialogOpen}
|
||||
/>
|
||||
))}
|
||||
{placeholder}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Карточка статуса события
|
||||
StatusCard.propTypes = {
|
||||
tasks: PropTypes.object.isRequired,
|
||||
status: PropTypes.object.isRequired,
|
||||
statusTitle: PropTypes.string.isRequired,
|
||||
colorRules: PropTypes.object.isRequired,
|
||||
extraData: PropTypes.object.isRequired,
|
||||
isCardAvailable: PropTypes.func.isRequired,
|
||||
onTasksReload: PropTypes.func.isRequired,
|
||||
onNoteDialogOpen: PropTypes.func.isRequired,
|
||||
onStatusColorChange: PropTypes.func.isRequired,
|
||||
placeholder: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { StatusCard };
|
||||
@ -1,109 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Диалог настройки карточки статуса
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, DialogActions, Button, Box, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { CustomInputField } from "./custom_input_field.js"; //Кастомное поле ввода
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
BCKG_COLOR: backgroundColor => ({ backgroundColor: backgroundColor })
|
||||
};
|
||||
|
||||
//--------------------------------
|
||||
//Вспомогательные классы и функции
|
||||
//--------------------------------
|
||||
|
||||
//Генерация элемента меню
|
||||
const menuItemRender = ({ item, key }) => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<MenuItem key={key} value={item.id} sx={STYLES.BCKG_COLOR(item.caption)}>
|
||||
<Typography variant="inherit" noWrap title={item.caption} component="div">
|
||||
{item.caption}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Диалог настройки карточки статуса
|
||||
const StatusCardSettings = ({ statusColor, availableColors, onClose, onColorChange }) => {
|
||||
//Состояние индекса текущего цвета
|
||||
const [colorIndex, setColorIndex] = useState(availableColors.indexOf(statusColor));
|
||||
|
||||
//При закрытии диалога с применением настройки статуса
|
||||
const handleDialogOk = () => {
|
||||
//Изменяем цвет статуса
|
||||
onColorChange(availableColors[colorIndex]);
|
||||
//Закрываем диалог
|
||||
onClose();
|
||||
};
|
||||
|
||||
//При изменении значения элемента
|
||||
const handleSettingsItemChange = (item, value) => {
|
||||
setColorIndex(value);
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
<Dialog open onClose={onClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Настройки</DialogTitle>
|
||||
<IconButton aria-label="close" onClick={onClose} sx={COMMON_STYLES.DIALOG_CLOSE_BUTTON}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
<DialogContent>
|
||||
<Box component="section" p={1}>
|
||||
<CustomInputField
|
||||
elementCode="color"
|
||||
elementValue={colorIndex}
|
||||
labelText="Цвет"
|
||||
items={availableColors.reduce((prev, cur) => [...prev, { id: prev.length, caption: cur }], [])}
|
||||
onChange={handleSettingsItemChange}
|
||||
sx={STYLES.BCKG_COLOR(availableColors[colorIndex])}
|
||||
menuItemRender={menuItemRender}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={COMMON_STYLES.DIALOG_ACTIONS}>
|
||||
<Button variant="text" onClick={handleDialogOk}>
|
||||
Применить
|
||||
</Button>
|
||||
<Button variant="text" onClick={onClose}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог настройки карточки статуса
|
||||
StatusCardSettings.propTypes = {
|
||||
statusColor: PropTypes.string.isRequired,
|
||||
availableColors: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onColorChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { StatusCardSettings };
|
||||
@ -1,415 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Карточка события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Draggable } from "react-beautiful-dnd"; //Работа с drag&drop
|
||||
import { Card, CardHeader, Typography, IconButton, Icon, Box, Menu, MenuItem, CardContent, Avatar, Stack } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { TaskDialog } from "../task_dialog"; //Форма события
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений
|
||||
import { TASK_COLORS, getTaskExpiredColor, getTaskBgColorByRule, makeCardActionsArray } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
||||
import { useTasksFunctions } from "../hooks/tasks_hooks"; //Состояние вспомогательных функций событий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
MENU_ITEM_DELIMITER: { borderBottom: "1px solid lightgrey" },
|
||||
CARD: (task, colorRule) => {
|
||||
const expiredColor = getTaskExpiredColor(task);
|
||||
const backgroundColor = task.nClosed ? "#d3d3d3" : colorRule.SCOLOR ? getTaskBgColorByRule(task, colorRule) : null;
|
||||
return {
|
||||
...(expiredColor ? { borderLeft: `solid ${expiredColor}` } : {}),
|
||||
...(backgroundColor ? { backgroundColor: backgroundColor } : {})
|
||||
};
|
||||
},
|
||||
CARD_HEADER_TITLE: {
|
||||
padding: "4px",
|
||||
width: "292px",
|
||||
display: "-webkit-box",
|
||||
hyphens: "auto",
|
||||
WebkitBoxOrient: "vertical",
|
||||
WebkitLineClamp: 2,
|
||||
overflow: "hidden"
|
||||
},
|
||||
CARD_HEADER: { padding: 0, cursor: "pointer" },
|
||||
CARD_CONTENT: { padding: "4px !important" },
|
||||
CARD_CONTENT_BOX: { display: "flex", alignItems: "center", width: "100%" },
|
||||
STACK_SENDER: { alignItems: "center", marginLeft: "auto", width: "50%", justifyContent: "flex-end", paddingLeft: "10px", gap: "5px" },
|
||||
TYPOGRAPHY_TASK: { color: "text.secondary", fontSize: 14, width: "40%", overflow: "hidden" },
|
||||
TYPOGRAPHY_SENDER: { color: "text.secondary", fontSize: 14, width: "80%", overflow: "hidden", textAlign: "end" },
|
||||
ICON_COLOR: linked => {
|
||||
return { color: theme => (linked ? TASK_COLORS.LINKED : theme.palette.grey[500]), width: "10%" };
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Действия карточки события
|
||||
const CardActions = ({
|
||||
taskRn,
|
||||
taskClosed,
|
||||
menuItems,
|
||||
cardActions,
|
||||
onMethodsMenuButtonClick,
|
||||
onMethodsMenuClose,
|
||||
onTasksReload,
|
||||
pointSettings,
|
||||
onOpenNoteDialog
|
||||
}) => {
|
||||
//При нажатии на действие меню
|
||||
const handleActionClick = action => {
|
||||
//Выполняем действие
|
||||
action.func({
|
||||
nEvent: taskRn,
|
||||
onReload: action.tasksReload ? () => onTasksReload(action.needAccountsReload) : null,
|
||||
onNoteOpen: pointSettings.ADDNOTE_ONSEND ? onOpenNoteDialog : null
|
||||
});
|
||||
//Закрываем меню действий
|
||||
onMethodsMenuClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={STYLES.BOX_ROW}>
|
||||
<IconButton id={`${taskRn}_menu_button`} aria-haspopup="true" onClick={onMethodsMenuButtonClick}>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
<Menu id={`${taskRn}_menu`} anchorEl={cardActions.anchorMenuMethods} open={cardActions.openMethods} onClose={onMethodsMenuClose}>
|
||||
{menuItems.map(action =>
|
||||
action.visible ? (
|
||||
<MenuItem
|
||||
sx={action.delimiter ? STYLES.MENU_ITEM_DELIMITER : {}}
|
||||
key={`${taskRn}_${action.method}`}
|
||||
onClick={() => handleActionClick(action)}
|
||||
disabled={taskClosed === 1 && action.disableClosed ? true : false}
|
||||
>
|
||||
<Icon>{action.icon}</Icon>
|
||||
<Typography pl={1}>{action.name}</Typography>
|
||||
</MenuItem>
|
||||
) : null
|
||||
)}
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Действия карточки события
|
||||
CardActions.propTypes = {
|
||||
taskRn: PropTypes.number.isRequired,
|
||||
taskClosed: PropTypes.oneOf([0, 1]).isRequired,
|
||||
menuItems: PropTypes.array.isRequired,
|
||||
cardActions: PropTypes.object.isRequired,
|
||||
onMethodsMenuButtonClick: PropTypes.func.isRequired,
|
||||
onMethodsMenuClose: PropTypes.func.isRequired,
|
||||
onTasksReload: PropTypes.func,
|
||||
pointSettings: PropTypes.object,
|
||||
onOpenNoteDialog: PropTypes.func
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Карточка события
|
||||
const TaskCard = ({ task, index, onTasksReload, colorRule, pointSettings, onOpenNoteDialog }) => {
|
||||
//Состояние диалога события
|
||||
const [taskDialogOpen, setTaskDialogOpen] = useState(false);
|
||||
|
||||
//Состояние действий события
|
||||
const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false });
|
||||
|
||||
//Состояние списка действий меню
|
||||
const [menuItems, setMenuItems] = useState([]);
|
||||
|
||||
//Вспомогательные функции открытия раздела
|
||||
const { handleClientEventsOpen, handleClientEventsNotesOpen, handleFileLinksOpen, handleCatalogTreeOpen } = useDictionary();
|
||||
|
||||
//Состояние вспомогательных функций событий
|
||||
const { handleTaskStateChange, handleTaskSend } = useTasksFunctions();
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDocument } = useContext(ApplicationСtx);
|
||||
|
||||
//По нажатию на открытие меню действий
|
||||
const handleMethodsMenuButtonClick = useCallback(event => {
|
||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true }));
|
||||
}, []);
|
||||
|
||||
//При закрытии меню
|
||||
const handleMethodsMenuClose = useCallback(() => {
|
||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false }));
|
||||
}, []);
|
||||
|
||||
//При удалении контрагента
|
||||
const handleTaskDelete = useCallback(
|
||||
async ({ nEvent, onReload }) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DELETE",
|
||||
args: { NCLNEVENTS: nEvent }
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
onReload ? onReload() : null;
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//При возврате в предыдущую точку события
|
||||
const handleTaskReturn = useCallback(
|
||||
async ({ nEvent, onReload }) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_RETURN",
|
||||
args: { NCLNEVENTS: nEvent }
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
onReload ? onReload() : null;
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//При перемещении в каталог
|
||||
const handleTaskMove = useCallback(
|
||||
async ({ nEvent, nCrn, onReload }) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_MOVE",
|
||||
args: { NCLNEVENTS: nEvent, NCRN: nCrn }
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
onReload ? onReload() : null;
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//По нажатию действия "Направить"
|
||||
const handleTaskSendAction = useCallback(
|
||||
async ({ nEvent, onReload, onNoteOpen }) => {
|
||||
//Выполняем направление события
|
||||
handleTaskSend({ nEvent, onReload, onNoteOpen });
|
||||
},
|
||||
[handleTaskSend]
|
||||
);
|
||||
|
||||
//По нажатия действия "Редактировать"
|
||||
const handleTaskEditAction = useCallback(() => {
|
||||
setTaskDialogOpen(true);
|
||||
}, []);
|
||||
|
||||
//По нажатия действия "Редактировать в разделе"
|
||||
const handleTaskEditClientAction = useCallback(
|
||||
async ({ nEvent }) => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SELECT",
|
||||
args: {
|
||||
NCLNEVENTS: nEvent
|
||||
}
|
||||
});
|
||||
if (data.NIDENT) {
|
||||
//Открываем раздел "События" с фильтром по записи
|
||||
handleClientEventsOpen({ nIdent: data.NIDENT });
|
||||
}
|
||||
},
|
||||
[executeStored, handleClientEventsOpen]
|
||||
);
|
||||
|
||||
//По нажатию действия "Удалить"
|
||||
const handleTaskDeleteAction = useCallback(
|
||||
({ nEvent, onReload }) => {
|
||||
showMsgWarn("Удалить событие?", () => handleTaskDelete({ nEvent, onReload }));
|
||||
},
|
||||
[handleTaskDelete, showMsgWarn]
|
||||
);
|
||||
|
||||
//По нажатию действия "Выполнить возврат"
|
||||
const handleTaskReturnAction = useCallback(
|
||||
({ nEvent, onReload }) => {
|
||||
showMsgWarn("Выполнить возврат события в предыдущую точку?", () => handleTaskReturn({ nEvent, onReload }));
|
||||
},
|
||||
[handleTaskReturn, showMsgWarn]
|
||||
);
|
||||
|
||||
//По нажатию действия "Примечания"
|
||||
const handleEventNotesOpenAction = useCallback(
|
||||
({ nEvent }) => {
|
||||
handleClientEventsNotesOpen({ nPrn: nEvent });
|
||||
},
|
||||
[handleClientEventsNotesOpen]
|
||||
);
|
||||
|
||||
//По нажатию действия "Присоединенные документы"
|
||||
const handleTaskFileLinksOpenAction = useCallback(
|
||||
({ nEvent }) => {
|
||||
handleFileLinksOpen({ nPrn: nEvent, sUnitCode: "ClientEvents" });
|
||||
},
|
||||
[handleFileLinksOpen]
|
||||
);
|
||||
|
||||
//По нажатию действия "Перейти"
|
||||
const handleTaskStateChangeAction = useCallback(
|
||||
async ({ nEvent, onReload, onNoteOpen }) => {
|
||||
//Выполняем изменения статуса события
|
||||
handleTaskStateChange({ nEvent, onReload, onNoteOpen });
|
||||
},
|
||||
[handleTaskStateChange]
|
||||
);
|
||||
|
||||
//По нажатию действия "Переместить"
|
||||
const handleTaskMoveAction = useCallback(
|
||||
async ({ nEvent, onReload }) => {
|
||||
//Открываем выбор записи из раздела "Каталоги иерархии"
|
||||
handleCatalogTreeOpen({
|
||||
sUnitName: "ClientEvents",
|
||||
nRn: task.nCrn,
|
||||
callBack: res => {
|
||||
//Выполняем перемещение события
|
||||
handleTaskMove({
|
||||
nEvent,
|
||||
nCrn: res.outParameters.out_RN,
|
||||
onReload
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[handleCatalogTreeOpen, handleTaskMove, task.nCrn]
|
||||
);
|
||||
|
||||
//При изменении ссылок в меню действий (для того, чтобы ссылка на объект менялась при реальной необходимости)
|
||||
useEffect(() => {
|
||||
//Устанавливаем список меню
|
||||
setMenuItems(
|
||||
makeCardActionsArray(
|
||||
handleTaskEditAction,
|
||||
handleTaskEditClientAction,
|
||||
handleTaskDeleteAction,
|
||||
handleTaskStateChangeAction,
|
||||
handleTaskReturnAction,
|
||||
handleTaskSendAction,
|
||||
handleEventNotesOpenAction,
|
||||
handleTaskFileLinksOpenAction,
|
||||
handleTaskMoveAction
|
||||
)
|
||||
);
|
||||
}, [
|
||||
handleEventNotesOpenAction,
|
||||
handleTaskFileLinksOpenAction,
|
||||
handleTaskSendAction,
|
||||
handleTaskStateChangeAction,
|
||||
handleTaskDeleteAction,
|
||||
handleTaskEditAction,
|
||||
handleTaskEditClientAction,
|
||||
handleTaskReturnAction,
|
||||
handleTaskMoveAction
|
||||
]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
{taskDialogOpen ? (
|
||||
<TaskDialog
|
||||
taskRn={task.nRn}
|
||||
taskType={task.sType}
|
||||
editable={pointSettings.BAN_UPDATE ? false : true}
|
||||
onTasksReload={onTasksReload}
|
||||
onClose={() => {
|
||||
setTaskDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Draggable draggableId={task.id.toString()} key={task.id} index={index}>
|
||||
{provided => (
|
||||
<Card ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} sx={STYLES.CARD(task, colorRule)}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography
|
||||
className="task-info"
|
||||
sx={STYLES.CARD_HEADER_TITLE}
|
||||
lang="ru"
|
||||
onClick={() => {
|
||||
menuItems.find(action =>
|
||||
action.method === "EDIT" ? action.func(task.nRn, action.tasksReload ? onTasksReload : null) : null
|
||||
);
|
||||
}}
|
||||
title={task.sDescription}
|
||||
>
|
||||
{task.sDescription}
|
||||
</Typography>
|
||||
}
|
||||
sx={STYLES.CARD_HEADER}
|
||||
action={
|
||||
<CardActions
|
||||
taskRn={task.nRn}
|
||||
taskClosed={task.nClosed}
|
||||
menuItems={menuItems}
|
||||
cardActions={cardActions}
|
||||
onMethodsMenuButtonClick={handleMethodsMenuButtonClick}
|
||||
onMethodsMenuClose={handleMethodsMenuClose}
|
||||
onTasksReload={onTasksReload}
|
||||
pointSettings={pointSettings}
|
||||
onOpenNoteDialog={onOpenNoteDialog}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<CardContent sx={STYLES.CARD_CONTENT}>
|
||||
<Box sx={STYLES.CARD_CONTENT_BOX}>
|
||||
<IconButton
|
||||
title={task.nLinkedRn ? "Событие получено по статусной модели" : null}
|
||||
onClick={
|
||||
task.nLinkedRn ? () => pOnlineShowDocument({ unitCode: task.sLinkedUnit, document: task.nLinkedRn }) : null
|
||||
}
|
||||
sx={STYLES.ICON_COLOR(task.nLinkedRn)}
|
||||
disabled={!task.nLinkedRn}
|
||||
>
|
||||
<Icon>assignment</Icon>
|
||||
</IconButton>
|
||||
<Typography sx={STYLES.TYPOGRAPHY_TASK} noWrap title={task.name}>
|
||||
{task.name}
|
||||
</Typography>
|
||||
{task.sSender ? (
|
||||
<Stack direction="row" spacing={0.5} sx={STYLES.STACK_SENDER}>
|
||||
<Typography sx={STYLES.TYPOGRAPHY_SENDER} title={task.sSender} noWrap>
|
||||
{task.sSender}
|
||||
</Typography>
|
||||
<Avatar src={task.avatar ? `data:image/png;base64,${task.avatar}` : null} />
|
||||
</Stack>
|
||||
) : null}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</Draggable>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Карточка события
|
||||
TaskCard.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
onTasksReload: PropTypes.func,
|
||||
colorRule: PropTypes.object,
|
||||
pointSettings: PropTypes.object,
|
||||
onOpenNoteDialog: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskCard };
|
||||
@ -1,151 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Форма события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useCallback } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { TaskFormTabInfo } from "./task_form_tab_info"; //Вкладка основной информации
|
||||
import { TaskFormTabExecutor } from "./task_form_tab_executor"; //Вкладка информации об исполнителе
|
||||
import { TaskFormTabProps } from "./task_form_tab_props"; //Вкладка информации со свойствами
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { height: "625px", textAlign: "center", overflow: "hidden", display: "flex", flexDirection: "column" },
|
||||
BOX_TAB: { height: "575px", overflowY: "auto", ...COMMON_STYLES.SCROLL }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Свойства вкладки
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`
|
||||
};
|
||||
}
|
||||
|
||||
//Вкладка информации
|
||||
function CustomTabPanel(props) {
|
||||
const { children, value, index, ...other } = props;
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
sx={STYLES.BOX_TAB}
|
||||
>
|
||||
{value === index && <Box pt={1}>{children}</Box>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
//Контроль свойств - Вкладка информации
|
||||
CustomTabPanel.propTypes = {
|
||||
children: PropTypes.node,
|
||||
index: PropTypes.number.isRequired,
|
||||
value: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
//Формирование кнопки для открытия раздела
|
||||
export const getInputProps = (onClick, disabled = false, icon = "list") => {
|
||||
//Генерация содержимого
|
||||
return {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton disabled={disabled} aria-label={`select`} onClick={onClick} edge="end">
|
||||
<Icon>{icon}</Icon>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Форма события
|
||||
const TaskForm = ({ task, taskType, editable, docProps, onTaskChange, onEventNextNumbGet }) => {
|
||||
//Состояние вкладки
|
||||
const [tab, setTab] = useState(0);
|
||||
|
||||
//При изменении вкладки
|
||||
const handleTabChange = (e, newValue) => {
|
||||
setTab(newValue);
|
||||
};
|
||||
|
||||
//При изменении поля
|
||||
const handleFieldEdit = useCallback(
|
||||
e => {
|
||||
onTaskChange({
|
||||
[e.target.id]: e.target.value,
|
||||
//Связанные значения, если меняется одно, то необходимо обнулить другое
|
||||
...(e.target.id === "sClntClnperson" ? { sClntClients: "" } : {}),
|
||||
...(e.target.id === "sClntClients" ? { sClntClnperson: "" } : {})
|
||||
});
|
||||
},
|
||||
[onTaskChange]
|
||||
);
|
||||
|
||||
//При изменении доп. свойства
|
||||
const handlePropEdit = useCallback(
|
||||
(docProp, value) => {
|
||||
onTaskChange({ docProps: { ...task.docProps, [docProp]: value } });
|
||||
},
|
||||
[onTaskChange, task.docProps]
|
||||
);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
<Tabs value={tab} onChange={handleTabChange} aria-label="tabs of values">
|
||||
<Tab label="Событие" {...a11yProps(0)} />
|
||||
<Tab label="Исполнитель" {...a11yProps(1)} />
|
||||
{docProps.length > 0 ? <Tab label="Свойства" {...a11yProps(2)} /> : null}
|
||||
</Tabs>
|
||||
<CustomTabPanel value={tab} index={0}>
|
||||
<TaskFormTabInfo task={task} editable={editable} onFieldEdit={handleFieldEdit} onEventNextNumbGet={onEventNextNumbGet} />
|
||||
</CustomTabPanel>
|
||||
<CustomTabPanel value={tab} index={1}>
|
||||
<TaskFormTabExecutor task={task} onFieldEdit={handleFieldEdit} />
|
||||
</CustomTabPanel>
|
||||
{docProps.length > 0 ? (
|
||||
<CustomTabPanel value={tab} index={2}>
|
||||
<TaskFormTabProps task={task} taskType={taskType} docProps={docProps} onPropEdit={handlePropEdit} />
|
||||
</CustomTabPanel>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Форма события
|
||||
TaskForm.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
taskType: PropTypes.string.isRequired,
|
||||
editable: PropTypes.bool.isRequired,
|
||||
docProps: PropTypes.array,
|
||||
onTaskChange: PropTypes.func.isRequired,
|
||||
onEventNextNumbGet: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskForm };
|
||||
@ -1,155 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Вкладка информации об исполнителе
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
||||
import dayjs from "dayjs"; //Работа с датами
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Подключение настройки пользовательского формата даты
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Вкладка информации об исполнителе
|
||||
const TaskFormTabExecutor = ({ task, onFieldEdit }) => {
|
||||
//Вспомогательные функции открытия раздела
|
||||
const { handleClientPersonOpen } = useDictionary();
|
||||
|
||||
//При изменении сотрудника-инициатора
|
||||
const handleInitClnpersonChange = () =>
|
||||
handleClientPersonOpen({
|
||||
sCode: task.sInitClnperson,
|
||||
callBack: res => {
|
||||
onFieldEdit({
|
||||
target: {
|
||||
id: "sInitClnperson",
|
||||
value: res.outParameters.out_CODE
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ ...COMMON_STYLES.BOX_WITH_LEGEND, ...STYLES.BOX_LEFT_ALIGN }} component="fieldset">
|
||||
<legend style={COMMON_STYLES.LEGEND}>Планирование</legend>
|
||||
<TextField
|
||||
id="dPlanDate"
|
||||
label="Начало работ"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="datetime-local"
|
||||
variant="standard"
|
||||
value={task.dPlanDate ? dayjs(task.dPlanDate, "DD.MM.YYYY HH:mm").format("YYYY-MM-DD HH:mm") : ""}
|
||||
onChange={onFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
</Box>
|
||||
<Box sx={{ ...COMMON_STYLES.BOX_WITH_LEGEND, ...COMMON_STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={COMMON_STYLES.LEGEND}>Инициатор</legend>
|
||||
<TextField
|
||||
id="sInitClnperson"
|
||||
label="Сотрудник"
|
||||
value={task.sInitClnperson}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(() => handleInitClnpersonChange(), task.isUpdate)}
|
||||
></TextField>
|
||||
<TextField id="sInitUser" label="Пользователь" value={task.sInitUser} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
||||
<TextField
|
||||
id="sInitReason"
|
||||
label="Основание"
|
||||
value={task.sInitReason}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
</Box>
|
||||
<Box sx={{ ...COMMON_STYLES.BOX_WITH_LEGEND, ...COMMON_STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={COMMON_STYLES.LEGEND}>Направить</legend>
|
||||
<TextField id="sToCompany" label="Организация" value={task.sToCompany} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
||||
<TextField
|
||||
id="sToDepartment"
|
||||
label="Подразделение"
|
||||
value={task.sToDepartment}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField id="sToClnpost" label="Должность" value={task.sToClnpost} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
||||
<TextField
|
||||
id="sToClnpsdep"
|
||||
label="Штатная должность"
|
||||
value={task.sToClnpsdep}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sToClnperson"
|
||||
label="Сотрудник"
|
||||
value={task.sToClnperson}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField
|
||||
id="sToFcstaffgrp"
|
||||
label="Нештатная должность"
|
||||
value={task.sToFcstaffgrp}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
<TextField id="sToUser" label="Пользователь" value={task.sToUser} variant="standard" onChange={onFieldEdit} disabled></TextField>
|
||||
<TextField
|
||||
id="sToUsergrp"
|
||||
label="Группа пользователей"
|
||||
value={task.sToUsergrp}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка информации об исполнителе
|
||||
TaskFormTabExecutor.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
onFieldEdit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormTabExecutor };
|
||||
@ -1,192 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Вкладка основной информации
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Вкладка основной информации
|
||||
const TaskFormTabInfo = ({ task, editable, onFieldEdit, onEventNextNumbGet }) => {
|
||||
//Вспомогательные функции открытия раздела
|
||||
const { handleClientPersonOpen, handleCatalogTreeOpen, handleClientClientsOpen } = useDictionary();
|
||||
|
||||
//При изменении каталога
|
||||
const handleCrnChange = () =>
|
||||
handleCatalogTreeOpen({
|
||||
sUnitName: "ClientEvents",
|
||||
sName: task.sCrn,
|
||||
callBack: res => {
|
||||
onFieldEdit({
|
||||
target: {
|
||||
id: "sCrn",
|
||||
value: res.outParameters.out_NAME
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//При изменении клиента-сотрудника
|
||||
const handleClntClnpersonChange = () =>
|
||||
handleClientPersonOpen({
|
||||
sCode: task.sClntClnperson,
|
||||
callBack: res => {
|
||||
onFieldEdit({
|
||||
target: {
|
||||
id: "sClntClnperson",
|
||||
value: res.outParameters.out_CODE
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//При изменении клиента-организации
|
||||
const handleClntClientsChange = () =>
|
||||
handleClientClientsOpen({
|
||||
sCode: task.sClntClients,
|
||||
callBack: res => {
|
||||
onFieldEdit({
|
||||
target: {
|
||||
id: "sClntClients",
|
||||
value: res.outParameters.out_CLIENT_CODE
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={COMMON_STYLES.BOX_WITH_LEGEND} component="fieldset">
|
||||
<legend style={COMMON_STYLES.LEGEND}>Событие</legend>
|
||||
<Box sx={STYLES.BOX_FEW_COLUMNS}>
|
||||
<TextField
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
||||
id="sCrn"
|
||||
label="Каталог"
|
||||
fullWidth
|
||||
value={task.sCrn}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
InputProps={getInputProps(handleCrnChange, task.isUpdate || task.nClosed === 1)}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
/>
|
||||
<TextField
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD("225px")}
|
||||
id="sPrefix"
|
||||
label="Префикс"
|
||||
value={task.sPrefix}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD("225px")}
|
||||
id="sNumber"
|
||||
label="Номер"
|
||||
value={task.sNumber}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
required
|
||||
disabled={task.isUpdate}
|
||||
InputProps={getInputProps(onEventNextNumbGet, !task.sPrefix || task.isUpdate, "refresh")}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="sType"
|
||||
label="Тип"
|
||||
value={task.sType}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled
|
||||
required
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD("225px", !task.isUpdate)}
|
||||
id="sStatus"
|
||||
label="Статус"
|
||||
value={task.sStatus}
|
||||
variant="standard"
|
||||
disabled
|
||||
required
|
||||
onChange={onFieldEdit}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
||||
fullWidth
|
||||
id="sDescription"
|
||||
label="Описание"
|
||||
value={task.sDescription}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={!task.sType || !editable}
|
||||
required
|
||||
multiline
|
||||
minRows={7}
|
||||
maxRows={7}
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ ...COMMON_STYLES.BOX_WITH_LEGEND, ...COMMON_STYLES.BOX_SINGLE_COLUMN }} component="fieldset">
|
||||
<legend style={COMMON_STYLES.LEGEND}>Клиент</legend>
|
||||
<TextField
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
||||
id="sClntClients"
|
||||
label="Организация"
|
||||
value={task.sClntClients}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={!task.sType || task.nClosed === 1}
|
||||
InputProps={getInputProps(() => handleClntClientsChange(), !task.sType || task.nClosed === 1)}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
||||
id="sClntClnperson"
|
||||
label="Сотрудник"
|
||||
value={task.sClntClnperson}
|
||||
variant="standard"
|
||||
onChange={onFieldEdit}
|
||||
disabled={!task.sType || task.nClosed === 1}
|
||||
InputProps={getInputProps(() => handleClntClnpersonChange(), !task.sType || task.nClosed === 1)}
|
||||
></TextField>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка основной информации
|
||||
TaskFormTabInfo.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
editable: PropTypes.bool.isRequired,
|
||||
onFieldEdit: PropTypes.func.isRequired,
|
||||
onEventNextNumbGet: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormTabInfo };
|
||||
@ -1,178 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Вкладка информации со свойствами
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, TextField } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { getInputProps } from "./task_form"; //Формирование кнопки доступа к разделу
|
||||
import dayjs from "dayjs"; //Работа с датами
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
|
||||
import { DP_DEFAULT_VALUE, DP_IN_VALUE, DP_RETURN_VALUE, validationError, formatSqlDate } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||||
import { useDictionary } from "../hooks/dict_hooks"; //Состояние открытия разделов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Подключение настройки пользовательского формата даты
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Вкладка информации со свойствами
|
||||
const TaskFormTabProps = ({ task, docProps, onPropEdit }) => {
|
||||
//Вспомогательные функции открытия раздела
|
||||
const { handleExtraDictionariesOpen, handleUnitOpen } = useDictionary();
|
||||
|
||||
//Выбор из словаря или дополнительного словаря
|
||||
const handleDictOpen = async (docProp, curValue = null) => {
|
||||
//Если способ выбора - словарь
|
||||
docProp.NENTRY_TYPE === 1
|
||||
? handleUnitOpen({
|
||||
sUnitCode: docProp.SUNITCODE,
|
||||
sShowMethod: docProp.SMETHOD_CODE,
|
||||
inputParameters: docProp.NPARAM_RN ? [{ name: docProp.SPARAM_IN_CODE, value: curValue }] : null,
|
||||
callBack: res => {
|
||||
onPropEdit(docProp.SFORMATTED_ID, res.outParameters[docProp.SPARAM_OUT_CODE]);
|
||||
}
|
||||
})
|
||||
: //Если способ выбора - доп. словарь
|
||||
handleExtraDictionariesOpen({
|
||||
nRn: docProp.NEXTRA_DICT_RN,
|
||||
sParamName: DP_IN_VALUE[docProp.NFORMAT],
|
||||
paramValue: curValue,
|
||||
callBack: res => {
|
||||
onPropEdit(docProp.SFORMATTED_ID, res.outParameters[DP_RETURN_VALUE[docProp.NFORMAT]]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//Инициализация дополнительного свойства
|
||||
const initPropValue = prop => {
|
||||
//Считываем значение свойства из события
|
||||
const value = task.docProps[prop.SFORMATTED_ID];
|
||||
//Если есть значение свойства
|
||||
if (value) {
|
||||
//Строка или число
|
||||
if (prop.NFORMAT < 2) {
|
||||
return prop.NNUM_PRECISION ? String(value).replace(".", ",") : value;
|
||||
}
|
||||
//Дата
|
||||
if (prop.NFORMAT === 2) {
|
||||
//Возвращаем значение исходя из подтипа даты
|
||||
switch (prop.NDATA_SUBTYPE) {
|
||||
//Дата без времени
|
||||
case 0:
|
||||
return dayjs(value).format("YYYY-MM-DD");
|
||||
//Дата и время без секунд
|
||||
case 1:
|
||||
return dayjs(value).format("YYYY-MM-DD HH:mm");
|
||||
//Дата и время с секундами
|
||||
default:
|
||||
return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
}
|
||||
}
|
||||
//Если это ничего из вышестоящего - время
|
||||
return formatSqlDate(value);
|
||||
}
|
||||
//Если нет значения, но это изменение события
|
||||
if (task.nRn) {
|
||||
//Возвращаем пустоту
|
||||
return "";
|
||||
}
|
||||
//Если нет значения и это добавление события - возвращаем значение по умолчанию
|
||||
return prop[DP_DEFAULT_VALUE[prop.NFORMAT]];
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={COMMON_STYLES.BOX_WITH_LEGEND} component="fieldset">
|
||||
<Box sx={STYLES.BOX_FEW_COLUMNS}>
|
||||
{docProps.map((docProp, index) => {
|
||||
return docProp.BSHOW_IN_GRID ? (
|
||||
<TextField
|
||||
error={
|
||||
!validationError(
|
||||
task.docProps[docProp.SFORMATTED_ID],
|
||||
docProp.NFORMAT,
|
||||
docProp.NNUM_WIDTH,
|
||||
docProp.NNUM_PRECISION,
|
||||
docProp.NSTR_WIDTH
|
||||
)
|
||||
}
|
||||
key={index}
|
||||
sx={COMMON_STYLES.TASK_FORM_TEXT_FIELD()}
|
||||
id={docProp.SFORMATTED_ID}
|
||||
type={
|
||||
docProp.NFORMAT < 2
|
||||
? "string"
|
||||
: docProp.NFORMAT === 2
|
||||
? docProp.NDATA_SUBTYPE === 0
|
||||
? "date"
|
||||
: "datetime-local"
|
||||
: "time"
|
||||
}
|
||||
label={docProp.SNAME}
|
||||
fullWidth
|
||||
value={initPropValue(docProp)}
|
||||
variant="standard"
|
||||
onChange={e => onPropEdit(e.target.id, e.target.value)}
|
||||
inputProps={
|
||||
(docProp.NFORMAT === 2 && docProp.NDATA_SUBTYPE === 2) || (docProp.NFORMAT === 3 && docProp.NDATA_SUBTYPE === 1)
|
||||
? { step: 1 }
|
||||
: {}
|
||||
}
|
||||
InputProps={
|
||||
docProp.NENTRY_TYPE > 0
|
||||
? getInputProps(() => handleDictOpen(docProp, task.docProps[docProp.SFORMATTED_ID]))
|
||||
: null
|
||||
}
|
||||
InputLabelProps={
|
||||
docProp.NFORMAT < 2
|
||||
? {}
|
||||
: {
|
||||
shrink: true
|
||||
}
|
||||
}
|
||||
required={docProp.BREQUIRE}
|
||||
disabled={docProp.BREADONLY}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Вкладка информации со свойствами
|
||||
TaskFormTabProps.propTypes = {
|
||||
task: PropTypes.object.isRequired,
|
||||
docProps: PropTypes.array.isRequired,
|
||||
onPropEdit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskFormTabProps };
|
||||
@ -1,212 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Фильтр отбора
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Chip, Stack, Icon, IconButton, Box, Menu, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { FilterDialog } from "./components/filter_dialog.js"; //Диалог фильтра
|
||||
import { COMMON_STYLES } from "./styles"; //Общие стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
ICON_ORDERS: orders => {
|
||||
return orders.length > 0 ? { color: "#1976d2" } : {};
|
||||
},
|
||||
MENU_ORDER: {
|
||||
width: "260px"
|
||||
},
|
||||
MENU_ITEM_ORDER: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between"
|
||||
},
|
||||
FILTERS_STACK: {
|
||||
paddingBottom: "5px",
|
||||
...COMMON_STYLES.SCROLL
|
||||
},
|
||||
STACK_FILTER: { maxWidth: "99vw" }
|
||||
};
|
||||
|
||||
//--------------------------
|
||||
//Вспомогательные компоненты
|
||||
//--------------------------
|
||||
|
||||
//Элемент меню сортировок
|
||||
const SortMenuItem = ({ item, caption, orders, onOrderChanged }) => {
|
||||
//Кнопка сортировки
|
||||
const order = orders.find(order => order.name == item);
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<MenuItem sx={STYLES.MENU_ITEM_ORDER} key={item} onClick={() => onOrderChanged(item)}>
|
||||
<Typography>{caption}</Typography>
|
||||
{order ? order.direction === "ASC" ? <Icon>arrow_upward</Icon> : <Icon>arrow_downward</Icon> : null}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Элемент меню сортировок
|
||||
SortMenuItem.propTypes = {
|
||||
item: PropTypes.string.isRequired,
|
||||
caption: PropTypes.string.isRequired,
|
||||
orders: PropTypes.array,
|
||||
onOrderChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Меню сортировок
|
||||
const SortMenu = ({ menuOrders, onOrdersMenuClose, orders, onOrderChanged }) => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Menu
|
||||
id={`sort_menu`}
|
||||
anchorEl={menuOrders.anchorMenuOrders}
|
||||
open={menuOrders.openOrders}
|
||||
onClose={onOrdersMenuClose}
|
||||
MenuListProps={{ sx: STYLES.MENU_ORDER }}
|
||||
>
|
||||
<SortMenuItem item={"DCHANGE_DATE"} caption={"Дата последнего изменения"} orders={orders} onOrderChanged={onOrderChanged} />
|
||||
<SortMenuItem item={"DPLAN_DATE"} caption={"Дата начала работ"} orders={orders} onOrderChanged={onOrderChanged} />
|
||||
<SortMenuItem item={"SPREF_NUMB"} caption={"Номер"} orders={orders} onOrderChanged={onOrderChanged} />
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Меню сортировок
|
||||
SortMenu.propTypes = {
|
||||
menuOrders: PropTypes.object.isRequired,
|
||||
onOrdersMenuClose: PropTypes.func.isRequired,
|
||||
orders: PropTypes.array,
|
||||
onOrderChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Элемент фильтра
|
||||
const FilterItem = ({ caption, value, onClick }) => {
|
||||
//При нажатии на элемент
|
||||
const handleClick = () => (onClick ? onClick() : null);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Chip
|
||||
label={
|
||||
<Stack direction={"row"} alignItems={"center"}>
|
||||
<strong>{caption}</strong>
|
||||
{value ? `:\u00A0${value}` : null}
|
||||
</Stack>
|
||||
}
|
||||
variant="outlined"
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Элемент фильтра
|
||||
FilterItem.propTypes = {
|
||||
caption: PropTypes.string.isRequired,
|
||||
value: PropTypes.any,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
//---------------
|
||||
|
||||
//Фильтр отбора
|
||||
const Filter = ({
|
||||
isFilterDialogOpen,
|
||||
filter,
|
||||
docLinks,
|
||||
selectedDocLink,
|
||||
onFilterChange,
|
||||
onDocLinksLoad,
|
||||
onFilterOpen,
|
||||
onFilterClose,
|
||||
onTasksReload,
|
||||
orders,
|
||||
onOrderChanged,
|
||||
...other
|
||||
}) => {
|
||||
//Состояние меню сортировки
|
||||
const [menuOrders, setMenuOrders] = useState({ anchorMenuOrders: null, openOrders: false });
|
||||
|
||||
//При нажатии на открытие меню сортировки
|
||||
const handleOrdersMenuButtonClick = event => {
|
||||
setMenuOrders(pv => ({ ...pv, anchorMenuOrders: event.currentTarget, openOrders: true }));
|
||||
};
|
||||
|
||||
//При закрытии меню
|
||||
const handleOrdersMenuClose = () => {
|
||||
setMenuOrders(pv => ({ ...pv, anchorMenuOrders: null, openOrders: false }));
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
{isFilterDialogOpen ? (
|
||||
<FilterDialog
|
||||
initial={{ filter, docLinks }}
|
||||
// docLinks={docLinks}
|
||||
onFilterChange={onFilterChange}
|
||||
onFilterClose={onFilterClose}
|
||||
onDocLinksLoad={onDocLinksLoad}
|
||||
/>
|
||||
) : null}
|
||||
<Box {...other}>
|
||||
<Stack direction="row" spacing={1} p={1} alignItems={"center"} sx={STYLES.STACK_FILTER}>
|
||||
<IconButton title="Обновить" onClick={onTasksReload}>
|
||||
<Icon>refresh</Icon>
|
||||
</IconButton>
|
||||
<IconButton title="Сортировать" sx={STYLES.ICON_ORDERS(orders)} onClick={handleOrdersMenuButtonClick}>
|
||||
<Icon>sort</Icon>
|
||||
</IconButton>
|
||||
<IconButton title="Фильтр" onClick={onFilterOpen}>
|
||||
<Icon>filter_alt</Icon>
|
||||
</IconButton>
|
||||
<Stack direction="row" spacing={1} alignItems={"center"} sx={STYLES.FILTERS_STACK}>
|
||||
{filter.sState ? <FilterItem caption={"Состояние"} value={filter.sState} onClick={onFilterOpen} /> : null}
|
||||
{filter.sType ? <FilterItem caption={"Тип"} value={filter.sType} onClick={onFilterOpen} /> : null}
|
||||
{filter.sCrnName ? <FilterItem caption={"Каталог"} value={filter.sCrnName} onClick={onFilterOpen} /> : null}
|
||||
{filter.bSubcatalogs ? <FilterItem caption={"Включая подкаталоги"} onClick={onFilterOpen} /> : null}
|
||||
{filter.sSendPerson ? <FilterItem caption={"Исполнитель"} value={filter.sSendPerson} onClick={onFilterOpen} /> : null}
|
||||
{filter.sSendDivision ? <FilterItem caption={"Подразделение"} value={filter.sSendDivision} onClick={onFilterOpen} /> : null}
|
||||
{filter.sSendUsrGrp ? (
|
||||
<FilterItem caption={"Группа пользователей"} value={filter.sSendUsrGrp} onClick={onFilterOpen} />
|
||||
) : null}
|
||||
{filter.sDocLink && selectedDocLink ? (
|
||||
<FilterItem caption={"Учётный документ"} value={selectedDocLink.descr} onClick={onFilterOpen} />
|
||||
) : null}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<SortMenu menuOrders={menuOrders} onOrdersMenuClose={handleOrdersMenuClose} orders={orders} onOrderChanged={onOrderChanged} />
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Фильтр отбора
|
||||
Filter.propTypes = {
|
||||
isFilterDialogOpen: PropTypes.bool.isRequired,
|
||||
filter: PropTypes.object.isRequired,
|
||||
docLinks: PropTypes.arrayOf(PropTypes.object),
|
||||
selectedDocLink: PropTypes.object,
|
||||
onFilterChange: PropTypes.func.isRequired,
|
||||
onDocLinksLoad: PropTypes.func,
|
||||
onFilterOpen: PropTypes.func.isRequired,
|
||||
onFilterClose: PropTypes.func.isRequired,
|
||||
onTasksReload: PropTypes.func.isRequired,
|
||||
orders: PropTypes.array,
|
||||
onOrderChanged: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//--------------------
|
||||
//Интерфейс компонента
|
||||
//--------------------
|
||||
|
||||
export { Filter };
|
||||
@ -1,256 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Пользовательские хуки: Хуки открытия разделов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useContext, useCallback } from "react"; //Классы React
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Состояние открытия разделов
|
||||
const useDictionary = () => {
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Отображение раздела "Сотрудники"
|
||||
const handleClientPersonOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientPersons",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Клиенты"
|
||||
const handleClientClientsOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientClients",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_CLIENT_CODE", value: prms.sCode }],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Каталоги"
|
||||
const handleCatalogTreeOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "CatalogTree",
|
||||
showMethod: "main",
|
||||
inputParameters: [
|
||||
{ name: "in_DOCNAME", value: prms.sUnitName },
|
||||
{ name: "in_NAME", value: prms.sName },
|
||||
{ name: "in_RN", value: prms.nRn }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Типы событий"
|
||||
const handleEventTypesOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientEventTypes",
|
||||
showMethod: "dictionary",
|
||||
inputParameters: [{ name: "pos_eventtypecode", value: prms.sCode }],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Контрагенты"
|
||||
const handleAgnlistOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "AGNLIST",
|
||||
showMethod: "agents",
|
||||
inputParameters: [{ name: "pos_agnmnemo", value: prms.sMnemo }],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Штатные подразделения"
|
||||
const handleInsDepartmentOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "INS_DEPARTMENT",
|
||||
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Нештатные структуры"
|
||||
const handleCostStaffGroupsOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "CostStaffGroups",
|
||||
inputParameters: [{ name: "in_CODE", value: prms.sCode }],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Дополнительные словари"
|
||||
const handleExtraDictionariesOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ExtraDictionaries",
|
||||
showMethod: "values",
|
||||
inputParameters: [
|
||||
{ name: "pos_rn", value: prms.nRn },
|
||||
{ name: prms.sParamName, value: prms.paramValue }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Маршруты событий (исполнители в точках)"
|
||||
const handleEventRoutesPointExecutersOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "EventRoutesPointExecuters",
|
||||
showMethod: "executers",
|
||||
inputParameters: prms.inputParameters,
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "События"
|
||||
const handleClientEventsOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientEvents",
|
||||
inputParameters: [{ name: "in_Ident", value: prms.nIdent }]
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "События (примечания)"
|
||||
const handleClientEventsNotesOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "ClientEventsNotes",
|
||||
showMethod: "main",
|
||||
inputParameters: [{ name: "in_PRN", value: prms.nPrn }]
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Присоединенные документы"
|
||||
const handleFileLinksOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "FileLinks",
|
||||
showMethod: "main_link",
|
||||
inputParameters: [
|
||||
{ name: "in_PRN", value: prms.nPrn },
|
||||
{ name: "in_UNITCODE", value: prms.sUnitCode }
|
||||
]
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение раздела "Маршруты событий (точки перехода)"
|
||||
const handleEventRoutesPointsPassessOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "EventRoutesPointsPasses",
|
||||
showMethod: "main_passes",
|
||||
inputParameters: [
|
||||
{ name: "in_ENVTYPE_CODE", value: prms.sEventType },
|
||||
{ name: "in_ENVSTAT_CODE", value: prms.sEventStatus },
|
||||
{ name: "in_POINT", value: prms.nPoint }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Универсальное отображение раздела
|
||||
const handleUnitOpen = useCallback(
|
||||
async prms => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: prms.sUnitCode,
|
||||
showMethod: prms.sShowMethod,
|
||||
inputParameters: prms.inputParameters,
|
||||
callBack: res => {
|
||||
res.success ? prms.callBack(res) : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
return {
|
||||
handleClientPersonOpen,
|
||||
handleClientClientsOpen,
|
||||
handleCatalogTreeOpen,
|
||||
handleEventTypesOpen,
|
||||
handleAgnlistOpen,
|
||||
handleInsDepartmentOpen,
|
||||
handleCostStaffGroupsOpen,
|
||||
handleExtraDictionariesOpen,
|
||||
handleEventRoutesPointExecutersOpen,
|
||||
handleClientEventsOpen,
|
||||
handleClientEventsNotesOpen,
|
||||
handleFileLinksOpen,
|
||||
handleEventRoutesPointsPassessOpen,
|
||||
handleUnitOpen
|
||||
};
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useDictionary };
|
||||
@ -1,122 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Пользовательские хуки: Хуки фильтра
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useEffect, useCallback } from "react"; //Классы React
|
||||
import { EVENT_STATES } from "../layouts"; //Перечисление состояний события
|
||||
import { getLocalStorageValue } from "../layouts"; //Вспомогательные функции
|
||||
|
||||
//--------------------------
|
||||
//Вспомогательные компоненты
|
||||
//--------------------------
|
||||
|
||||
//Проверка возможности загрузки данных фильтра из локального хранилища
|
||||
const isLocalStorageExists = () => {
|
||||
return getLocalStorageValue("sType");
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Хук фильтра
|
||||
//const useFilters = filterOpen => {
|
||||
const useFilters = () => {
|
||||
//Состояние фильтра
|
||||
const [filters, setFilters] = useState({
|
||||
loaded: false,
|
||||
isSetByUser: !isLocalStorageExists(),
|
||||
values: {
|
||||
sState: EVENT_STATES[1],
|
||||
sType: "",
|
||||
sCrnName: "",
|
||||
sCrnRnList: "",
|
||||
bSubcatalogs: false,
|
||||
sSendPerson: "",
|
||||
sSendDivision: "",
|
||||
sSendUsrGrp: "",
|
||||
sDocLink: ""
|
||||
}
|
||||
});
|
||||
|
||||
//Установить значение фильтра
|
||||
const setFilterValues = useCallback((values, isSetByUser = true) => {
|
||||
setFilters({ loaded: true, isSetByUser: isSetByUser, values: values });
|
||||
}, []);
|
||||
|
||||
//Загрузка значений фильтра из локального хранилища браузера
|
||||
const loadLocalStorageValues = useCallback(async () => {
|
||||
//Загружаем значения по умолчанию
|
||||
let values = { ...filters.values };
|
||||
//Обходим ключи объекта значений
|
||||
for (let key in values) {
|
||||
//Заполняем значениями из хранилища
|
||||
switch (key) {
|
||||
//Локальное хранилище не хранит булево, форматируем строку в булево
|
||||
case "bSubcatalogs":
|
||||
values[key] = getLocalStorageValue(key) === "true";
|
||||
break;
|
||||
//Не переносим информацию о связанных записях
|
||||
case "sDocLink":
|
||||
break;
|
||||
//Переносим все остальные значения
|
||||
default:
|
||||
values[key] = getLocalStorageValue(key, "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Устанавливаем значения фильтра
|
||||
setFilterValues(values, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//При изменении значений фильтра
|
||||
const handleFiltersChange = useCallback(
|
||||
filters => {
|
||||
setFilterValues(filters);
|
||||
},
|
||||
[setFilterValues]
|
||||
);
|
||||
|
||||
//Сохранение при закрытии панели
|
||||
useEffect(() => {
|
||||
//Обработка события закрытия
|
||||
const onBeforeUnload = () => {
|
||||
//Обходим ключи фильтра
|
||||
for (let key in filters.values) {
|
||||
//Если это не связи - сохраняем значение в хранилище
|
||||
key !== "sDocLink" ? localStorage.setItem(key, filters.values[key] ? filters.values[key] : "") : null;
|
||||
}
|
||||
};
|
||||
//Если данные были загружены и произошли изменения
|
||||
if (filters.loaded && filters.isSetByUser) {
|
||||
//Вешаем обработчик события закрытия
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
}
|
||||
//Очищаем при размонтировании
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
};
|
||||
}, [filters.loaded, filters.isSetByUser, filters.values]);
|
||||
|
||||
//При подключении к странице
|
||||
useEffect(() => {
|
||||
//Если требуется загрузить фильтр из локального хранилища
|
||||
if (!filters.loaded && !filters.isSetByUser) {
|
||||
loadLocalStorageValues();
|
||||
}
|
||||
}, [filters.isSetByUser, filters.loaded, loadLocalStorageValues]);
|
||||
|
||||
return [filters, handleFiltersChange];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useFilters };
|
||||
@ -1,243 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Пользовательские хуки: Хуки основных данных
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { getRandomColor, getLocalStorageValue } from "../layouts"; //Вспомогательные функции
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Хук дополнительных данных
|
||||
const useExtraData = filtersType => {
|
||||
//Состояние дополнительных данных
|
||||
const [extraData, setExtraData] = useState({
|
||||
dataLoaded: false,
|
||||
reload: false,
|
||||
typeLoaded: "",
|
||||
evRoutes: [],
|
||||
evPoints: [],
|
||||
noteTypes: [],
|
||||
docLinks: []
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Считывание учётных документов
|
||||
const handleDocLinksLoad = useCallback(
|
||||
async (type = filtersType) => {
|
||||
//Считываем данные
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DOCLINKS_GET",
|
||||
args: {
|
||||
SEVNTYPE_CODE: type
|
||||
},
|
||||
isArray: name => name === "XDOCLINKS",
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Возвращаем учётные документы
|
||||
return [...(data?.XDOCLINKS || [])];
|
||||
},
|
||||
[executeStored, filtersType]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
//Загрузка дополнительных данных
|
||||
const loadExtraData = async () => {
|
||||
//Считываем данные
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_GET_INFO_BY_CODE",
|
||||
args: {
|
||||
SEVNTYPE_CODE: filtersType
|
||||
},
|
||||
isArray: name => ["XEVROUTES", "XEVPOINTS", "XNOTETYPES"].includes(name),
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Форматируем типы примечаний под нужный формат
|
||||
let noteTypes = [...(data?.XNOTETYPES || [])].reduce((prev, cur) => [...prev, cur.SNAME], []);
|
||||
//Считываем учётные документы
|
||||
let docLinks = await handleDocLinksLoad(filtersType);
|
||||
//Обновляем дополнительные данные
|
||||
setExtraData({
|
||||
dataLoaded: true,
|
||||
reload: false,
|
||||
typeLoaded: filtersType,
|
||||
evRoutes: [...(data?.XEVROUTES || [])],
|
||||
evPoints: [...(data?.XEVPOINTS || [])],
|
||||
noteTypes: [...noteTypes],
|
||||
docLinks: [...docLinks]
|
||||
});
|
||||
};
|
||||
|
||||
//Если указан тип событий и необходимо обновить
|
||||
if (extraData.reload && filtersType) {
|
||||
//Загружаем дополнительные данные
|
||||
if (!extraData.typeLoaded || filtersType !== extraData.typeLoaded) {
|
||||
loadExtraData();
|
||||
}
|
||||
}
|
||||
}, [executeStored, extraData.reload, extraData.typeLoaded, filtersType, handleDocLinksLoad]);
|
||||
|
||||
return [extraData, setExtraData, handleDocLinksLoad];
|
||||
};
|
||||
|
||||
//Хук заливок пользовательских настроек
|
||||
const useColorRules = () => {
|
||||
//Собственное состояние
|
||||
const [colorRules, setColorRules] = useState({
|
||||
loaded: false,
|
||||
rules: [],
|
||||
selectedColorRule: JSON.parse(getLocalStorageValue("settingsColorRule")) || {}
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости загрузки заливок
|
||||
useEffect(() => {
|
||||
//Считывание пользовательских настроек
|
||||
let getColorRules = async () => {
|
||||
//Считываем данные
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_DP_RULES_GET",
|
||||
isArray: name => name === "XRULES",
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Формируем массив правил заливки пользовательских настроек
|
||||
let newColorRules = [...(data.XRULES || [])].reduce(
|
||||
(prev, cur) => [
|
||||
...prev,
|
||||
{
|
||||
id: prev.length,
|
||||
SFIELD: cur.SFIELD,
|
||||
SDP_NAME: cur.SDP_NAME,
|
||||
SCOLOR: cur.SCOLOR,
|
||||
STYPE: cur.STYPE,
|
||||
fromValue: cur.NFROM ?? cur.SFROM ?? cur.DFROM,
|
||||
toValue: cur.NTO ?? cur.STO ?? cur.DTO
|
||||
}
|
||||
],
|
||||
[]
|
||||
);
|
||||
//Устанавливаем заливки пользовательских настроек
|
||||
setColorRules(pv => ({ ...pv, loaded: true, rules: [...newColorRules] }));
|
||||
};
|
||||
|
||||
if (!colorRules.loaded) getColorRules();
|
||||
}, [colorRules.loaded, executeStored]);
|
||||
|
||||
//Сохранение при закрытии панели
|
||||
useEffect(() => {
|
||||
//Обработка события закрытия
|
||||
const onBeforeUnload = () => {
|
||||
localStorage.setItem("settingsColorRule", JSON.stringify(colorRules.selectedColorRule));
|
||||
};
|
||||
//Вешаем обработчик события закрытия
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
//Очищаем при размонтировании
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
};
|
||||
}, [colorRules.selectedColorRule]);
|
||||
|
||||
return [colorRules, setColorRules];
|
||||
};
|
||||
|
||||
//Хук статусов событий
|
||||
const useStatuses = filterType => {
|
||||
//Собственное состояние статусов
|
||||
const [statuses, setStatuses] = useState([]);
|
||||
|
||||
//Состояние статусов
|
||||
const [statusesState, setStatusesState] = useState({
|
||||
sorted: false,
|
||||
reload: true,
|
||||
attr: getLocalStorageValue("statusesSortAttr", "SEVNSTAT_NAME"),
|
||||
direction: getLocalStorageValue("statusesSortDirection", "asc")
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости сортировки статусов
|
||||
useEffect(() => {
|
||||
//Сортируем статусы
|
||||
const sortStatuses = unsortedStatuses => {
|
||||
//Инициализируем поле сортировки и порядок сортировки
|
||||
const attr = statusesState.attr;
|
||||
const direction = statusesState.direction;
|
||||
//Сортируем
|
||||
let sortedStatuses = unsortedStatuses.sort((a, b) =>
|
||||
direction === "asc" ? a[attr].localeCompare(b[attr]) : b[attr].localeCompare(a[attr])
|
||||
);
|
||||
//Возвращаем
|
||||
return sortedStatuses;
|
||||
};
|
||||
//Загружаем и сортируем статусы
|
||||
const loadAndSortStatuses = async filterType => {
|
||||
//Инициализируем статусы
|
||||
let newStatuses = [];
|
||||
//Если требуется перезагрузка
|
||||
if (statusesState.reload) {
|
||||
const loadedStatuses = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVNSTATS_LOAD",
|
||||
args: {
|
||||
SCLNEVNTYPES: filterType
|
||||
},
|
||||
isArray: name => name === "XSTATUS",
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Загружаем статусы и инициализируем цвета
|
||||
newStatuses = [...(loadedStatuses?.XSTATUS || [])].reduce(
|
||||
(prev, cur) => [...prev, { ...cur, color: getRandomColor(prev.length + 1) }],
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
//Загружаем из состояния
|
||||
newStatuses = [...statuses];
|
||||
}
|
||||
//Сортируем, если требуется
|
||||
newStatuses = !statusesState.sorted ? sortStatuses(newStatuses) : newStatuses;
|
||||
//Обновляем состояние статусов
|
||||
setStatuses([...newStatuses]);
|
||||
//Обновляем информацию о состоянии статусов
|
||||
setStatusesState(pv => ({ ...pv, sorted: true, reload: false }));
|
||||
};
|
||||
//При необходимости изменения сортировки
|
||||
if (filterType && (statusesState.reload || !statusesState.sorted)) {
|
||||
//Считываем старые статусы или загружаем новые
|
||||
loadAndSortStatuses(filterType);
|
||||
}
|
||||
}, [executeStored, filterType, statuses, statusesState.attr, statusesState.direction, statusesState.reload, statusesState.sorted]);
|
||||
|
||||
//Сохранение при закрытии панели
|
||||
useEffect(() => {
|
||||
//Обработка события закрытия
|
||||
const onBeforeUnload = () => {
|
||||
localStorage.setItem("statusesSortAttr", statusesState.attr);
|
||||
localStorage.setItem("statusesSortDirection", statusesState.direction);
|
||||
};
|
||||
//Вешаем обработчик события закрытия
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
//Очищаем при размонтировании
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
};
|
||||
}, [statusesState.attr, statusesState.direction]);
|
||||
|
||||
return [statuses, statusesState, setStatuses, setStatusesState];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useExtraData, useColorRules, useStatuses };
|
||||
@ -1,193 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Пользовательские хуки: Хуки диалога события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Хук для события
|
||||
const useClientEvent = (taskRn, taskType = "") => {
|
||||
//Собственное состояние
|
||||
const [task, setTask] = useState({
|
||||
init: true,
|
||||
nRn: taskRn,
|
||||
sCrn: "",
|
||||
sPrefix: "",
|
||||
sNumber: "",
|
||||
sType: taskType,
|
||||
sStatus: "",
|
||||
sDescription: "",
|
||||
sClntClients: "",
|
||||
sClntClnperson: "",
|
||||
dStartDate: "",
|
||||
sInitClnperson: "",
|
||||
sInitUser: "",
|
||||
sInitReason: "",
|
||||
sToCompany: "",
|
||||
sToDepartment: "",
|
||||
sToClnpost: "",
|
||||
sToClnpsdep: "",
|
||||
sToClnperson: "",
|
||||
sToFcstaffgrp: "",
|
||||
sToUser: "",
|
||||
sToUsergrp: "",
|
||||
sCurrentUser: "",
|
||||
isUpdate: false,
|
||||
insertDisabled: true,
|
||||
updateDisabled: true,
|
||||
docProps: {}
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При инициализации события
|
||||
useEffect(() => {
|
||||
//Если это инициализация
|
||||
if (task.init) {
|
||||
//Если указан рег. номер события
|
||||
if (taskRn) {
|
||||
//Считывание параметров события
|
||||
const readEvent = async () => {
|
||||
//Считываем информацию о событии по рег. номеру
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_GET",
|
||||
args: {
|
||||
NCLNEVENTS: task.nRn
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Фильтруем доп. свойства
|
||||
let docProps = Object.keys(data.XEVENT)
|
||||
.filter(key => key.includes("DP_"))
|
||||
.reduce((prev, key) => ({ ...prev, [key]: data.XEVENT[key] }), {});
|
||||
//Устанавливаем информацию о событии
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
sCrn: data.XEVENT.SCRN,
|
||||
nClosed: data.XEVENT.NCLOSED,
|
||||
sPrefix: data.XEVENT.SPREF,
|
||||
sNumber: data.XEVENT.SNUMB,
|
||||
sType: data.XEVENT.STYPE,
|
||||
sStatus: data.XEVENT.SSTATUS,
|
||||
sDescription: data.XEVENT.SDESCRIPTION,
|
||||
sClntClients: data.XEVENT.SCLIENT_CLIENT,
|
||||
sClntClnperson: data.XEVENT.SCLIENT_PERSON,
|
||||
dPlanDate: data.XEVENT.SPLAN_DATE,
|
||||
sInitClnperson: data.XEVENT.SINIT_PERSON,
|
||||
sInitUser: data.XEVENT.SINIT_AUTHID,
|
||||
sInitReason: data.XEVENT.SREASON,
|
||||
sToCompany: data.XEVENT.SSEND_CLIENT,
|
||||
sToDepartment: data.XEVENT.SSEND_DIVISION,
|
||||
sToClnpost: data.XEVENT.SSEND_POST,
|
||||
sToClnpsdep: data.XEVENT.SSEND_PERFORM,
|
||||
sToClnperson: data.XEVENT.SSEND_PERSON,
|
||||
sToFcstaffgrp: data.XEVENT.SSEND_STAFFGRP,
|
||||
sToUser: data.XEVENT.SSEND_USER_NAME,
|
||||
sToUsergrp: data.XEVENT.SSEND_USER_GROUP,
|
||||
sCurrentUser: data.XEVENT.SINIT_AUTHID,
|
||||
isUpdate: true,
|
||||
init: false,
|
||||
docProps: docProps
|
||||
}));
|
||||
};
|
||||
//Инициализация параметров события
|
||||
readEvent();
|
||||
} else {
|
||||
//Считывание изначальных параметров события
|
||||
const initEvent = async () => {
|
||||
//Инициализируем параметры события
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INIT",
|
||||
args: {
|
||||
SEVENT_TYPE: task.sType
|
||||
}
|
||||
});
|
||||
//Если есть данные
|
||||
if (data) {
|
||||
//Устанавливаем данные по событию
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
sPrefix: data.SPREF,
|
||||
sNumber: data.SNUMB,
|
||||
sStatus: data.SSTATUS,
|
||||
sCurrentUser: data.SINIT_AUTHNAME,
|
||||
sInitClnperson: data.SINIT_PERSON,
|
||||
sInitUser: !data.SINIT_PERSON ? data.SINIT_AUTHNAME : "",
|
||||
init: false
|
||||
}));
|
||||
}
|
||||
};
|
||||
//Инициализация изначальных параметров события
|
||||
initEvent();
|
||||
}
|
||||
}
|
||||
if (!task.init) {
|
||||
setTask(pv => ({ ...pv, sInitUser: !task.sInitClnperson ? task.sCurrentUser : "" }));
|
||||
}
|
||||
}, [executeStored, task.init, task.nRn, task.sType, task.sCurrentUser, task.sInitClnperson, taskRn]);
|
||||
|
||||
//Проверка доступности действия
|
||||
useEffect(() => {
|
||||
setTask(pv => ({
|
||||
...pv,
|
||||
insertDisabled:
|
||||
!task.sCrn ||
|
||||
!task.sPrefix ||
|
||||
!task.sNumber ||
|
||||
!task.sType ||
|
||||
!task.sStatus ||
|
||||
!task.sDescription ||
|
||||
(!task.sInitClnperson && !task.sInitUser),
|
||||
updateDisabled: !task.sDescription
|
||||
}));
|
||||
}, [task.sCrn, task.sDescription, task.sInitClnperson, task.sInitUser, task.sNumber, task.sPrefix, task.sStatus, task.sType]);
|
||||
|
||||
return [task, setTask];
|
||||
};
|
||||
|
||||
//Хук для получения свойств раздела "События"
|
||||
const useDocsProps = taskType => {
|
||||
//Собственное состояние
|
||||
const [docProps, setDocsProps] = useState({ loaded: false, props: [] });
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
useEffect(() => {
|
||||
//Загрузка доп. свойств
|
||||
let getDocsProps = async () => {
|
||||
//Считываема доп. свойства по типу события
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_PROPS_GET",
|
||||
args: { SEVNTYPE_CODE: taskType },
|
||||
isArray: name => name === "XPROPS",
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Устанавливаем доп. свойства
|
||||
setDocsProps({ loaded: true, props: [...(data?.XPROPS || [])] });
|
||||
};
|
||||
//Если доп. свойства не загружены
|
||||
if (!docProps.loaded) {
|
||||
//Загружаем доп. свойства
|
||||
getDocsProps();
|
||||
}
|
||||
}, [docProps.loaded, executeStored, taskType]);
|
||||
|
||||
return [docProps];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useClientEvent, useDocsProps };
|
||||
@ -1,453 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Пользовательские хуки: Хуки событий
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useContext, useEffect, useCallback } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { object2Base64XML } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { convertFilterValuesToArray } from "../layouts"; //Вспомогательные функции
|
||||
import { useDictionary } from "./dict_hooks"; //Состояние открытия разделов
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Хук обработки перехода события
|
||||
const useTasksFunctions = () => {
|
||||
//Состояние открытия раздела
|
||||
const { handleEventRoutesPointExecutersOpen, handleEventRoutesPointsPassessOpen } = useDictionary();
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Выполнение направления события
|
||||
const handleSendExec = useCallback(
|
||||
//Выполняем финальное перенаправление события
|
||||
async ({ mainArgs, onReload = null }) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND",
|
||||
args: { ...mainArgs }
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
onReload ? onReload() : null;
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//При направлении события
|
||||
const handleSend = useCallback(
|
||||
async ({ mainArgs, onReload = null, onNoteOpen = null }) => {
|
||||
//Если требуется добавить примечание
|
||||
if (onNoteOpen) {
|
||||
//Открываем примечание с коллбэком на направление события
|
||||
onNoteOpen(async note => {
|
||||
//Выполняем изменение статуса
|
||||
handleSendExec({ mainArgs: { ...mainArgs, SNOTE_HEADER: note.header, SNOTE: note.text }, onReload });
|
||||
});
|
||||
} else {
|
||||
//Выполняем изменение статуса
|
||||
handleSendExec({ mainArgs, onReload });
|
||||
}
|
||||
},
|
||||
[handleSendExec]
|
||||
);
|
||||
|
||||
//По нажатию действия "Направить"
|
||||
const handleTaskSend = useCallback(
|
||||
async ({ nEvent, onReload = null, onNoteOpen = null }) => {
|
||||
//Выполняем инициализацию параметров
|
||||
const firstStep = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_SEND",
|
||||
args: {
|
||||
NSTEP: 1,
|
||||
NEVENT: nEvent
|
||||
}
|
||||
});
|
||||
if (firstStep) {
|
||||
//Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
|
||||
handleEventRoutesPointExecutersOpen({
|
||||
inputParameters: [
|
||||
{ name: "in_IDENT", value: firstStep.NIDENT },
|
||||
{ name: "in_EVENT", value: nEvent },
|
||||
{ name: "in_PERSON_CODE", value: firstStep.SSEND_PERSON },
|
||||
{ name: "in_USER_NAME", value: firstStep.SSEND_USER_NAME },
|
||||
{ name: "in_EVENT_TYPE", value: firstStep.SEVENT_TYPE },
|
||||
{ name: "in_EVENT_STAT", value: firstStep.SEVENT_STAT },
|
||||
{ name: "in_INIT_PERSON", value: firstStep.SINIT_PERSON },
|
||||
{ name: "in_INIT_AUTHNAME", value: firstStep.SINIT_AUTHNAME },
|
||||
{ name: "in_CLIENT_CLIENT", value: firstStep.SCLIENT_CLIENT },
|
||||
{ name: "in_CLIENT_PERSON", value: firstStep.SCLIENT_PERSON }
|
||||
],
|
||||
callBack: sendPrms => {
|
||||
//Собираем основные параметры направления события
|
||||
const mainArgs = {
|
||||
NIDENT: firstStep.NIDENT,
|
||||
NSTEP: 2,
|
||||
NEVENT: nEvent,
|
||||
SSEND_CLIENT: sendPrms.outParameters.out_CLIENT_CODE,
|
||||
SSEND_DIVISION: sendPrms.outParameters.out_DIVISION_CODE,
|
||||
SSEND_POST: sendPrms.outParameters.out_POST_CODE,
|
||||
SSEND_PERFORM: sendPrms.outParameters.out_POST_IN_DIV_CODE,
|
||||
SSEND_PERSON: sendPrms.outParameters.out_PERSON_CODE,
|
||||
SSEND_STAFFGRP: sendPrms.outParameters.out_STAFFGRP_CODE,
|
||||
SSEND_USER_GROUP: sendPrms.outParameters.out_USER_GROUP_CODE,
|
||||
SSEND_USER_NAME: sendPrms.outParameters.out_USER_NAME,
|
||||
NSEND_PREDEFINED_EXEC: sendPrms.outParameters.out_PREDEFINED_EXEC,
|
||||
NSEND_PREDEFINED_PROC: sendPrms.outParameters.out_PREDEFINED_PROC
|
||||
};
|
||||
//Перенаправляем событие
|
||||
handleSend({ nEvent, mainArgs, onReload, onNoteOpen });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[executeStored, handleEventRoutesPointExecutersOpen, handleSend]
|
||||
);
|
||||
|
||||
//Выполнение изменения статуса события
|
||||
const handleStateChangeExec = useCallback(
|
||||
async ({ mainArgs, onReload = null }) => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: { ...mainArgs }
|
||||
});
|
||||
//Если требуется перезагрузить данные
|
||||
onReload ? onReload() : null;
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//При изменении статуса события
|
||||
const handleStateChange = useCallback(
|
||||
async ({ mainArgs, onReload = null, onNoteOpen = null }) => {
|
||||
//Если необходимо добавить примечание
|
||||
if (onNoteOpen) {
|
||||
//Открываем примечание с коллбэком на изменение статуса
|
||||
onNoteOpen(async note => {
|
||||
//Выполняем изменение статуса
|
||||
handleStateChangeExec({ mainArgs: { ...mainArgs, SNOTE_HEADER: note.header, SNOTE: note.text }, onReload });
|
||||
});
|
||||
} else {
|
||||
//Выполняем изменение статуса
|
||||
handleStateChangeExec({ mainArgs, onReload });
|
||||
}
|
||||
},
|
||||
[handleStateChangeExec]
|
||||
);
|
||||
|
||||
//При выборе исполнителя
|
||||
const handleExecuterSelect = useCallback(
|
||||
async ({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => {
|
||||
//Если требуется выбрать получателя
|
||||
if (pointInfo.NSELECT_EXEC === 1) {
|
||||
//Открываем раздел "Маршруты событий (исполнители в точках)" для выбора исполнителя
|
||||
handleEventRoutesPointExecutersOpen({
|
||||
inputParameters: [
|
||||
{ name: "in_IDENT", value: pointInfo.NIDENT },
|
||||
{ name: "in_EVENT", value: nEvent },
|
||||
{ name: "in_EVENT_TYPE", value: pointInfo.SEVENT_TYPE },
|
||||
{ name: "in_EVENT_STAT", value: pointInfo.SEVENT_STAT },
|
||||
{ name: "in_INIT_PERSON", value: pointInfo.SINIT_PERSON },
|
||||
{ name: "in_INIT_AUTHNAME", value: pointInfo.SINIT_AUTHNAME },
|
||||
{ name: "in_CLIENT_CLIENT", value: pointInfo.SCLIENT_CLIENT },
|
||||
{ name: "in_CLIENT_PERSON", value: pointInfo.SCLIENT_PERSON }
|
||||
],
|
||||
callBack: sendPrms => {
|
||||
const mainArgs = {
|
||||
NIDENT: pointInfo.NIDENT,
|
||||
NSTEP: 4,
|
||||
NEVENT: nEvent,
|
||||
SEVENT_STAT: pointInfo.SEVENT_STAT,
|
||||
SSEND_CLIENT: sendPrms.outParameters.out_CLIENT_CODE,
|
||||
SSEND_DIVISION: sendPrms.outParameters.out_DIVISION_CODE,
|
||||
SSEND_POST: sendPrms.outParameters.out_POST_CODE,
|
||||
SSEND_PERFORM: sendPrms.outParameters.out_POST_IN_DIV_CODE,
|
||||
SSEND_PERSON: sendPrms.outParameters.out_PERSON_CODE,
|
||||
SSEND_STAFFGRP: sendPrms.outParameters.out_STAFFGRP_CODE,
|
||||
SSEND_USER_GROUP: sendPrms.outParameters.out_USER_GROUP_CODE,
|
||||
SSEND_USER_NAME: sendPrms.outParameters.out_USER_NAME,
|
||||
NSEND_PREDEFINED_EXEC: sendPrms.outParameters.out_PREDEFINED_EXEC,
|
||||
NSEND_PREDEFINED_PROC: sendPrms.outParameters.out_PREDEFINED_PROC
|
||||
};
|
||||
//Выполняем изменение статуса
|
||||
handleStateChange({ mainArgs, onReload, onNoteOpen });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//Общие аргументы
|
||||
const mainArgs = {
|
||||
NIDENT: pointInfo.NIDENT,
|
||||
NSTEP: 4,
|
||||
NEVENT: nEvent,
|
||||
SEVENT_STAT: pointInfo.SEVENT_STAT
|
||||
};
|
||||
//Выполняем изменение статуса
|
||||
handleStateChange({ mainArgs, onReload, onNoteOpen });
|
||||
}
|
||||
},
|
||||
[handleEventRoutesPointExecutersOpen, handleStateChange]
|
||||
);
|
||||
|
||||
//При выполнении третьего шага
|
||||
const handleMakeThirdStep = useCallback(
|
||||
async ({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => {
|
||||
//Выполняем переход на следующий шаг
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: {
|
||||
NIDENT: pointInfo.NIDENT,
|
||||
NSTEP: 3,
|
||||
NPASS: pointInfo.NPASS
|
||||
}
|
||||
});
|
||||
//Выполняем выбор исполнителя
|
||||
handleExecuterSelect({
|
||||
nEvent,
|
||||
pointInfo,
|
||||
onReload,
|
||||
onNoteOpen
|
||||
});
|
||||
},
|
||||
[executeStored, handleExecuterSelect]
|
||||
);
|
||||
|
||||
//При выполнении второго шага
|
||||
const handleMakeSecondStep = useCallback(
|
||||
async ({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => {
|
||||
//Состояние параметров текущего действия
|
||||
let currentPointInfo = { ...pointInfo };
|
||||
//Выполняем переход на следующий шаг
|
||||
const secondStep = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: {
|
||||
NIDENT: currentPointInfo.NIDENT,
|
||||
NSTEP: 2,
|
||||
NPASS: currentPointInfo.NPASS
|
||||
}
|
||||
});
|
||||
//Устанавливаем признак необходимости выбора исполнителя
|
||||
currentPointInfo.NSELECT_EXEC = secondStep.NSELECT_EXEC;
|
||||
//Выполняем третий шаг
|
||||
handleMakeThirdStep({ nEvent, pointInfo: currentPointInfo, onReload, onNoteOpen });
|
||||
},
|
||||
[executeStored, handleMakeThirdStep]
|
||||
);
|
||||
|
||||
//При выборе следующей точки события
|
||||
const handleNextPointSelect = useCallback(
|
||||
({ nEvent, pointInfo, onReload = null, onNoteOpen = null }) => {
|
||||
//Состояние параметров текущего действия
|
||||
let currentPointInfo = { ...pointInfo };
|
||||
//Открываем раздел "Маршруты событий (точки перехода)" для выбора следующей точки
|
||||
handleEventRoutesPointsPassessOpen({
|
||||
sEventType: currentPointInfo.SEVENT_TYPE,
|
||||
sEventStatus: currentPointInfo.SEVENT_STAT,
|
||||
nPoint: currentPointInfo.NPOINT,
|
||||
callBack: async point => {
|
||||
//Устанавливаем полученную точку перехода
|
||||
currentPointInfo.NPASS = point.outParameters.out_RN;
|
||||
currentPointInfo.SEVENT_STAT = point.outParameters.out_NEXT_POINT;
|
||||
//Выполняем второй шаг
|
||||
handleMakeSecondStep({ nEvent, pointInfo: currentPointInfo, onReload, onNoteOpen });
|
||||
}
|
||||
});
|
||||
},
|
||||
[handleEventRoutesPointsPassessOpen, handleMakeSecondStep]
|
||||
);
|
||||
|
||||
//По нажатию действия "Перейти"
|
||||
const handleTaskStateChange = useCallback(
|
||||
async ({ nEvent, sNextStat = null, onReload = null, onNoteOpen = null }) => {
|
||||
//Выполняем инициализацию параметров
|
||||
const eventInfo = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_STATE_CHANGE",
|
||||
args: {
|
||||
NSTEP: 1,
|
||||
NEVENT: nEvent,
|
||||
SNEXT_STAT: sNextStat
|
||||
}
|
||||
});
|
||||
//Если информация о события проинициализирована
|
||||
if (eventInfo) {
|
||||
//Если следующий статус неопределен
|
||||
if (!sNextStat) {
|
||||
//Выполнение перехода с выбором точки
|
||||
handleNextPointSelect({
|
||||
nEvent,
|
||||
pointInfo: eventInfo,
|
||||
onReload,
|
||||
onNoteOpen
|
||||
});
|
||||
} else {
|
||||
//Выполняем второй шаг
|
||||
handleMakeSecondStep({ nEvent, pointInfo: eventInfo, onReload, onNoteOpen });
|
||||
}
|
||||
}
|
||||
},
|
||||
[executeStored, handleMakeSecondStep, handleNextPointSelect]
|
||||
);
|
||||
|
||||
return { handleTaskStateChange, handleTaskSend };
|
||||
};
|
||||
|
||||
//Хук получения событий
|
||||
const useTasks = (filterValues, ordersValues) => {
|
||||
//Состояние событий
|
||||
const [tasks, setTasks] = useState({
|
||||
loaded: false,
|
||||
rows: [],
|
||||
reload: false,
|
||||
accountsReload: false,
|
||||
loadedAccounts: []
|
||||
});
|
||||
|
||||
//Состояние вспомогательных функций событий
|
||||
const { handleTaskStateChange } = useTasksFunctions();
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//Инициализация параметров события
|
||||
const initTask = (id, task, avatar = null) => {
|
||||
//Фильтруем доп. свойства
|
||||
let newDocProps = Object.keys(task)
|
||||
.filter(key => key.includes("DP_"))
|
||||
.reduce((prev, key) => ({ ...prev, [key]: task[key] }), {});
|
||||
//Возвращаем структуру события
|
||||
return {
|
||||
id: id,
|
||||
avatar: avatar,
|
||||
name: task.SPREF_NUMB,
|
||||
nRn: task.NRN,
|
||||
sCrn: "",
|
||||
nCrn: task.NCRN,
|
||||
nClosed: task.NCLOSED,
|
||||
sPrefix: task.SEVPREF,
|
||||
sNumber: task.SEVNUMB,
|
||||
sType: task.SEVTYPE_CODE,
|
||||
sStatus: task.SEVSTAT_NAME,
|
||||
sDescription: task.SEVDESCR,
|
||||
sClntClients: "",
|
||||
sClntClnperson: "",
|
||||
dchange_date: task.DCHANGE_DATE,
|
||||
dStartDate: task.DREG_DATE,
|
||||
dExpireDate: task.DEXPIRE_DATE,
|
||||
dPlanDate: task.DPLAN_DATE,
|
||||
sInitClnperson: task.SINIT_PERSON,
|
||||
sInitUser: "",
|
||||
sInitReason: "",
|
||||
sToCompany: "",
|
||||
sToDepartment: task.SSEND_DIVISION,
|
||||
sToClnpost: "",
|
||||
sToClnpsdep: "",
|
||||
sToClnperson: task.SSEND_PERSON,
|
||||
sToFcstaffgrp: "",
|
||||
sToUser: "",
|
||||
sToUsergrp: task.SSEND_USRGRP,
|
||||
sSender: task.SSENDER,
|
||||
sCurrentUser: "",
|
||||
sLinkedUnit: task.SLINKED_UNIT,
|
||||
nLinkedRn: task.NLINKED_RN,
|
||||
docProps: newDocProps
|
||||
};
|
||||
};
|
||||
|
||||
//Взаимодействие с событием (через перенос)
|
||||
const onDragEnd = useCallback(
|
||||
({ path, eventPoints, openNoteDialog, destCode }) => {
|
||||
//Определяем нужные параметры
|
||||
const { source, destination } = path;
|
||||
//Если путь не указан
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
//Если происходит изменение статуса
|
||||
if (destination.droppableId !== source.droppableId) {
|
||||
//Конвертим ID переносимого события
|
||||
let nDraggableTaskId = parseInt(path.draggableId);
|
||||
//Считываем строку, у которой изменяется статус
|
||||
let task = tasks.rows.find(r => r.id === nDraggableTaskId);
|
||||
//Изменяем статус у события
|
||||
task.statusId = parseInt(path.destination.droppableId);
|
||||
//Получение настройки точки назначения
|
||||
const pointSettings = eventPoints.find(eventPoint => eventPoint.SEVPOINT === destCode);
|
||||
//Изменяем статус события с добавлением примечания
|
||||
handleTaskStateChange({
|
||||
nEvent: task.nRn,
|
||||
sNextStat: destCode,
|
||||
onReload: () => setTasks(pv => ({ ...pv, reload: true, accountsReload: true })),
|
||||
onNoteOpen: pointSettings.ADDNOTE_ONCHST ? openNoteDialog : null
|
||||
});
|
||||
}
|
||||
},
|
||||
[handleTaskStateChange, tasks.rows]
|
||||
);
|
||||
|
||||
//При необходимости перезагрузки данных
|
||||
useEffect(() => {
|
||||
//Считывание данных с учетом фильтрации
|
||||
let getTasks = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_LOAD",
|
||||
args: {
|
||||
CFILTERS: {
|
||||
VALUE: object2Base64XML(convertFilterValuesToArray(filterValues), { arrayNodeName: "filters" }),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
},
|
||||
CORDERS: { VALUE: object2Base64XML(ordersValues, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||
NINCLUDE_ACCOUNTS: tasks.accountsReload ? 1 : 0
|
||||
},
|
||||
isArray: name => name === "XAGENTS",
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Считываем информацию о событиях
|
||||
let events = data.XCLNEVENTS.XDATA.XDATA_GRID;
|
||||
//Считываем иноформацию о контрагентах
|
||||
let accounts = tasks.accountsReload ? [...(data.XAGENTS_WITH_IMG.XAGENTS || [])] : tasks.loadedAccounts;
|
||||
//Инициализируем события
|
||||
let newRows = [];
|
||||
//Если есть события
|
||||
if (events.rows) {
|
||||
//Формируем структуру событий
|
||||
newRows = [...(events.rows || [])].reduce(
|
||||
(prev, cur) => [...prev, initTask(prev.length, cur, accounts.find(agent => agent.SAGNABBR === cur.SSENDER)?.BIMAGE)],
|
||||
[]
|
||||
);
|
||||
}
|
||||
//Возвращаем информацию
|
||||
return { rows: [...newRows], loadedAccounts: accounts };
|
||||
};
|
||||
//Считывание данных
|
||||
let getData = async () => {
|
||||
//Считываем информацию о задачах
|
||||
let eventTasks = await getTasks();
|
||||
//Загружаем данные
|
||||
setTasks(pv => ({
|
||||
...pv,
|
||||
loaded: true,
|
||||
rows: eventTasks.rows,
|
||||
loadedAccounts: eventTasks.loadedAccounts,
|
||||
reload: false,
|
||||
accountsReload: false
|
||||
}));
|
||||
};
|
||||
//Если необходимо загрузить данные и указан тип событий и загружены все необходимые вспомогательные данные
|
||||
if (tasks.reload) {
|
||||
//Загружаем данные
|
||||
getData();
|
||||
}
|
||||
}, [SERV_DATA_TYPE_CLOB, executeStored, filterValues, ordersValues, tasks.accountsReload, tasks.loadedAccounts, tasks.reload]);
|
||||
|
||||
return [tasks, setTasks, onDragEnd];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useTasksFunctions, useTasks };
|
||||
@ -1,16 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Панель мониторинга: Точка входа
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { ClntTaskBoard } from "./clnt_task_board"; //Корневая панель выполнения работ
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export const RootClass = ClntTaskBoard;
|
||||
@ -1,303 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Перечисление "Состояние события"
|
||||
export const EVENT_STATES = Object.freeze({ 0: "Все", 1: "Не аннулированные", 2: "Аннулированные" });
|
||||
|
||||
//Допустимые значение поля сортировки
|
||||
export const sortAttrs = [
|
||||
{ id: "SEVNSTAT_CODE", descr: "Мнемокод статуса" },
|
||||
{ id: "SEVNSTAT_NAME", descr: "Наименование статуса" },
|
||||
{ id: "SEVPOINT_DESCR", descr: "Описание точки маршрута" }
|
||||
];
|
||||
|
||||
//Допустимые значения направления сортировки
|
||||
export const sortDest = [];
|
||||
sortDest[-1] = "desc";
|
||||
sortDest[1] = "asc";
|
||||
|
||||
//Цвета статусов
|
||||
export const COLORS = [
|
||||
"mediumSlateBlue",
|
||||
"lightSalmon",
|
||||
"fireBrick",
|
||||
"orange",
|
||||
"gold",
|
||||
"limeGreen",
|
||||
"yellowGreen",
|
||||
"mediumAquaMarine",
|
||||
"paleTurquoise",
|
||||
"steelBlue",
|
||||
"skyBlue",
|
||||
"tan"
|
||||
];
|
||||
|
||||
//Перечисление "Цвет задачи"
|
||||
export const TASK_COLORS = Object.freeze({ EXPIRED: "#ff0000", EXPIRES_SOON: "#ffdf00", LINKED: "#1e90ff" });
|
||||
|
||||
//Перечисление Доп. свойства "Значение по умолчанию"
|
||||
export const DP_DEFAULT_VALUE = Object.freeze({ 0: "SDEFAULT_STR", 1: "NDEFAULT_NUM", 2: "DDEFAULT_DATE", 3: "NDEFAULT_NUM" });
|
||||
//Перечисление Доп. свойства "Префикс формата данных"
|
||||
export const DP_TYPE_PREFIX = Object.freeze({ 0: "S", 1: "N", 2: "D", 3: "N" });
|
||||
//Перечисление Доп. свойства "Входящее значение дополнительного словаря"
|
||||
export const DP_IN_VALUE = Object.freeze({ 0: "pos_str_value", 1: "pos_num_value", 2: "pos_date_value", 3: "pos_num_value" });
|
||||
//Перечисление Доп. свойства "Исходящее значение дополнительного словаря"
|
||||
export const DP_RETURN_VALUE = Object.freeze({ 0: "str_value", 1: "num_value", 2: "date_value", 3: "num_value" });
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Формирование массива из 0, 1 и более элементов
|
||||
export const makeArray = arr => {
|
||||
return arr ? (arr.length ? arr : [arr]) : [];
|
||||
};
|
||||
|
||||
//Конвертация формата HEX в формат RGB
|
||||
const convertHexToRGB = hex => {
|
||||
let r = parseInt(hex.slice(1, 3), 16);
|
||||
let g = parseInt(hex.slice(3, 5), 16);
|
||||
let b = parseInt(hex.slice(5, 7), 16);
|
||||
let a = 0.5;
|
||||
r = Math.round((a * (r / 255) + a * (255 / 255)) * 255);
|
||||
g = Math.round((a * (g / 255) + a * (255 / 255)) * 255);
|
||||
b = Math.round((a * (b / 255) + a * (255 / 255)) * 255);
|
||||
return "rgb(" + r + ", " + g + ", " + b + ")";
|
||||
};
|
||||
|
||||
//Считывание заливки события по условию
|
||||
export const getTaskBgColorByRule = (task, colorRule) => {
|
||||
//Исходя из типа определяем наименование и возвращаем цвет заливки
|
||||
switch (colorRule.STYPE) {
|
||||
case "number":
|
||||
return (!colorRule.fromValue || Number(task.docProps[`N${colorRule.SFIELD}`]) >= Number(colorRule.fromValue)) &&
|
||||
(!colorRule.toValue || Number(task.docProps[`N${colorRule.SFIELD}`]) <= Number(colorRule.toValue))
|
||||
? convertHexToRGB(colorRule.SCOLOR)
|
||||
: null;
|
||||
default:
|
||||
return task.docProps[`S${colorRule.SFIELD}`] == colorRule.fromValue ? convertHexToRGB(colorRule.SCOLOR) : null;
|
||||
}
|
||||
};
|
||||
|
||||
//Индикация истечения срока отработки события
|
||||
export const getTaskExpiredColor = task => {
|
||||
//Определяем текущую дату
|
||||
let sysDate = new Date();
|
||||
//Определяем дату истечения срока события
|
||||
let expireDate = task.dExpireDate ? new Date(task.dExpireDate) : null;
|
||||
//Если дата истечения срока определена
|
||||
if (expireDate) {
|
||||
//Определяем разницу между датами
|
||||
let daysDiff = ((expireDate.getTime() - sysDate.getTime()) / (1000 * 60 * 60 * 24)).toFixed(2);
|
||||
//Если разница меньше 0 - срок истечен
|
||||
if (daysDiff < 0) return TASK_COLORS.EXPIRED;
|
||||
//Если разница меньше 4 - скоро истечет
|
||||
if (daysDiff < 4) return TASK_COLORS.EXPIRES_SOON;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
//Цвет из hsl формата в rgba формат
|
||||
const convertHslToRgba = (h, s, l) => {
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
const k = n => (n + h / 30) % 12;
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = n => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
||||
return `rgba(${Math.floor(255 * f(0))},${Math.floor(255 * f(8))},${Math.floor(255 * f(4))},0.3)`;
|
||||
};
|
||||
|
||||
//Формирование случайного цвета
|
||||
export const getRandomColor = index => {
|
||||
const hue = index * 137.508;
|
||||
return convertHslToRgba(hue, 50, 70);
|
||||
};
|
||||
|
||||
//Формат дополнительного свойства типа число (длина, точность)
|
||||
const formatRegExpNum = (width, precision) =>
|
||||
new RegExp("^(\\d{1," + (width - precision) + "}" + (precision > 0 ? "((\\.|,)\\d{1," + precision + "})?" : "") + ")?$");
|
||||
|
||||
//Формат дополнительного свойства типа строка (длина)
|
||||
const formatRegExpStr = length => new RegExp("^.{0," + length + "}$");
|
||||
|
||||
//Проверка валидности числа
|
||||
const isValidNum = (width, precision, value) => {
|
||||
return formatRegExpNum(width, precision).test(value);
|
||||
};
|
||||
|
||||
//Проверка валидности строки
|
||||
const isValidStr = (length, value) => {
|
||||
return formatRegExpStr(length).test(value);
|
||||
};
|
||||
|
||||
//Признак ошибки валидации
|
||||
export const validationError = (value = "", format, numWidth, numPrecision, strLength) => {
|
||||
//Исходим от формата
|
||||
switch (format) {
|
||||
//Проверка строки
|
||||
case 0:
|
||||
return isValidStr(strLength, value);
|
||||
//Проверка числа
|
||||
case 1:
|
||||
return isValidNum(numWidth, numPrecision, value);
|
||||
//Остальное не проверяем
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//Конвертация времени в привычный формат
|
||||
export const formatSqlDate = timeStamp => {
|
||||
//Если есть разделитель
|
||||
if (timeStamp.indexOf(".") !== -1) {
|
||||
//Определяем секунды
|
||||
let seconds = 24 * 60 * 60 * timeStamp;
|
||||
//Определяем часы
|
||||
const hours = Math.trunc(seconds / (60 * 60));
|
||||
//Переопределяем секунды
|
||||
seconds = seconds % (60 * 60);
|
||||
//Определяем минуты
|
||||
const minutes = Math.trunc(seconds / 60);
|
||||
//Определяем остаток секунд
|
||||
seconds = Math.round(seconds % 60);
|
||||
//Форматируем
|
||||
const formattedTime = ("0" + hours).slice(-2) + ":" + ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2);
|
||||
//Возвращаем результат
|
||||
return formattedTime;
|
||||
}
|
||||
return timeStamp;
|
||||
};
|
||||
|
||||
//Считывание значений из локального хранилища
|
||||
export const getLocalStorageValue = (sName, defaultValue = null) => localStorage.getItem(sName) || defaultValue;
|
||||
|
||||
//Форматирование фильтра в массив для отбора
|
||||
export const convertFilterValuesToArray = filterValues => {
|
||||
//Инициализируем значение "с" состояния ("Все", "Не аннулированные" - 0, "Аннулированые" - 1)
|
||||
let nClosedFrom = filterValues.sState ? ([EVENT_STATES[0], EVENT_STATES[1]].includes(filterValues.sState) ? 0 : 1) : 0;
|
||||
//Инициализируем значение "по" состояния ("Все", "Аннулированные" - 1, "Не аннулированные" - 0)
|
||||
let nClosedTo = filterValues.sState ? ([EVENT_STATES[0], EVENT_STATES[2]].includes(filterValues.sState) ? 1 : 0) : 0;
|
||||
//Формируем массив значений фильтра
|
||||
let filterValuesArray = [
|
||||
{ name: "NCLOSED", from: nClosedFrom, to: nClosedTo },
|
||||
{ name: "SEVTYPE_CODE", from: filterValues.sType, to: null },
|
||||
{ name: "NCRN", from: filterValues.sCrnRnList, to: null },
|
||||
{ name: "SSEND_PERSON", from: filterValues.sSendPerson, to: null },
|
||||
{ name: "SSEND_DIVISION", from: filterValues.sSendDivision, to: null },
|
||||
{ name: "SSEND_USRGRP", from: filterValues.sSendUsrGrp, to: null },
|
||||
{ name: "NLINKED_RN", from: filterValues.sDocLink, to: null }
|
||||
];
|
||||
return filterValuesArray;
|
||||
};
|
||||
|
||||
//Формирование массива действий карточки события
|
||||
export const makeCardActionsArray = (onEdit, onEditClient, onDelete, onStateChange, onReturn, onSend, onNotesOpen, onFileLinksOpen, onMove) => {
|
||||
//Формируем список действий карточки
|
||||
return [
|
||||
{
|
||||
method: "EDIT",
|
||||
name: "Исправить",
|
||||
icon: "edit",
|
||||
visible: false,
|
||||
delimiter: false,
|
||||
tasksReload: false,
|
||||
needAccountsReload: false,
|
||||
disableClosed: false,
|
||||
func: onEdit
|
||||
},
|
||||
{
|
||||
method: "EDIT_CLIENT",
|
||||
name: "Исправить в разделе",
|
||||
icon: "edit_note",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
tasksReload: false,
|
||||
needAccountsReload: false,
|
||||
disableClosed: false,
|
||||
func: onEditClient
|
||||
},
|
||||
{
|
||||
method: "MOVE",
|
||||
name: "Переместить",
|
||||
icon: "drive_file_move",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
tasksReload: true,
|
||||
needAccountsReload: false,
|
||||
disableClosed: false,
|
||||
func: onMove
|
||||
},
|
||||
{
|
||||
method: "DELETE",
|
||||
name: "Удалить",
|
||||
icon: "delete",
|
||||
visible: true,
|
||||
delimiter: true,
|
||||
tasksReload: true,
|
||||
needAccountsReload: false,
|
||||
disableClosed: false,
|
||||
func: onDelete
|
||||
},
|
||||
{
|
||||
method: "TASK_STATE_CHANGE",
|
||||
name: "Перейти",
|
||||
icon: "turn_right",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
tasksReload: true,
|
||||
needAccountsReload: true,
|
||||
disableClosed: true,
|
||||
func: onStateChange
|
||||
},
|
||||
{
|
||||
method: "TASK_RETURN",
|
||||
name: "Выполнить возврат",
|
||||
icon: "turn_left",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
tasksReload: true,
|
||||
needAccountsReload: true,
|
||||
disableClosed: true,
|
||||
func: onReturn
|
||||
},
|
||||
{
|
||||
method: "TASK_SEND",
|
||||
name: "Направить",
|
||||
icon: "send",
|
||||
visible: true,
|
||||
delimiter: true,
|
||||
tasksReload: true,
|
||||
needAccountsReload: true,
|
||||
disableClosed: true,
|
||||
func: onSend
|
||||
},
|
||||
{
|
||||
method: "NOTES",
|
||||
name: "Примечания",
|
||||
icon: "event_note",
|
||||
visible: true,
|
||||
delimiter: true,
|
||||
tasksReload: false,
|
||||
needAccountsReload: false,
|
||||
disableClosed: false,
|
||||
func: onNotesOpen
|
||||
},
|
||||
{
|
||||
method: "FILE_LINKS",
|
||||
name: "Присоединенные документы",
|
||||
icon: "attach_file",
|
||||
visible: true,
|
||||
delimiter: false,
|
||||
tasksReload: false,
|
||||
needAccountsReload: false,
|
||||
disableClosed: false,
|
||||
func: onFileLinksOpen
|
||||
}
|
||||
];
|
||||
};
|
||||
@ -1,48 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент: Общие стили
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Общие стили
|
||||
export const COMMON_STYLES = {
|
||||
TASK_FORM_TEXT_FIELD: (widthVal, greyDisabled = false) => ({
|
||||
margin: "4px",
|
||||
...(widthVal ? { width: widthVal } : {}),
|
||||
...(greyDisabled
|
||||
? {
|
||||
"& .MuiInputBase-input.Mui-disabled": {
|
||||
WebkitTextFillColor: "rgba(0, 0, 0, 0.87)"
|
||||
},
|
||||
"& .MuiInputLabel-root.Mui-disabled": {
|
||||
WebkitTextFillColor: "rgba(0, 0, 0, 0.6)"
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}),
|
||||
BOX_WITH_LEGEND: { border: "1px solid #939393", marginBottom: "1px" },
|
||||
BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" },
|
||||
LEGEND: { textAlign: "left" },
|
||||
SELECT_MENU: width => {
|
||||
return { overflowY: "auto", ...APP_STYLES.SCROLL, width: width ? width : null };
|
||||
},
|
||||
STACK_DOCLINKS: { alignItems: "baseline" },
|
||||
SCROLL: { ...APP_STYLES.SCROLL, overflowY: "auto" },
|
||||
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" },
|
||||
DIALOG_CLOSE_BUTTON: {
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500]
|
||||
},
|
||||
ZERO_PADDING: { padding: 0 }
|
||||
};
|
||||
@ -1,176 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||||
Компонент панели: Диалог формы события
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useCallback, useContext, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { useClientEvent } from "./hooks/task_dialog_hooks"; //Хук для события
|
||||
import { useDocsProps } from "./hooks/task_dialog_hooks"; //Хук для получения доп. свойств раздела "События"
|
||||
import { TaskForm } from "./components/task_form"; //Форма события
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { object2Base64XML } from "../../core/utils"; //Вспомогательные функции
|
||||
import { hasValue } from "../../core/utils"; //Вспомогательные процедуры и функции
|
||||
import { P8PDialog } from "../../components/p8p_dialog"; //Типовой диалог
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог формы события
|
||||
const TaskDialog = ({ taskRn, taskType, editable, onTasksReload, onClose }) => {
|
||||
//Собственное состояние
|
||||
const [task, setTask] = useClientEvent(taskRn, taskType);
|
||||
|
||||
//Состояние допустимых дополнительных свойств
|
||||
const [docProps] = useDocsProps(taskType);
|
||||
|
||||
//Состояние заполненности всех обязательных доп. свойств
|
||||
const [docPropsReady, setDocPropsReady] = useState(false);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//При изменении информации о задаче
|
||||
const handleTaskChange = useCallback(
|
||||
newTaskValues => {
|
||||
setTask(pv => ({ ...pv, ...newTaskValues }));
|
||||
},
|
||||
[setTask]
|
||||
);
|
||||
|
||||
//При добавлении события
|
||||
const handleInsertTask = async callBack => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_INSERT",
|
||||
args: {
|
||||
SCRN: task.sCrn,
|
||||
SPREF: task.sPrefix,
|
||||
SNUMB: task.sNumber,
|
||||
STYPE: task.sType,
|
||||
SSTATUS: task.sStatus,
|
||||
SPLAN_DATE: task.dPlanDate,
|
||||
SINIT_PERSON: task.sInitClnperson,
|
||||
SCLIENT_CLIENT: task.sClntClients,
|
||||
SCLIENT_PERSON: task.sClntClnperson,
|
||||
SDESCRIPTION: task.sDescription,
|
||||
SREASON: task.sInitReason,
|
||||
CPROPS: {
|
||||
VALUE: object2Base64XML(
|
||||
[
|
||||
Object.fromEntries(
|
||||
Object.entries(task.docProps)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
.filter(([_, v]) => v != (null || ""))
|
||||
)
|
||||
],
|
||||
{
|
||||
arrayNodeName: "props"
|
||||
}
|
||||
),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
}
|
||||
});
|
||||
callBack();
|
||||
};
|
||||
|
||||
//При исправлении события
|
||||
const handleUpdateEvent = async callBack => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_UPDATE",
|
||||
args: {
|
||||
NCLNEVENTS: task.nRn,
|
||||
SCLIENT_CLIENT: task.sClntClients,
|
||||
SCLIENT_PERSON: task.sClntClnperson,
|
||||
SDESCRIPTION: task.sDescription,
|
||||
CPROPS: {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
VALUE: object2Base64XML([Object.fromEntries(Object.entries(task.docProps).filter(([_, v]) => v != (null || "")))], {
|
||||
arrayNodeName: "props"
|
||||
}),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
}
|
||||
});
|
||||
callBack();
|
||||
};
|
||||
|
||||
//При считывании следующего номера события
|
||||
const handleEventNextNumbGet = useCallback(async () => {
|
||||
//Считываем данные
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_CLNTTSKBRD.CLNEVENTS_NEXTNUMB_GET",
|
||||
args: {
|
||||
SPREFIX: task.sPrefix
|
||||
}
|
||||
});
|
||||
//Если данные есть
|
||||
if (data) {
|
||||
//Устанавливаем номер
|
||||
setTask(pv => ({ ...pv, sNumber: data.SEVENT_NUMB }));
|
||||
}
|
||||
}, [executeStored, setTask, task.sPrefix]);
|
||||
|
||||
//Проверка заполненности всех обязательных доп. свойств
|
||||
useEffect(() => {
|
||||
//Если доп. свойства загрузились
|
||||
if (docProps.loaded) {
|
||||
//Проверяем остались ли обязательные незаполненные свойства
|
||||
let notFilled = docProps.props.some(docProp => docProp.BREQUIRE === true && !hasValue(task.docProps[docProp.SFORMATTED_ID]));
|
||||
//Если незаполненных обязательных доп. свойств не осталось - доп. свойства готовы, иначе не готовы
|
||||
setDocPropsReady(!notFilled);
|
||||
} else {
|
||||
//Доп. свойства не готовы
|
||||
setDocPropsReady(false);
|
||||
}
|
||||
}, [docProps.loaded, docProps.props, task.docProps]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<>
|
||||
{!task.init && docProps.loaded && (
|
||||
<P8PDialog
|
||||
title={task.nRn ? `Исправление события${task.nClosed ? " [аннулировано]" : ""}` : "Добавление события"}
|
||||
fullWidth={true}
|
||||
onOk={() => (taskRn ? handleUpdateEvent(onClose).then(onTasksReload) : handleInsertTask(onClose).then(onTasksReload))}
|
||||
onClose={onClose ? onClose : null}
|
||||
okDisabled={taskRn ? task.updateDisabled || !editable || !docPropsReady : task.insertDisabled || !docPropsReady}
|
||||
scrollContent={false}
|
||||
>
|
||||
<TaskForm
|
||||
task={task}
|
||||
taskType={taskType}
|
||||
editable={!taskRn || editable ? true : false}
|
||||
docProps={docProps.props}
|
||||
onTaskChange={handleTaskChange}
|
||||
onEventNextNumbGet={handleEventNextNumbGet}
|
||||
/>
|
||||
</P8PDialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог формы события
|
||||
TaskDialog.propTypes = {
|
||||
taskRn: PropTypes.number,
|
||||
taskType: PropTypes.string.isRequired,
|
||||
editable: PropTypes.bool,
|
||||
onTasksReload: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { TaskDialog };
|
||||
@ -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 };
|
||||
@ -1,16 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - ПУП - Выдача сменного задания на участок
|
||||
Панель мониторинга: Точка входа
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { MechRecCostJobs } from "./mech_rec_cost_jobs_manage_mp"; //Корневая панель выдачи сменного задания на участок
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export const RootClass = MechRecCostJobs;
|
||||
@ -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 };
|
||||
@ -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 };
|
||||
@ -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 => ({
|
||||
|
||||
@ -36,8 +36,7 @@ import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardContent,
|
||||
CardActions,
|
||||
Tooltip
|
||||
CardActions
|
||||
} from "@mui/material"; //Интерфейсные элементы
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
@ -56,34 +55,19 @@ import { IncomFromDepsDataGrid } from "./datagrids/incomefromdeps"; //Табли
|
||||
//---------
|
||||
|
||||
//Склонения для документов
|
||||
const PLANS_DECLINATIONS = ["план", "плана", "планов"];
|
||||
const SPEC_DECLINATIONS = ["элемент", "элемента", "элементов"];
|
||||
const DECLINATIONS = ["план", "плана", "планов"];
|
||||
|
||||
//Поля сортировки
|
||||
const SORT_REP_DATE = "DREP_DATE";
|
||||
const SORT_REP_DATE_TO = "DREP_DATE_TO";
|
||||
|
||||
//Максимальное количество элементов
|
||||
const MAX_TASKS = 10000;
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
|
||||
PLANS_CHECKBOX_HAVEDOCS: { alignContent: "space-around" },
|
||||
PLANS_LIST_CONTAINER: { height: "100%", display: "flex", flexDirection: "column", justifyContent: "space-between" },
|
||||
PLANS_LIST_ITEM_ZERODOCS: { backgroundColor: "#ebecec" },
|
||||
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
|
||||
PLANS_LIST_ITEM_SECONDARY: { wordWrap: "break-word", fontSize: "0.6rem", textTransform: "uppercase" },
|
||||
PLANS_LIST_ITEM_PLAN: {
|
||||
backgroundColor: "#c7e7f1",
|
||||
"&:hover": { backgroundColor: `#c7e7f1`, filter: "brightness(0.92) !important" }
|
||||
},
|
||||
PLANS_LIST_ITEM_PLAN_FIELD: {
|
||||
marginLeft: "15px"
|
||||
},
|
||||
PLANS_LIST_FILTER_CONTAINER: { height: "calc(100% - 55px)", overflowY: "auto" },
|
||||
PLANS_LIST_BUTTONS_CONTAINER: { display: "flex", justifyContent: "space-around", paddingBottom: "10px", height: "45px" },
|
||||
PLANS_LIST_BUTTON: { minWidth: "125px", height: "35px" },
|
||||
PLANS_BUTTON: { position: "absolute", top: `calc(${APP_BAR_HEIGHT} + 16px)`, left: "16px" },
|
||||
PLANS_DRAWER: {
|
||||
width: "350px",
|
||||
@ -100,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" }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
@ -137,9 +102,7 @@ const parseProdPlanSpXML = async xmlDoc => {
|
||||
};
|
||||
|
||||
//Форматирование для отображения количества документов
|
||||
const formatCountDocs = (nCountDocs, nType = 0) => {
|
||||
//Склонение документов
|
||||
let DECLINATIONS = nType === 0 ? PLANS_DECLINATIONS : SPEC_DECLINATIONS;
|
||||
const formatCountDocs = nCountDocs => {
|
||||
//Получаем последнюю цифру в значении
|
||||
let num = (nCountDocs % 100) % 10;
|
||||
//Документов
|
||||
@ -152,146 +115,55 @@ const formatCountDocs = (nCountDocs, nType = 0) => {
|
||||
return `${nCountDocs} ${DECLINATIONS[2]}`;
|
||||
};
|
||||
|
||||
//Изменение информации об отмеченных планах
|
||||
const updateCtlgPlanInfo = (selectedPlans, plan) => {
|
||||
//Результат изменений
|
||||
let res = { selectedPlans: [...selectedPlans] || [], selectedPlansElements: plan.NCOUNT_DOCS };
|
||||
//Определяем наличие в отмеченных планах
|
||||
let selectedIndex = res.selectedPlans.indexOf(plan.NRN);
|
||||
//Если уже есть в отмеченных - удаляем, нет - добавляем
|
||||
if (selectedIndex > -1) {
|
||||
//Удаляем план из выбранных
|
||||
res.selectedPlans.splice(selectedIndex, 1);
|
||||
//Переворачиваем сумму документов
|
||||
res.selectedPlansElements = res.selectedPlansElements * -1;
|
||||
} else {
|
||||
//Добавляем план в выбранные
|
||||
res.selectedPlans.push(plan.NRN);
|
||||
}
|
||||
//Возвращаем результат
|
||||
return res;
|
||||
};
|
||||
|
||||
//Список каталогов планов
|
||||
const PlanCtlgsList = ({
|
||||
planCtlgs = [],
|
||||
selectedPlans = [],
|
||||
selectedPlanCtlg,
|
||||
selectedPlansElements,
|
||||
filter,
|
||||
setFilter,
|
||||
onCtlgClick,
|
||||
onCtlgPlanClick,
|
||||
onCtlgPlansOk,
|
||||
onCtlgPlansCancel
|
||||
} = {}) => {
|
||||
const PlanCtlgsList = ({ planCtlgs = [], selectedPlanCtlg, filter, setFilter, onClick } = {}) => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.PLANS_LIST_CONTAINER}>
|
||||
<Box sx={STYLES.PLANS_LIST_FILTER_CONTAINER}>
|
||||
<TextField
|
||||
sx={STYLES.PLANS_FINDER}
|
||||
name="planFilter"
|
||||
label="Каталог"
|
||||
value={filter.ctlgName}
|
||||
variant="standard"
|
||||
fullWidth
|
||||
onChange={event => {
|
||||
setFilter(pv => ({ ...pv, ctlgName: event.target.value }));
|
||||
}}
|
||||
></TextField>
|
||||
<FormGroup sx={STYLES.PLANS_CHECKBOX_HAVEDOCS}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox checked={filter.haveDocs} onChange={event => setFilter(pv => ({ ...pv, haveDocs: event.target.checked }))} />
|
||||
}
|
||||
label="Только с планами"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</FormGroup>
|
||||
<List>
|
||||
{planCtlgs.map(ctlg => (
|
||||
<Box key={ctlg.NRN}>
|
||||
<ListItemButton
|
||||
sx={ctlg.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : null}
|
||||
key={ctlg.NRN}
|
||||
selected={ctlg.NRN === selectedPlanCtlg}
|
||||
onClick={() => (onCtlgClick ? onCtlgClick(ctlg) : null)}
|
||||
disabled={ctlg.NCOUNT_DOCS == 0}
|
||||
>
|
||||
<ListItemText
|
||||
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{ctlg.SNAME}</Typography>}
|
||||
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(ctlg.NCOUNT_DOCS, 0)}</Typography>}
|
||||
/>
|
||||
</ListItemButton>
|
||||
{ctlg.NRN === selectedPlanCtlg && ctlg.XCRN_PLANS.length > 1
|
||||
? ctlg.XCRN_PLANS.map(plan => (
|
||||
<ListItemButton
|
||||
sx={plan.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : STYLES.PLANS_LIST_ITEM_PLAN}
|
||||
key={plan.NRN}
|
||||
disabled={plan.NCOUNT_DOCS == 0}
|
||||
onClick={() => (onCtlgPlanClick ? onCtlgPlanClick(plan) : null)}
|
||||
>
|
||||
<ListItemText
|
||||
sx={STYLES.PLANS_LIST_ITEM_PLAN_FIELD}
|
||||
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{plan.SNAME}</Typography>}
|
||||
secondary={
|
||||
<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>
|
||||
{formatCountDocs(plan.NCOUNT_DOCS, 1)}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
{plan.NCOUNT_DOCS !== 0 ? <Checkbox checked={selectedPlans.includes(plan.NRN)} /> : null}
|
||||
</ListItemButton>
|
||||
))
|
||||
: null}
|
||||
</Box>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
<Box sx={STYLES.PLANS_LIST_BUTTONS_CONTAINER}>
|
||||
<Tooltip
|
||||
title={
|
||||
!selectedPlanCtlg
|
||||
? "Не выбран каталог планов"
|
||||
: selectedPlans.length === 0
|
||||
? "Не выбраны планы каталога"
|
||||
: selectedPlansElements > MAX_TASKS
|
||||
? `Выбранные планы превышают максимум элементов (выбрано: ${selectedPlansElements}, максимум: ${MAX_TASKS})`
|
||||
: null
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<Button
|
||||
sx={STYLES.PLANS_LIST_BUTTON}
|
||||
variant="contained"
|
||||
disabled={selectedPlans.length === 0 || selectedPlansElements > MAX_TASKS}
|
||||
onClick={onCtlgPlansOk}
|
||||
>
|
||||
Применить
|
||||
</Button>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Button sx={STYLES.PLANS_LIST_BUTTON} variant="contained" onClick={onCtlgPlansCancel}>
|
||||
Отмена
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<div>
|
||||
<TextField
|
||||
sx={STYLES.PLANS_FINDER}
|
||||
name="planFilter"
|
||||
label="Каталог"
|
||||
value={filter.ctlgName}
|
||||
variant="standard"
|
||||
fullWidth
|
||||
onChange={event => {
|
||||
setFilter(pv => ({ ...pv, ctlgName: event.target.value }));
|
||||
}}
|
||||
></TextField>
|
||||
<FormGroup sx={STYLES.PLANS_CHECKBOX_HAVEDOCS}>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={filter.haveDocs} onChange={event => setFilter(pv => ({ ...pv, haveDocs: event.target.checked }))} />}
|
||||
label="Только с планами"
|
||||
labelPlacement="end"
|
||||
/>
|
||||
</FormGroup>
|
||||
<List>
|
||||
{planCtlgs.map(p => (
|
||||
<ListItemButton
|
||||
sx={p.NCOUNT_DOCS == 0 ? STYLES.PLANS_LIST_ITEM_ZERODOCS : null}
|
||||
key={p.NRN}
|
||||
selected={p.NRN === selectedPlanCtlg}
|
||||
onClick={() => (onClick ? onClick(p) : null)}
|
||||
>
|
||||
<ListItemText
|
||||
primary={<Typography sx={STYLES.PLANS_LIST_ITEM_PRIMARY}>{p.SNAME}</Typography>}
|
||||
secondary={<Typography sx={STYLES.PLANS_LIST_ITEM_SECONDARY}>{formatCountDocs(p.NCOUNT_DOCS)}</Typography>}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Список каталогов планов
|
||||
PlanCtlgsList.propTypes = {
|
||||
planCtlgs: PropTypes.array,
|
||||
selectedPlans: PropTypes.array,
|
||||
selectedPlanCtlg: PropTypes.number,
|
||||
selectedPlansElements: PropTypes.number,
|
||||
onCtlgClick: PropTypes.func,
|
||||
onCtlgPlanClick: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
filter: PropTypes.object,
|
||||
setFilter: PropTypes.func,
|
||||
onCtlgPlansOk: PropTypes.func,
|
||||
onCtlgPlansCancel: PropTypes.func
|
||||
setFilter: PropTypes.func
|
||||
};
|
||||
|
||||
//Генерация диалога задачи
|
||||
@ -360,18 +232,12 @@ const MechRecCostProdPlans = () => {
|
||||
showPlanList: false,
|
||||
planCtlgs: [],
|
||||
planCtlgsLoaded: false,
|
||||
selectedPlans: [],
|
||||
selectedPlansElements: 0,
|
||||
selectedPlanCtlgSpecsLoaded: false,
|
||||
selectedPlanCtlg: null,
|
||||
selectedPlanCtlgMaxLevel: null,
|
||||
selectedPlanCtlgLevel: null,
|
||||
selectedPlanCtlgOutOfLimit: 0,
|
||||
selectedPlanCtlgSort: null,
|
||||
selectedPlanCtlgMenuItems: null,
|
||||
loadedCtlg: null,
|
||||
loadedPlans: [],
|
||||
loadedElements: 0,
|
||||
gantt: {},
|
||||
selectedTaskDetail: null,
|
||||
selectedTaskDetailType: null,
|
||||
@ -387,14 +253,11 @@ const MechRecCostProdPlans = () => {
|
||||
const { InlineMsgInfo } = useContext(MessagingСtx);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту навигации
|
||||
const { getNavigationSearch } = useContext(NavigationCtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgInfo } = useContext(MessagingСtx);
|
||||
|
||||
//Инициализация каталогов планов
|
||||
const initPlanCtlgs = useCallback(async () => {
|
||||
if (!state.init) {
|
||||
@ -402,101 +265,74 @@ const MechRecCostProdPlans = () => {
|
||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_PP_CTLG_INIT",
|
||||
args: {},
|
||||
respArg: "COUT",
|
||||
isArray: name => ["XFCPRODPLAN_CRNS", "XCRN_PLANS"].includes(name)
|
||||
isArray: name => name === "XFCPRODPLAN_CRNS"
|
||||
});
|
||||
setState(pv => ({ ...pv, init: true, planCtlgs: [...(data?.XFCPRODPLAN_CRNS || [])], planCtlgsLoaded: true }));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [state.init, executeStored]);
|
||||
|
||||
//Выбор каталога планов
|
||||
const selectPlan = project => {
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selectedPlanCtlg: project,
|
||||
selectedPlanCtlgSpecsLoaded: false,
|
||||
selectedPlanCtlgMaxLevel: null,
|
||||
selectedPlanCtlgLevel: null,
|
||||
selectedPlanCtlgSort: null,
|
||||
selectedPlanCtlgMenuItems: null,
|
||||
gantt: {},
|
||||
showPlanList: false,
|
||||
selectedTaskDetail: null,
|
||||
selectedTaskDetailType: null
|
||||
}));
|
||||
};
|
||||
|
||||
//Сброс выбора каталога планов
|
||||
const unselectPlan = () =>
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selectedPlanCtlgSpecsLoaded: false,
|
||||
selectedPlanCtlg: null,
|
||||
selectedPlanCtlgMaxLevel: null,
|
||||
selectedPlanCtlgLevel: null,
|
||||
selectedPlanCtlgSort: null,
|
||||
selectedPlanCtlgMenuItems: null,
|
||||
gantt: {},
|
||||
showPlanList: false,
|
||||
selectedTaskDetail: null,
|
||||
selectedTaskDetailType: null
|
||||
}));
|
||||
|
||||
//Загрузка списка спецификаций каталога планов
|
||||
const loadPlanCtglSpecs = useCallback(
|
||||
async (level = null, sort = null) => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_GET",
|
||||
args: {
|
||||
NCRN: state.selectedPlanCtlg,
|
||||
CFCPRODPLANS: {
|
||||
VALUE: state.selectedPlans.length > 0 ? state.selectedPlans.join(";") : null,
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
},
|
||||
NLEVEL: level,
|
||||
SSORT_FIELD: sort,
|
||||
NFCPRODPLANSP: state.planSpec
|
||||
}
|
||||
args: { NCRN: state.selectedPlanCtlg, NLEVEL: level, SSORT_FIELD: sort, NFCPRODPLANSP: state.planSpec }
|
||||
});
|
||||
let doc = await parseProdPlanSpXML(data.COUT);
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selectedPlanCtlgMaxLevel: data.NMAX_LEVEL,
|
||||
selectedPlanCtlgLevel: level || level === 0 ? level : data.NMAX_LEVEL,
|
||||
selectedPlanCtlgOutOfLimit: data.NOUT_OF_LIMIT,
|
||||
selectedPlanCtlgSort: sort,
|
||||
selectedPlanCtlgMenuItems: [...Array(data.NMAX_LEVEL).keys()].map(el => el + 1),
|
||||
selectedPlanCtlgMenuItems: state.selectedPlanCtlgMenuItems
|
||||
? state.selectedPlanCtlgMenuItems
|
||||
: [...Array(data.NMAX_LEVEL).keys()].map(el => el + 1),
|
||||
selectedPlanCtlgSpecsLoaded: true,
|
||||
gantt: { ...doc, tasks: [...(doc?.tasks || [])] },
|
||||
loadedCtlg: state.selectedPlanCtlg,
|
||||
loadedPlans: [...state.selectedPlans],
|
||||
loadedElements: state.selectedPlansElements,
|
||||
showPlanList: false
|
||||
gantt: { ...doc, tasks: [...(doc?.tasks || [])] }
|
||||
}));
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[executeStored, state.selectedPlanCtlg, state.selectedPlans, state.planSpec]
|
||||
[executeStored, state.ident, state.selectedPlanCtlg, state.planSpec]
|
||||
);
|
||||
|
||||
//Обработка нажатия на элемент в списке каталогов планов
|
||||
const handleCtlgClick = project => {
|
||||
//Если этот каталог не был выбран
|
||||
if (state.selectedPlanCtlg != project.NRN) {
|
||||
//Если выбран уже загруженный - укажем информацию о том, как он загружен
|
||||
if (project.NRN === state.loadedCtlg) {
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selectedPlanCtlg: project.NRN,
|
||||
selectedPlans: [...pv.loadedPlans],
|
||||
selectedPlansElements: pv.loadedElements
|
||||
}));
|
||||
} else {
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selectedPlanCtlg: project.NRN,
|
||||
selectedPlans: project.XCRN_PLANS.length === 1 ? [project.XCRN_PLANS[0].NRN] : [],
|
||||
selectedPlansElements: 0
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
setState(pv => ({ ...pv, selectedPlanCtlg: null, selectedPlans: [], selectedPlansElements: 0 }));
|
||||
}
|
||||
};
|
||||
|
||||
//Обработка нажатия на элемент в списке планов каталога
|
||||
const handleCtlgPlanClick = plan => {
|
||||
//Считываем обновленную информацию об отмеченных планах
|
||||
let newPlansInfo = updateCtlgPlanInfo(state.selectedPlans, plan);
|
||||
//Обновляем список отмеченных планов
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selectedPlans: [...newPlansInfo.selectedPlans],
|
||||
selectedPlansElements: pv.selectedPlansElements + newPlansInfo.selectedPlansElements
|
||||
}));
|
||||
};
|
||||
|
||||
//Обработка нажатия "ОК" при отборе планов
|
||||
const handleSelectedPlansOk = () => {
|
||||
//Загружаем диаграмму
|
||||
loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
|
||||
};
|
||||
|
||||
//Обработка нажатия "Отмена" при отборе планов
|
||||
const handleSelectedPlansCancel = () => {
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selectedPlanCtlg: pv.loadedCtlg,
|
||||
selectedPlans: [...pv.loadedPlans] || [],
|
||||
selectedPlansElements: pv.loadedElements,
|
||||
showPlanList: false
|
||||
}));
|
||||
const handleProjectClick = project => {
|
||||
if (state.selectedPlanCtlg != project.NRN) selectPlan(project.NRN);
|
||||
else unselectPlan();
|
||||
};
|
||||
|
||||
//При подключении компонента к странице
|
||||
@ -509,8 +345,8 @@ const MechRecCostProdPlans = () => {
|
||||
|
||||
//При смене выбранного каталога плана или при явном указании позиции спецификации плана
|
||||
useEffect(() => {
|
||||
if (state.planSpec) loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
|
||||
}, [state.planSpec, loadPlanCtglSpecs]);
|
||||
if (state.selectedPlanCtlg || state.planSpec) loadPlanCtglSpecs(null, SORT_REP_DATE_TO);
|
||||
}, [state.selectedPlanCtlg, state.planSpec, loadPlanCtglSpecs]);
|
||||
|
||||
//Выбор уровня
|
||||
const handleChangeSelectLevel = selectedLevel => {
|
||||
@ -534,17 +370,6 @@ const MechRecCostProdPlans = () => {
|
||||
setState(pv => ({ ...pv, selectedTaskDetail: taskRn, selectedTaskDetailType: taskType }));
|
||||
};
|
||||
|
||||
//При открытии окна информации об ограничении уровня
|
||||
const handleLevelLimitInfoOpen = () => {
|
||||
//Отображаем информацию
|
||||
showMsgInfo(
|
||||
`Размер производственной программы превышает предельно допустимый для одновременного отображения в виде диаграммы Ганта.
|
||||
Доступные для просмотра уровни вложенности ограничены.
|
||||
Вы можете просматривать производственную программу частями, используя действие "Открытие панели Производственная программа" в спецификации "Выпуск"
|
||||
раздела "Планы и отчеты производства изделий".`
|
||||
);
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box>
|
||||
@ -553,18 +378,18 @@ const MechRecCostProdPlans = () => {
|
||||
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
|
||||
Каталоги планов
|
||||
</Fab>
|
||||
<Drawer anchor={"left"} open={state.showPlanList} onClose={handleSelectedPlansCancel} sx={STYLES.PLANS_DRAWER}>
|
||||
<Drawer
|
||||
anchor={"left"}
|
||||
open={state.showPlanList}
|
||||
onClose={() => setState(pv => ({ ...pv, showPlanList: false }))}
|
||||
sx={STYLES.PLANS_DRAWER}
|
||||
>
|
||||
<PlanCtlgsList
|
||||
planCtlgs={filteredPlanCtgls}
|
||||
selectedPlans={state.selectedPlans}
|
||||
selectedPlanCtlg={state.selectedPlanCtlg}
|
||||
selectedPlansElements={state.selectedPlansElements}
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
onCtlgClick={handleCtlgClick}
|
||||
onCtlgPlanClick={handleCtlgPlanClick}
|
||||
onCtlgPlansOk={handleSelectedPlansOk}
|
||||
onCtlgPlansCancel={handleSelectedPlansCancel}
|
||||
onClick={handleProjectClick}
|
||||
/>
|
||||
</Drawer>
|
||||
</>
|
||||
@ -609,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}
|
||||
@ -646,14 +463,14 @@ const MechRecCostProdPlans = () => {
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
) : !state.loadedCtlg ? (
|
||||
) : !state.selectedPlanCtlg ? (
|
||||
<Box pt={3}>
|
||||
<InlineMsgInfo
|
||||
okBtn={false}
|
||||
text={
|
||||
state.planSpec
|
||||
? "Загружаю график для выбранной позиции плана..."
|
||||
: "Укажите каталог планов или планы для отображения их спецификаций"
|
||||
: "Укажите каталог планов для отображения их спецификаций"
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useEffect, useContext, useCallback } from "react"; //Классы React
|
||||
import React, { useState, useEffect, useContext } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { object2Base64XML, formatDateRF } from "../../core/utils"; //Вспомогательные функции
|
||||
|
||||
@ -32,60 +32,17 @@ export const useFilteredPlans = (plans, filter) => {
|
||||
return filteredPlans;
|
||||
};
|
||||
|
||||
//Хук для планов
|
||||
const useDeptCostProdPlans = month => {
|
||||
//Собственное состояние - таблица данных
|
||||
const [state, setState] = useState({
|
||||
init: false,
|
||||
loaded: false,
|
||||
showPlanList: false,
|
||||
rows: [],
|
||||
reload: true,
|
||||
selected: {},
|
||||
currentMonth: ""
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При подключении компонента к странице
|
||||
useEffect(() => {
|
||||
const initPlans = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_DEPT_INIT",
|
||||
args: { SMONTH: month },
|
||||
respArg: "COUT",
|
||||
isArray: name => name === "XFCPRODPLANS",
|
||||
attributeValueProcessor: (name, val) => (name === "SPERIOD" ? undefined : val)
|
||||
});
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
init: true,
|
||||
rows: [...(data?.XFCPRODPLANS || [])],
|
||||
loaded: true,
|
||||
reload: false,
|
||||
selected: {},
|
||||
currentMonth: month
|
||||
}));
|
||||
};
|
||||
//Если месяц указан и он не соответствует текущим данным
|
||||
if (month && month !== state.currentMonth) {
|
||||
initPlans();
|
||||
}
|
||||
}, [executeStored, month, state.currentMonth]);
|
||||
|
||||
//Возвращаем данные
|
||||
return [state, setState];
|
||||
};
|
||||
|
||||
//Хук для информации о плане
|
||||
const useDeptCostProdPlanInfo = plan => {
|
||||
//Хук для основной таблицы
|
||||
const useDeptCostProdPlans = () => {
|
||||
//Собственное состояние - таблица данных
|
||||
const [state, setState] = useState({
|
||||
init: false,
|
||||
showPlanList: false,
|
||||
showIncomeFromDeps: null,
|
||||
showFcroutelst: null,
|
||||
planList: [],
|
||||
planListLoaded: false,
|
||||
selectedPlan: {},
|
||||
dataLoaded: false,
|
||||
columnsDef: [],
|
||||
orders: null,
|
||||
@ -100,43 +57,14 @@ const useDeptCostProdPlanInfo = plan => {
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости очистки данных о плане
|
||||
const handleClear = useCallback(
|
||||
() =>
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
init: false,
|
||||
showPlanList: false,
|
||||
showIncomeFromDeps: null,
|
||||
showFcroutelst: null,
|
||||
dataLoaded: false,
|
||||
columnsDef: [],
|
||||
orders: null,
|
||||
rows: [],
|
||||
reload: true,
|
||||
pageNumber: 1,
|
||||
morePages: true,
|
||||
fixedHeader: false,
|
||||
fixedColumns: 0
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
//При изменении состояния сортировки
|
||||
const handleOrderChanged = ({ orders }) => setState(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
|
||||
|
||||
//При изменении количества отображаемых страниц
|
||||
const handlePagesCountChanged = () => setState(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
|
||||
|
||||
//При необходимости обновить данные таблицы
|
||||
useEffect(() => {
|
||||
//Если план выбран
|
||||
if (plan.NRN) {
|
||||
if (state.selectedPlan.NRN) {
|
||||
const loadData = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLANSP_DEPT_DG_GET",
|
||||
args: {
|
||||
NFCPRODPLAN: plan.NRN,
|
||||
NFCPRODPLAN: state.selectedPlan.NRN,
|
||||
CORDERS: { VALUE: object2Base64XML(state.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB },
|
||||
NPAGE_NUMBER: state.pageNumber,
|
||||
NPAGE_SIZE: DATA_GRID_PAGE_SIZE_LARGE,
|
||||
@ -148,7 +76,6 @@ const useDeptCostProdPlanInfo = plan => {
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
...data.XDATA_GRID,
|
||||
init: true,
|
||||
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,
|
||||
@ -156,20 +83,31 @@ const useDeptCostProdPlanInfo = plan => {
|
||||
morePages: (data.XDATA_GRID.rows || []).length >= DATA_GRID_PAGE_SIZE_LARGE
|
||||
}));
|
||||
};
|
||||
//Если необходимо перезагрузить
|
||||
if (state.reload) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
//Если план не выбран и есть какие-то данные
|
||||
if (!plan.NRN && state.dataLoaded) {
|
||||
//Очищаем их
|
||||
handleClear();
|
||||
}
|
||||
}, [plan.NRN, state.reload, state.orders, state.pageNumber, state.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB, handleClear]);
|
||||
}, [state.selectedPlan, state.reload, state.orders, state.pageNumber, state.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB]);
|
||||
|
||||
//Возвращаем данные
|
||||
return [state, setState, handleClear, handleOrderChanged, handlePagesCountChanged];
|
||||
//При подключении компонента к странице
|
||||
useEffect(() => {
|
||||
const initPlans = async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_MECHREC.FCPRODPLAN_DEPT_INIT",
|
||||
args: {},
|
||||
respArg: "COUT",
|
||||
isArray: name => name === "XFCPRODPLANS",
|
||||
attributeValueProcessor: (name, val) => (name === "SPERIOD" ? undefined : val)
|
||||
});
|
||||
setState(pv => ({ ...pv, init: true, planList: [...(data?.XFCPRODPLANS || [])], planListLoaded: true }));
|
||||
};
|
||||
if (!state.init) {
|
||||
initPlans();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return [state, setState];
|
||||
};
|
||||
|
||||
//Хук для таблицы маршрутных листов
|
||||
@ -226,7 +164,6 @@ const useCostRouteLists = task => {
|
||||
task
|
||||
]);
|
||||
|
||||
//Возвращаем данные
|
||||
return [costRouteLists, setCostRouteLists];
|
||||
};
|
||||
|
||||
@ -286,7 +223,6 @@ const useCostRouteListsSpecs = mainRowRN => {
|
||||
mainRowRN
|
||||
]);
|
||||
|
||||
//Возвращаем данные
|
||||
return [costRouteListsSpecs, setCostRouteListsSpecs];
|
||||
};
|
||||
|
||||
@ -336,8 +272,7 @@ const useIncomFromDeps = task => {
|
||||
}
|
||||
}, [SERV_DATA_TYPE_CLOB, executeStored, incomFromDeps.dataLoaded, incomFromDeps.orders, incomFromDeps.pageNumber, incomFromDeps.reload, task]);
|
||||
|
||||
//Возвращаем данные
|
||||
return [incomFromDeps, setIncomFromDeps];
|
||||
};
|
||||
|
||||
export { useDeptCostProdPlans, useDeptCostProdPlanInfo, useCostRouteLists, useCostRouteListsSpecs, useIncomFromDeps };
|
||||
export { useDeptCostProdPlans, useCostRouteLists, useCostRouteListsSpecs, useIncomFromDeps };
|
||||
|
||||
@ -12,7 +12,7 @@ import PropTypes from "prop-types"; //Контроль свойств компо
|
||||
import { Drawer, Fab, Box, List, ListItemButton, ListItemText, Typography, TextField, Link, Grid } from "@mui/material"; //Интерфейсные элементы
|
||||
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Заголовок страницы
|
||||
import { useDeptCostProdPlans, useFilteredPlans, useDeptCostProdPlanInfo } from "./hooks"; //Вспомогательные хуки
|
||||
import { useDeptCostProdPlans, useFilteredPlans } from "./hooks"; //Вспомогательные хуки
|
||||
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
@ -34,8 +34,7 @@ const TITLE_PADDING_BOTTOM = "20px";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
PLANS_FILTER: { paddingTop: "20px", display: "flex", flexDirection: "column", alignItems: "center", gap: "5px" },
|
||||
PLANS_FILTER_ITEM: { margin: "0px 10px", width: "93%" },
|
||||
PLANS_FINDER: { marginTop: "10px", marginLeft: "10px", width: "93%" },
|
||||
PLANS_LIST_ITEM_PRIMARY: { wordWrap: "break-word" },
|
||||
PLANS_BUTTON: { position: "absolute" },
|
||||
PLANS_DRAWER: {
|
||||
@ -61,18 +60,10 @@ const STYLES = {
|
||||
FACT_VALUE: { color: "blue" }
|
||||
};
|
||||
|
||||
//Имена полей компонента
|
||||
const SFIELD_MONTH = "month";
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Считывание текущего года и месяца в формате "YYYY-MM"
|
||||
const getCurrentYearMonth = () => {
|
||||
return `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
//Генерация представления ячейки заголовка группы
|
||||
export const groupCellRender = ({ group }) => ({
|
||||
cellStyle: STYLES.DATA_GRID_GROUP_CELL,
|
||||
@ -97,7 +88,7 @@ const getRowBackgroudColor = row => {
|
||||
};
|
||||
|
||||
//Генерация заливки строки исходя от значений
|
||||
const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick }) => {
|
||||
const dataCellRender = ({ row, columnDef, handleProdOrderClick, handleMatresCodeClick }) => {
|
||||
//Описываем общие свойства
|
||||
let cellProps = { title: row[columnDef.name] };
|
||||
//Описываем общий стиль
|
||||
@ -136,7 +127,7 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
|
||||
cellProps,
|
||||
cellStyle,
|
||||
data: (
|
||||
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => onProdOrderClick(row["NRN"])}>
|
||||
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => handleProdOrderClick(row["NRN"])}>
|
||||
{row[columnDef.name]}
|
||||
</Link>
|
||||
)
|
||||
@ -148,7 +139,7 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
|
||||
cellProps,
|
||||
cellStyle: STYLES.DATA_GRID_CELL_MATRES_CODE(cellStyle, row),
|
||||
data: (
|
||||
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => onMatresCodeClick(row["NRN"])}>
|
||||
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => handleMatresCodeClick(row["NRN"])}>
|
||||
{row[columnDef.name]}
|
||||
</Link>
|
||||
)
|
||||
@ -158,38 +149,21 @@ const dataCellRender = ({ row, columnDef, onProdOrderClick, onMatresCodeClick })
|
||||
};
|
||||
|
||||
//Список каталогов планов
|
||||
const PlanList = ({ plans = [], selectedPlan, filter, onFilterChange, onClick } = {}) => {
|
||||
//При изменении фильтра
|
||||
const handleFilterChange = e => {
|
||||
onFilterChange && onFilterChange(e);
|
||||
};
|
||||
|
||||
const PlanList = ({ plans = [], selectedPlan, filter, setFilter, onClick } = {}) => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div>
|
||||
<Box sx={STYLES.PLANS_FILTER}>
|
||||
<TextField
|
||||
sx={STYLES.PLANS_FILTER_ITEM}
|
||||
name={SFIELD_MONTH}
|
||||
label="Месяц"
|
||||
type="month"
|
||||
value={filter.month}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
variant="standard"
|
||||
fullWidth
|
||||
onChange={handleFilterChange}
|
||||
required={true}
|
||||
></TextField>
|
||||
<TextField
|
||||
sx={STYLES.PLANS_FILTER_ITEM}
|
||||
name="planName"
|
||||
label="План"
|
||||
value={filter.planName}
|
||||
variant="standard"
|
||||
fullWidth
|
||||
onChange={handleFilterChange}
|
||||
></TextField>
|
||||
</Box>
|
||||
<TextField
|
||||
sx={STYLES.PLANS_FINDER}
|
||||
name="planFilter"
|
||||
label="План"
|
||||
value={filter.planName}
|
||||
variant="standard"
|
||||
fullWidth
|
||||
onChange={event => {
|
||||
setFilter(pv => ({ ...pv, planName: event.target.value }));
|
||||
}}
|
||||
></TextField>
|
||||
<List>
|
||||
{plans.map(p => (
|
||||
<ListItemButton key={p.NRN} selected={p.NRN === selectedPlan.NRN} onClick={() => (onClick ? onClick(p) : null)}>
|
||||
@ -207,7 +181,7 @@ PlanList.propTypes = {
|
||||
selectedPlan: PropTypes.object,
|
||||
onClick: PropTypes.func,
|
||||
filter: PropTypes.object,
|
||||
onFilterChange: PropTypes.func
|
||||
setFilter: PropTypes.func
|
||||
};
|
||||
|
||||
//-----------
|
||||
@ -216,135 +190,135 @@ PlanList.propTypes = {
|
||||
|
||||
//Корневая панель производственного плана цеха
|
||||
const MechRecDeptCostProdPlans = () => {
|
||||
//Собственное состояние - таблица данных
|
||||
const [state, setState] = useDeptCostProdPlans();
|
||||
|
||||
//Состояние для фильтра каталогов
|
||||
const [filter, setFilter] = useState({ planName: "", month: getCurrentYearMonth() });
|
||||
|
||||
//Собственное состояние - таблица планов
|
||||
const [plans, setPlans] = useDeptCostProdPlans(filter.month);
|
||||
|
||||
//Собственное состояние - таблица информации
|
||||
const [planInfo, setPlanInfo, onPlanInfoClear, onPlanInfoOrderChanged, onPlanInfoPagesCountChanged] = useDeptCostProdPlanInfo(plans.selected);
|
||||
const [filter, setFilter] = useState({ planName: "" });
|
||||
|
||||
//Массив отфильтрованных каталогов
|
||||
const filteredPlanCtgls = useFilteredPlans(plans.rows, filter);
|
||||
const filteredPlanCtgls = useFilteredPlans(state.planList, filter);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { InlineMsgInfo } = useContext(MessagingСtx);
|
||||
|
||||
//Выбор плана
|
||||
const selectPlan = plan => {
|
||||
setPlans(pv => ({
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selected: plan,
|
||||
showPlanList: false
|
||||
showIncomeFromDeps: null,
|
||||
showFcroutelst: null,
|
||||
selectedPlan: plan,
|
||||
showPlanList: false,
|
||||
dataLoaded: false,
|
||||
columnsDef: [],
|
||||
orders: null,
|
||||
rows: [],
|
||||
reload: true,
|
||||
pageNumber: 1,
|
||||
morePages: true
|
||||
}));
|
||||
onPlanInfoClear();
|
||||
};
|
||||
|
||||
//Сброс выбора плана
|
||||
const unselectPlan = () => {
|
||||
setPlans(pv => ({
|
||||
const unselectPlan = () =>
|
||||
setState(pv => ({
|
||||
...pv,
|
||||
selected: {},
|
||||
showPlanList: false
|
||||
showIncomeFromDeps: null,
|
||||
showFcroutelst: null,
|
||||
selectedPlan: {},
|
||||
showPlanList: false,
|
||||
dataLoaded: false,
|
||||
columnsDef: [],
|
||||
orders: null,
|
||||
rows: [],
|
||||
reload: true,
|
||||
pageNumber: 1,
|
||||
morePages: true
|
||||
}));
|
||||
onPlanInfoClear();
|
||||
};
|
||||
|
||||
//Обработка нажатия на элемент в списке планов
|
||||
const handlePlanClick = plan => {
|
||||
if (plans.selected.NRN != plan.NRN) selectPlan(plan);
|
||||
if (state.selectedPlan.NRN != plan.NRN) selectPlan(plan);
|
||||
else unselectPlan();
|
||||
};
|
||||
|
||||
//При изменении состояния сортировки информации плана
|
||||
const handlePlanInfoOrderChanged = ({ orders }) => onPlanInfoOrderChanged({ orders });
|
||||
//При изменении состояния сортировки
|
||||
const handleOrderChanged = ({ orders }) => setState(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true }));
|
||||
|
||||
//При изменении количества отображаемых страниц информации плана
|
||||
const handlePlanInfoPagesCountChanged = () => onPlanInfoPagesCountChanged();
|
||||
//При изменении количества отображаемых страниц
|
||||
const handlePagesCountChanged = () => setState(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true }));
|
||||
|
||||
//При нажатии на "Заказ"
|
||||
const handleProdOrderClick = planSp => {
|
||||
setPlanInfo(pv => ({ ...pv, showIncomeFromDeps: planSp }));
|
||||
setState(pv => ({ ...pv, showIncomeFromDeps: planSp }));
|
||||
};
|
||||
|
||||
//При нажатии на "Обозначение"
|
||||
const handleMatresCodeClick = planSp => {
|
||||
setPlanInfo(pv => ({ ...pv, showFcroutelst: planSp }));
|
||||
setState(pv => ({ ...pv, showFcroutelst: planSp }));
|
||||
};
|
||||
|
||||
//При изменении фильтров
|
||||
const handleFilterChange = e =>
|
||||
setFilter(pv => ({ ...pv, [e.target.name]: e.target.name === SFIELD_MONTH && !e.target.value ? getCurrentYearMonth() : e.target.value }));
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box p={2}>
|
||||
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setPlans(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
|
||||
<Fab variant="extended" sx={STYLES.PLANS_BUTTON} onClick={() => setState(pv => ({ ...pv, showPlanList: !pv.showPlanList }))}>
|
||||
Планы
|
||||
</Fab>
|
||||
<Drawer
|
||||
anchor={"left"}
|
||||
open={plans.showPlanList}
|
||||
onClose={() => setPlans(pv => ({ ...pv, showPlanList: false }))}
|
||||
open={state.showPlanList}
|
||||
onClose={() => setState(pv => ({ ...pv, showPlanList: false }))}
|
||||
sx={STYLES.PLANS_DRAWER}
|
||||
>
|
||||
<PlanList
|
||||
plans={filteredPlanCtgls}
|
||||
selectedPlan={plans.selected}
|
||||
selectedPlan={state.selectedPlan}
|
||||
filter={filter}
|
||||
onFilterChange={handleFilterChange}
|
||||
setFilter={setFilter}
|
||||
onClick={handlePlanClick}
|
||||
/>
|
||||
</Drawer>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Box display="flex" justifyContent="center" alignItems="center">
|
||||
{planInfo.dataLoaded ? (
|
||||
planInfo.rows.length === 0 ? (
|
||||
{state.dataLoaded ? (
|
||||
state.rows.length === 0 ? (
|
||||
<InlineMsgInfo okBtn={false} text={"В плане отсутствуют записи спецификации"} />
|
||||
) : (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
<Typography sx={STYLES.TITLE} variant={"h6"}>
|
||||
{`Производственный план цеха №${plans.selected.SSUBDIV} на ${plans.selected.SPERIOD}`}
|
||||
{`Производственный план цеха №${state.selectedPlan.SSUBDIV} на ${state.selectedPlan.SPERIOD}`}
|
||||
</Typography>
|
||||
<Box>
|
||||
<P8PDataGrid
|
||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||
containerComponentProps={{ elevation: 6, sx: STYLES.DATA_GRID_CONTAINER }}
|
||||
fixedHeader={planInfo.fixedHeader}
|
||||
fixedColumns={planInfo.fixedColumns}
|
||||
columnsDef={planInfo.columnsDef}
|
||||
rows={planInfo.rows}
|
||||
fixedHeader={state.fixedHeader}
|
||||
fixedColumns={state.fixedColumns}
|
||||
columnsDef={state.columnsDef}
|
||||
rows={state.rows}
|
||||
size={P8P_DATA_GRID_SIZE.MEDIUM}
|
||||
morePages={planInfo.morePages}
|
||||
reloading={planInfo.reload}
|
||||
onOrderChanged={handlePlanInfoOrderChanged}
|
||||
onPagesCountChanged={handlePlanInfoPagesCountChanged}
|
||||
dataCellRender={prms =>
|
||||
dataCellRender({
|
||||
...prms,
|
||||
onProdOrderClick: handleProdOrderClick,
|
||||
onMatresCodeClick: handleMatresCodeClick
|
||||
})
|
||||
}
|
||||
morePages={state.morePages}
|
||||
reloading={state.reload}
|
||||
onOrderChanged={handleOrderChanged}
|
||||
onPagesCountChanged={handlePagesCountChanged}
|
||||
dataCellRender={prms => dataCellRender({ ...prms, handleProdOrderClick, handleMatresCodeClick })}
|
||||
groupCellRender={groupCellRender}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
) : !plans.selected.NRN ? (
|
||||
) : !state.selectedPlan.NRN ? (
|
||||
<InlineMsgInfo okBtn={false} text={"Укажите план для отображения спецификаций"} />
|
||||
) : null}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{planInfo.showIncomeFromDeps ? (
|
||||
<IncomFromDepsDataGridDialog task={planInfo.showIncomeFromDeps} onClose={() => handleProdOrderClick(null)} />
|
||||
) : null}
|
||||
{planInfo.showFcroutelst ? (
|
||||
<CostRouteListsDataGridDialog task={planInfo.showFcroutelst} onClose={() => handleMatresCodeClick(null)} />
|
||||
{state.showIncomeFromDeps ? (
|
||||
<IncomFromDepsDataGridDialog task={state.showIncomeFromDeps} onClose={() => handleProdOrderClick(null)} />
|
||||
) : null}
|
||||
{state.showFcroutelst ? <CostRouteListsDataGridDialog task={state.showFcroutelst} onClose={() => handleMatresCodeClick(null)} /> : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,51 +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"; //Хуки компонентов
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Редактор свойств компонента панели
|
||||
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>
|
||||
{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 };
|
||||
@ -1,83 +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"; //Хуки компонентов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
COMPONENT_BOX: isDragging => {
|
||||
return isDragging ? { pointerEvents: "none" } : {};
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Представление компонента панели
|
||||
const ComponentView = ({ id, isDragging, path, settings = {}, values = {}, onValuesChange = null } = {}) => {
|
||||
//Подгрузка модуля представления компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
|
||||
const [ComponentView, init] = useComponentModule({ path, module: "view" });
|
||||
|
||||
//При смене значений
|
||||
const handleValuesChange = values => onValuesChange && onValuesChange(values);
|
||||
|
||||
//Расчёт флага наличия компонента
|
||||
const haveComponent = path ? true : false;
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Box className={"component-view__wrap"} sx={STYLES.COMPONENT_BOX(isDragging)}>
|
||||
{haveComponent && init && <ComponentView.default id={id} {...settings} values={values} onValuesChange={handleValuesChange} />}
|
||||
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - компонент панели
|
||||
ComponentView.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
isDragging: PropTypes.bool.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>
|
||||
);
|
||||
};
|
||||
*/
|
||||
@ -1,51 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: График (общие ресурсы действия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Собственные значения типа действия
|
||||
const P8P_CA_CHART_TYPE_VALUE = {
|
||||
GRPH_ELEMENT: "Элемент графика"
|
||||
};
|
||||
|
||||
//Типы значений действий графика
|
||||
const P8P_CA_CHART_VALUE_TYPES = [...Object.values(P8P_CA_CHART_TYPE_VALUE)];
|
||||
|
||||
//Доступные области действий графика
|
||||
const P8P_CA_CHART_ACTION_AREAS = [
|
||||
{ name: "Компонент", area: "component", hasElement: false },
|
||||
{ name: "Элемент графика", area: "chart_item", hasElement: false }
|
||||
];
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Получение значения собственного типа действия
|
||||
const getChartCustomTypeValue = ({ type, value, prms }) => {
|
||||
//Если это элемента графика - возвращаем нужное значение, иначе - null
|
||||
return type === P8P_CA_CHART_TYPE_VALUE.GRPH_ELEMENT ? prms.item[value] : null;
|
||||
};
|
||||
|
||||
//Считывание обработчиков графика
|
||||
const getChartHandlers = handlers => {
|
||||
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||
return {
|
||||
onComponentClick: handlers["component."]?.fn,
|
||||
onChartItemClick: handlers["chart_item."]?.fn
|
||||
};
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CA_CHART_VALUE_TYPES, P8P_CA_CHART_ACTION_AREAS, getChartCustomTypeValue, getChartHandlers };
|
||||
@ -1,73 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: График (редактор настроек)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { P8P_CA_CHART_VALUE_TYPES, P8P_CA_CHART_ACTION_AREAS } from "./action"; //Общие ресурсы действий графика
|
||||
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//График (редактор настроек)
|
||||
const ChartEditor = ({ id, dataSource = null, valueProviders = {}, actions = P8P_CAS_INITIAL, onSettingsChange = null } = {}) => {
|
||||
//Собственное состояние - текущие настройки
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
//При сохранении изменений элемента
|
||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||
|
||||
//При изменении действий
|
||||
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||
|
||||
//При сохранении настроек
|
||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource, actions });
|
||||
}, [settings, id, dataSource, actions]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PEditorBox title={"Параметры графика"} onSave={handleSave}>
|
||||
<P8PEditorSubHeader title={"Источник данных"} />
|
||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||
<P8PEditorSubHeader title={"Действия"} />
|
||||
<P8PActions
|
||||
actions={settings?.actions}
|
||||
valueProviders={valueProviders}
|
||||
areas={P8P_CA_CHART_ACTION_AREAS}
|
||||
valueTypes={P8P_CA_CHART_VALUE_TYPES}
|
||||
onChange={handleActionsChange}
|
||||
/>
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - График (редактор настроек)
|
||||
ChartEditor.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onSettingsChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export default ChartEditor;
|
||||
@ -1,101 +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 { useDataSource, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import {
|
||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||
P8P_COMPONENT_INLINE_MESSAGE,
|
||||
P8PComponentInlineMessage
|
||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||
import { P8P_CA_SHAPE } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { getChartCustomTypeValue, getChartHandlers } from "./action"; //Общие ресурсы действий графика
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Иконка компонента
|
||||
const COMPONENT_ICON = "bar_chart";
|
||||
|
||||
//Наименование компонента
|
||||
const COMPONENT_NAME = "График";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CHART: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", display: "flex" },
|
||||
CONTAINER: clickable => ({
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//График (представление)
|
||||
const Chart = ({ dataSource = null, values = {}, actions = [], onValuesChange = null } = {}) => {
|
||||
//Собственное состояние - данные
|
||||
const [chart, error, haveConfing, haveData] = useDataSource({ dataSource, values, componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.CHART });
|
||||
|
||||
//Собственное состояние - обработчики компонента
|
||||
const [handlers] = useComponentHandlers({ actions, onValuesChange, getCustomTypeValue: getChartCustomTypeValue });
|
||||
|
||||
//Обработчики областей
|
||||
const { onComponentClick, onChartItemClick } = getChartHandlers(handlers);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Paper
|
||||
sx={STYLES.CONTAINER(onComponentClick)}
|
||||
className={"component-view__container component-view__container__empty"}
|
||||
elevation={6}
|
||||
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||
>
|
||||
{haveConfing && haveData ? (
|
||||
<P8PChart
|
||||
style={STYLES.CHART}
|
||||
{...chart}
|
||||
options={{ responsive: true, maintainAspectRatio: false }}
|
||||
onClick={prms => onChartItemClick && onChartItemClick({ values, prms })}
|
||||
/>
|
||||
) : (
|
||||
<P8PComponentInlineMessage
|
||||
icon={COMPONENT_ICON}
|
||||
name={COMPONENT_NAME}
|
||||
message={!haveConfing ? P8P_COMPONENT_INLINE_MESSAGE.NO_SETTINGS : error ? error : P8P_COMPONENT_INLINE_MESSAGE.NO_DATA_FOUND}
|
||||
type={error ? P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR : P8P_COMPONENT_INLINE_MESSAGE_TYPE.COMMON}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - График (представление)
|
||||
Chart.propTypes = {
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
values: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onValuesChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export default Chart;
|
||||
@ -1,57 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Описание
|
||||
*/
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
const COMPONENTS = [
|
||||
{
|
||||
name: "Форма",
|
||||
path: "form",
|
||||
settings: {
|
||||
id: "",
|
||||
title: "",
|
||||
autoApply: false,
|
||||
orientation: "v",
|
||||
items: []
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "График",
|
||||
path: "chart",
|
||||
settings: {
|
||||
id: "",
|
||||
dataSource: {},
|
||||
actions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Таблица",
|
||||
path: "table",
|
||||
settings: {
|
||||
id: "",
|
||||
dataSource: {},
|
||||
actions: [],
|
||||
conditions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Индикатор",
|
||||
path: "indicator",
|
||||
settings: {
|
||||
id: "",
|
||||
dataSource: {},
|
||||
actions: [],
|
||||
conditions: []
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { COMPONENTS };
|
||||
@ -1,639 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Хуки компонентов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useEffect, useContext, useCallback, useLayoutEffect } from "react"; //Классы React
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений
|
||||
import { object2Base64XML, genUID, xml2JSON } from "../../../core/utils"; //Вспомогательные функции
|
||||
import { exportXMLFile } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
import { P8P_COMPONENT_SETTINGS_PATHS } from "../../../components/editors/p8p_component_settings"; //Дополнительные настройки источников
|
||||
import { getActionsVariables } from "../../../components/editors/p8p_component_action/util"; //Вспомогательный функционал действий компонентов
|
||||
import { P8P_CA_TYPE } from "../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Начальное состояние размера макета
|
||||
const INITIAL_BREAKPOINT = "lg";
|
||||
|
||||
//Начальное состояние макета
|
||||
const INITIAL_LAYOUTS = {
|
||||
[INITIAL_BREAKPOINT]: []
|
||||
};
|
||||
|
||||
//---------
|
||||
//Константы расчета высоты строки ResponsiveGridLayout
|
||||
//---------
|
||||
|
||||
//Структура параметров для расчета высоты строки ResponsiveGridLayout
|
||||
const GRID_LAYOUT_PARAMS = {
|
||||
//Высота главного меню (px)
|
||||
APP_BAR_HEIGHT: 64,
|
||||
//Дополнительный отступ (px)
|
||||
SAFE_OFFSET: 20,
|
||||
//Максимальное количество строк (число)
|
||||
MAX_ROWS: 68,
|
||||
//Высота отступа (px)
|
||||
MARGIN: 10
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Проверка на массив загружаемых данных панели
|
||||
const isArrayPanelDesc = (name, jPath) =>
|
||||
["items", "arguments", "conditions", "actions", "dependencies", "inputParams"].includes(name) || /(.*)XLAYOUTS\.[^.]*$/.test(jPath);
|
||||
|
||||
//Обработка значений тэгов загружаемых данных панели
|
||||
const tagValueProcessorPanelDesc = (name, val, jPath) =>
|
||||
["condValue", "resValue", "description"].includes(name)
|
||||
? undefined
|
||||
: /(.*)dataSource.arguments.value$/.test(jPath) || /(.*)XVALUE_PROVIDERS(.*)$/.test(jPath)
|
||||
? undefined
|
||||
: val;
|
||||
|
||||
//Конвертация серверного описания компонентов в данные для редактора панелей
|
||||
const convertServerData2Components = components => {
|
||||
//Корректировка информации о действия (Для типа "setVariable" значение ключа "params" - обязательно массив, в иных случаях - объект)
|
||||
const correctionActions = actions => {
|
||||
return actions.reduce(
|
||||
(prevActions, action) => [
|
||||
...prevActions,
|
||||
{
|
||||
...(action.type === P8P_CA_TYPE.setVariable.code && !Array.isArray(action.params)
|
||||
? { ...action, params: [{ ...action.params }] }
|
||||
: { ...action })
|
||||
}
|
||||
],
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
//Форматируем устанавливая пустой объект для dataSource, если он пуст
|
||||
return Object.keys(components).reduce(
|
||||
(prev, cur) => ({
|
||||
...prev,
|
||||
[cur]: {
|
||||
...components[cur],
|
||||
settings: {
|
||||
...components[cur].settings,
|
||||
//dataSource - обязательно объект
|
||||
...(components[cur].settings?.dataSource ? { dataSource: components[cur].settings.dataSource || {} } : {}),
|
||||
//actions - требуют корректировки
|
||||
...(components[cur].settings?.actions
|
||||
? {
|
||||
actions: correctionActions(components[cur].settings.actions)
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}
|
||||
}),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
//Конвертация серверного описания проводников значений в данные для редактора панелей
|
||||
const convertServerData2ValueProviders = valueProviders => {
|
||||
//Форматируем инициализируя dependencies, если требуется
|
||||
return Object.keys(valueProviders).reduce(
|
||||
(prev, cur) => ({ ...prev, [cur]: { ...valueProviders[cur], dependencies: valueProviders[cur].dependencies || [] } }),
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
//Конвертация серверного описания панели в данные для редактора панелей
|
||||
const serverPanelData2PanelDesc = (components, valueProviders, layouts, breakpoint) => {
|
||||
//Возвращаем информацию о панеле с учетом конвертаций
|
||||
return {
|
||||
components: convertServerData2Components(components),
|
||||
valueProviders: convertServerData2ValueProviders(valueProviders),
|
||||
layouts: layouts[breakpoint] ? { [breakpoint]: [...layouts[breakpoint]] } : INITIAL_LAYOUTS,
|
||||
breakpoint: breakpoint
|
||||
};
|
||||
};
|
||||
|
||||
//Считывание общего списка зависимостей от проводника значений
|
||||
const getValueProvidersLinks = (componentPath, settings) => {
|
||||
//Если это индикатор/график/таблица
|
||||
if ([P8P_COMPONENT_SETTINGS_PATHS.INDICATOR, P8P_COMPONENT_SETTINGS_PATHS.CHART, P8P_COMPONENT_SETTINGS_PATHS.TABLE].includes(componentPath)) {
|
||||
//Собираем зависимости из настройки источника
|
||||
let argumentsSources = Array.isArray(settings?.dataSource?.arguments)
|
||||
? settings.dataSource.arguments.reduce((prev, cur) => (cur.valueSource ? [...prev, cur.valueSource] : [...prev]), [])
|
||||
: [];
|
||||
//Собираем зависимости из параметров действий
|
||||
let actionsSources = Array.isArray(settings?.actions) ? getActionsVariables(settings.actions) : [];
|
||||
//Возвращаем зависимости компонента
|
||||
return [...new Set([...argumentsSources, ...actionsSources])];
|
||||
}
|
||||
//Если это форма
|
||||
if (P8P_COMPONENT_SETTINGS_PATHS.FORM === componentPath) {
|
||||
//Собираем зависимости из элементов формы
|
||||
let items = Array.isArray(settings?.items) ? settings.items.map(item => item.name) : [];
|
||||
//Возвращаем зависимости компонента
|
||||
return [...new Set(items)];
|
||||
}
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Отложенная загрузка модуля компонента (как альтернативу можно применять 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 usePanel = () => {
|
||||
//Собственное состояние - рабочая панель
|
||||
const [panel, setPanel] = useState();
|
||||
//Собственное состояние - имя рабочей панели
|
||||
const [panelName, setPanelName] = useState(null);
|
||||
//Собственное состояние - наличие изменений рабочей панели
|
||||
const [isPanelChanged, setIsPanelChanged] = useState(false);
|
||||
//Собственное состояние - возможность редактирования
|
||||
const [isEditAvaliable, setIsEditAvaliable] = useState(true);
|
||||
//Собственное состояние - режим редактирования
|
||||
const [editMode, setEditMode] = useState(true);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//При необходимости загрузки информации о панели
|
||||
const loadPanel = async panel => {
|
||||
//Считываем информацию с сервера
|
||||
const res = await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_ATTRS_GET_BY_CODE",
|
||||
args: {
|
||||
SCODE: panel
|
||||
},
|
||||
respArg: "COUT",
|
||||
loader: true
|
||||
});
|
||||
//Устанавливаем рабочую панель
|
||||
setPanel(res.XPANEL_ATTRS.rn);
|
||||
//Устанавливаем наименование рабочей панели
|
||||
setPanelName(res.XPANEL_ATTRS.name);
|
||||
//Сбрасываем наличие изменений рабочей панели
|
||||
setIsPanelChanged(false);
|
||||
//Загружаемую панель запрещено редактировать
|
||||
setIsEditAvaliable(false);
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
//При выборе панели
|
||||
const selectPanel = (panel, panelName, isPanelEditAvaliable) => {
|
||||
//Обновляем информацию о панели
|
||||
setPanel(panel);
|
||||
setPanelName(panelName);
|
||||
setIsEditAvaliable(isPanelEditAvaliable);
|
||||
setEditMode(isPanelEditAvaliable);
|
||||
setIsPanelChanged(false);
|
||||
};
|
||||
|
||||
//При закрытии панели
|
||||
const closePanel = () => {
|
||||
setPanel(null);
|
||||
setPanelName(null);
|
||||
setIsPanelChanged(false);
|
||||
};
|
||||
|
||||
//При установке признака изменений панели
|
||||
const setPanelChanged = isChanged => setIsPanelChanged(isChanged);
|
||||
|
||||
//При установке признака режима редактирования
|
||||
const changeEditMode = isEditMode => setEditMode(isEditMode);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [panel, panelName, editMode, isEditAvaliable, isPanelChanged, loadPanel, selectPanel, closePanel, changeEditMode, setPanelChanged];
|
||||
};
|
||||
|
||||
//Работа с менеджером панелей
|
||||
const usePanelManager = () => {
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//Добавление панели
|
||||
const insertPanel = useCallback(
|
||||
async (code, name) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_INSERT", args: { SCODE: code, SNAME: name }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Изменение панели
|
||||
const updatePanel = useCallback(
|
||||
async (query, code, name) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_UPDATE", args: { NRN: query, SCODE: code, SNAME: name }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Удаление панели
|
||||
const deletePanel = useCallback(
|
||||
async query => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_DELETE", args: { NRN: query }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Установка флага готовности панели
|
||||
const setPanelReady = useCallback(
|
||||
async (query, ready) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_READY_SET", args: { NRN: query, NREADY: ready }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Установка флага публичности панели
|
||||
const setPanelPbl = useCallback(
|
||||
async (query, pbl) => {
|
||||
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_PBL_SET", args: { NRN: query, NPBL: pbl }, loader: false });
|
||||
setRefresh(true);
|
||||
},
|
||||
[executeStored]
|
||||
);
|
||||
|
||||
//Импорт новой панели
|
||||
const importPanel = useCallback(
|
||||
async fileData => {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_IMPORT",
|
||||
args: {
|
||||
CPANEL: {
|
||||
//Форматируем данные документа в base64
|
||||
VALUE: btoa(unescape(encodeURIComponent(fileData))),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
},
|
||||
loader: true
|
||||
});
|
||||
setRefresh(true);
|
||||
},
|
||||
[SERV_DATA_TYPE_CLOB, executeStored]
|
||||
);
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_LIST",
|
||||
respArg: "COUT",
|
||||
isArray: name => ["XPANEL"].includes(name),
|
||||
attributeValueProcessor: (name, val) => (["code", "name"].includes(name) ? undefined : val),
|
||||
loader: true
|
||||
});
|
||||
setData(data?.XPANELS?.XPANEL || []);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
}
|
||||
};
|
||||
//Если надо обновить
|
||||
if (refresh)
|
||||
//Получим данные
|
||||
loadData();
|
||||
}, [refresh, executeStored]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [data, insertPanel, updatePanel, deletePanel, setPanelReady, setPanelPbl, importPanel];
|
||||
};
|
||||
|
||||
//Работа с содержимым панели
|
||||
const usePanelDesc = panel => {
|
||||
//Собственное состояние - флаг инициализированности
|
||||
const [isInit, setInit] = useState(false);
|
||||
|
||||
//Собственное состояние - флаг необходимости обновления
|
||||
const [refresh, setRefresh] = useState(true);
|
||||
|
||||
//Собственное состояние - данные
|
||||
const [data, setData] = useState({
|
||||
components: {},
|
||||
valueProviders: {},
|
||||
layouts: INITIAL_LAYOUTS,
|
||||
breakpoint: INITIAL_BREAKPOINT
|
||||
});
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgErr } = useContext(MessagingСtx);
|
||||
|
||||
//Считывание базовой информации о панели
|
||||
const getPanelInfo = useCallback(async () => {
|
||||
//Считываем информацию с сервера
|
||||
const res = await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_ATTRS_GET",
|
||||
args: {
|
||||
NRN: panel
|
||||
},
|
||||
respArg: "COUT",
|
||||
loader: true
|
||||
});
|
||||
//Забираем все без RN
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { rn, ...panelAttrs } = res.XPANEL_ATTRS;
|
||||
//Возвращаем результат
|
||||
return panelAttrs;
|
||||
}, [executeStored, panel]);
|
||||
|
||||
//Формирования данных панели для выгрузки
|
||||
const makeXMLPanelDesc = useCallback(
|
||||
async (includePanelInfo = false, isBase64 = true) => {
|
||||
//Считываем информацию о выгружаемой панели
|
||||
const panelInfo = includePanelInfo ? await getPanelInfo() : {};
|
||||
//Сформируем данные в формат XML
|
||||
const xmlData = object2Base64XML(
|
||||
{
|
||||
XPANEL: {
|
||||
...(includePanelInfo ? { XPANEL_INFO: panelInfo } : {}),
|
||||
XCOMPONENTS: data.components,
|
||||
XVALUE_PROVIDERS: data.valueProviders,
|
||||
XLAYOUTS: data.layouts,
|
||||
XOPTIONS: {
|
||||
breakpoint: data.breakpoint
|
||||
}
|
||||
}
|
||||
},
|
||||
{ suppressEmptyNode: false }
|
||||
);
|
||||
//Возвращаем данные в формате XML (формат исходит от признака)
|
||||
return isBase64 ? xmlData : decodeURIComponent(escape(atob(xmlData)));
|
||||
},
|
||||
[data.breakpoint, data.components, data.layouts, data.valueProviders, getPanelInfo]
|
||||
);
|
||||
|
||||
//Добавление компонента в макет
|
||||
const addComponent = component => {
|
||||
//Генерируем ID
|
||||
const id = genUID(component.path);
|
||||
//Добавляем компонент и его макет
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
components: { ...pv.components, [id]: { ...component, settings: { ...component.settings, id: id } } },
|
||||
layouts: { ...pv.layouts, [data.breakpoint]: [...pv.layouts[data.breakpoint], { i: id, x: 0, y: 0, w: 4, h: 10 }] }
|
||||
}));
|
||||
};
|
||||
|
||||
//Удаление компонента из макета
|
||||
const deleteComponent = id => {
|
||||
//Удаляем все старые зависимости от компонента
|
||||
const newValueProviders = Object.keys(data.valueProviders).reduce(
|
||||
(prev, cur) => ({
|
||||
...prev,
|
||||
[cur]: { ...data.valueProviders[cur], dependencies: data.valueProviders[cur].dependencies.filter(el => el !== id) }
|
||||
}),
|
||||
{}
|
||||
);
|
||||
//Обновляем данные
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
layouts: { ...pv.layouts, [data.breakpoint]: data.layouts[data.breakpoint].filter(item => item.i !== id) },
|
||||
components: { ...pv.components, [id]: { ...pv.components[id], deleted: true } },
|
||||
valueProviders: { ...newValueProviders }
|
||||
}));
|
||||
};
|
||||
|
||||
//Изменение размера холста
|
||||
const breakpointChange = breakpoint => setData(pv => ({ ...pv, breakpoint: breakpoint }));
|
||||
|
||||
//Изменение состояния макета
|
||||
const layoutsChange = layouts => setData(pv => ({ ...pv, layouts: layouts }));
|
||||
|
||||
//Изменение значений в компоненте
|
||||
const changeValueProviders = values => {
|
||||
//Считываем проводники
|
||||
const newValueProviders = { ...data.valueProviders };
|
||||
//Переносим новые значения в проводники
|
||||
Object.keys(values).map(el => (newValueProviders[el].value = values[el]));
|
||||
//Обновляем проводники
|
||||
setData(pv => ({ ...pv, valueProviders: { ...newValueProviders } }));
|
||||
};
|
||||
|
||||
//Изменение настроек компонента
|
||||
const changeComponentSettings = (id = null, settings = {}, onChanged = null) => {
|
||||
//Считываем новые зависимости компонента
|
||||
const providedValues = getValueProvidersLinks(data.components[id].path, settings);
|
||||
//Удаляем все старые зависимости от компонента
|
||||
const newValueProviders = Object.keys(data.valueProviders).reduce(
|
||||
(prev, cur) => ({
|
||||
...prev,
|
||||
[cur]: { ...data.valueProviders[cur], dependencies: data.valueProviders[cur].dependencies.filter(el => el !== id) }
|
||||
}),
|
||||
{}
|
||||
);
|
||||
//Добавляем новые зависимости
|
||||
providedValues.map(providerName => newValueProviders[providerName].dependencies.push(id));
|
||||
//Обновляем данные
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
components: { ...pv.components, [id]: { ...pv.components[id], settings: { ...settings } } },
|
||||
valueProviders: { ...newValueProviders }
|
||||
}));
|
||||
//Выполняем действия после изменения
|
||||
onChanged && onChanged();
|
||||
};
|
||||
|
||||
//Изменение настроек панели
|
||||
const changePanelSettings = valueProviders => {
|
||||
//Обновляем данные
|
||||
setData(pv => ({
|
||||
...pv,
|
||||
valueProviders: { ...valueProviders }
|
||||
}));
|
||||
};
|
||||
|
||||
//Сохранение описания панели
|
||||
const savePanelDesc = useCallback(
|
||||
async (callBack = null) => {
|
||||
try {
|
||||
await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_DESC_SET",
|
||||
args: {
|
||||
NRN: panel,
|
||||
CPANEL: {
|
||||
VALUE: await makeXMLPanelDesc(),
|
||||
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||
}
|
||||
},
|
||||
loader: true
|
||||
});
|
||||
callBack && callBack(false);
|
||||
} catch (e) {
|
||||
callBack && callBack(true);
|
||||
}
|
||||
},
|
||||
[SERV_DATA_TYPE_CLOB, executeStored, makeXMLPanelDesc, panel]
|
||||
);
|
||||
|
||||
//Загрузка описания панели из файла
|
||||
const importPanelDesc = async (fileData, callBack = null) => {
|
||||
//Формируем данные панели
|
||||
const panelData = await xml2JSON({
|
||||
xmlDoc: fileData,
|
||||
isArray: isArrayPanelDesc,
|
||||
tagValueProcessor: tagValueProcessorPanelDesc
|
||||
});
|
||||
//Если файл содержит тэг XPANEL
|
||||
if (panelData.XPANEL) {
|
||||
setData(
|
||||
serverPanelData2PanelDesc(
|
||||
panelData.XPANEL?.XCOMPONENTS || {},
|
||||
panelData.XPANEL?.XVALUE_PROVIDERS || {},
|
||||
panelData.XPANEL?.XLAYOUTS || {},
|
||||
panelData.XPANEL?.XOPTIONS?.breakpoint || INITIAL_BREAKPOINT
|
||||
)
|
||||
);
|
||||
callBack && callBack(true);
|
||||
} else {
|
||||
showMsgErr("Загружаемые данные не соответствуют формату настройки панели.");
|
||||
callBack && callBack(false);
|
||||
}
|
||||
};
|
||||
|
||||
//Выгрузка панели в файл
|
||||
const exportPanelDesc = async panelName => {
|
||||
//Формируем XML-представление панели
|
||||
const xmlPanelDesc = await makeXMLPanelDesc(true, false);
|
||||
//Выгружаем в файл
|
||||
exportXMLFile(xmlPanelDesc, panelName);
|
||||
};
|
||||
|
||||
//При необходимости получить/обновить данные
|
||||
useEffect(() => {
|
||||
//Загрузка данных с сервера
|
||||
const loadData = async () => {
|
||||
try {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_PE.PANEL_DESC_GET",
|
||||
args: { NRN: panel },
|
||||
respArg: "COUT",
|
||||
isArray: isArrayPanelDesc,
|
||||
tagValueProcessor: tagValueProcessorPanelDesc,
|
||||
loader: true
|
||||
});
|
||||
setData(
|
||||
serverPanelData2PanelDesc(
|
||||
data?.XCOMPONENTS || {},
|
||||
data?.XVALUE_PROVIDERS || {},
|
||||
data?.XLAYOUTS || {},
|
||||
data?.XOPTIONS?.breakpoint || INITIAL_BREAKPOINT
|
||||
)
|
||||
);
|
||||
setInit(true);
|
||||
} finally {
|
||||
setRefresh(false);
|
||||
}
|
||||
};
|
||||
//Если надо обновить
|
||||
if (refresh)
|
||||
if (panel)
|
||||
//Если есть для чего получать данные
|
||||
loadData();
|
||||
//Нет идентификатора запроса - нет данных
|
||||
else
|
||||
setData({
|
||||
components: {},
|
||||
valueProviders: {},
|
||||
layouts: INITIAL_LAYOUTS,
|
||||
breakpoint: INITIAL_BREAKPOINT
|
||||
});
|
||||
}, [refresh, panel, executeStored]);
|
||||
|
||||
//При изменении входных свойств - поднимаем флаг обновления
|
||||
useEffect(() => {
|
||||
setInit(false);
|
||||
setRefresh(true);
|
||||
}, [panel]);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [
|
||||
data,
|
||||
isInit,
|
||||
addComponent,
|
||||
deleteComponent,
|
||||
breakpointChange,
|
||||
layoutsChange,
|
||||
changeValueProviders,
|
||||
changeComponentSettings,
|
||||
changePanelSettings,
|
||||
savePanelDesc,
|
||||
importPanelDesc,
|
||||
exportPanelDesc
|
||||
];
|
||||
};
|
||||
|
||||
//Работа с соотношением размеров
|
||||
const useWindowResize = () => {
|
||||
//Состояние высоты строки ResponsiveGridLayout
|
||||
const [rowHeight, setRowHeight] = useState(1);
|
||||
//При изменении размера
|
||||
useLayoutEffect(() => {
|
||||
//Расчет высоты строки
|
||||
const updateRowHeight = () => {
|
||||
//Определяем доступную область
|
||||
const availableHeight = window.innerHeight - GRID_LAYOUT_PARAMS.APP_BAR_HEIGHT - GRID_LAYOUT_PARAMS.SAFE_OFFSET;
|
||||
//Промежутки между рядами
|
||||
const totalMargins = (GRID_LAYOUT_PARAMS.MAX_ROWS - 1) * GRID_LAYOUT_PARAMS.MARGIN;
|
||||
//Рассчитываем высоту строки
|
||||
const result = (availableHeight - totalMargins) / GRID_LAYOUT_PARAMS.MAX_ROWS;
|
||||
//Устанавливаем
|
||||
setRowHeight(result);
|
||||
};
|
||||
//Запускаем при открытии панели
|
||||
updateRowHeight();
|
||||
//Устанавливаем обработчик на событие
|
||||
window.addEventListener("resize", updateRowHeight);
|
||||
//Удаляем обработчик на событие
|
||||
return () => window.removeEventListener("resize", updateRowHeight);
|
||||
}, []);
|
||||
|
||||
//Возвращаем интерфейс хука
|
||||
return [rowHeight];
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useComponentModule, usePanel, usePanelManager, usePanelDesc, useWindowResize };
|
||||
@ -1,52 +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"
|
||||
};
|
||||
|
||||
//Доступность сохранения настроек элемента
|
||||
export const isItemOkDisabled = item => (!item.name ? true : false);
|
||||
@ -1,380 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Форма (редактор настроек)
|
||||
*/
|
||||
|
||||
//TODO: Контроль уникальности имени элемента формы
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
Icon,
|
||||
Select,
|
||||
Menu,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Chip,
|
||||
Stack,
|
||||
InputAdornment,
|
||||
IconButton
|
||||
} from "@mui/material"; //Интерфейсные элементы
|
||||
import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения
|
||||
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Диалог настройки
|
||||
import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов
|
||||
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы
|
||||
import { ITEM_SHAPE, ITEM_INITIAL, ITEMS_INITIAL, ORIENTATION, isItemOkDisabled } from "./common"; //Общие ресурсы и константы формы
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CHIP_ITEM: { ...COMMON_STYLES.CHIP(true, false) }
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Редактор элемента
|
||||
const ItemEditor = ({ item = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => {
|
||||
//Собственное состояние - параметры элемента формы
|
||||
const [state, setState] = useState({ ...ITEM_INITIAL, ...item });
|
||||
|
||||
//Собственное состояние - элемент привязки меню выбора источника
|
||||
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState(null);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//При закрытии редактора с сохранением
|
||||
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
|
||||
}))
|
||||
});
|
||||
};
|
||||
|
||||
//Открытие/сокрытие меню выбора источника
|
||||
const toggleValueProvidersMenu = target => setValueProvidersMenuAnchorEl(target instanceof Element ? target : null);
|
||||
|
||||
//При очистке значения/связывания параметра
|
||||
const handleParamClearClick = () => setState(pv => ({ ...pv, name: "" }));
|
||||
|
||||
//При отображении меню связывания параметра с поставщиком данных
|
||||
const handleParamLinkMenuClick = e => setValueProvidersMenuAnchorEl(e.currentTarget);
|
||||
|
||||
//При выборе элемента меню связывания аргумента с поставщиком данных
|
||||
const handleParamLinkClick = valueSource => {
|
||||
//Устанавливаем выбранный параметр
|
||||
setState(pv => ({ ...pv, name: valueSource }));
|
||||
//Закрываем меню
|
||||
toggleValueProvidersMenu();
|
||||
};
|
||||
|
||||
//Список значений
|
||||
const values = Object.keys(valueProviders);
|
||||
|
||||
//Наличие значений
|
||||
const isValues = values && values.length > 0 ? true : false;
|
||||
|
||||
//Меню привязки к поставщикам значений
|
||||
const valueProvidersMenu = isValues && (
|
||||
<Menu anchorEl={valueProvidersMenuAnchorEl} open={Boolean(valueProvidersMenuAnchorEl)} onClose={toggleValueProvidersMenu}>
|
||||
{values.map((value, i) => (
|
||||
<MenuItem key={i} onClick={() => handleParamLinkClick(value)}>
|
||||
{value}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PConfigDialog
|
||||
title={`${item ? TITLES.UPDATE : TITLES.INSERT} элемента`}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okDisabled={isItemOkDisabled(state)}
|
||||
>
|
||||
<Stack direction={"column"} spacing={1}>
|
||||
{valueProvidersMenu}
|
||||
<TextField
|
||||
type={"text"}
|
||||
variant={"standard"}
|
||||
value={state.name}
|
||||
label={"Имя"}
|
||||
id={"name"}
|
||||
required={true}
|
||||
onChange={handleChange}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleParamClearClick}>
|
||||
<Icon>clear</Icon>
|
||||
</IconButton>
|
||||
{isValues && (
|
||||
<IconButton onClick={handleParamLinkMenuClick}>
|
||||
<Icon>settings_ethernet</Icon>
|
||||
</IconButton>
|
||||
)}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<TextField type={"text"} variant={"standard"} value={state.caption} label={"Приглашение"} id={"caption"} onChange={handleChange} />
|
||||
<TextField
|
||||
type={"text"}
|
||||
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>
|
||||
</P8PConfigDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - редактор элемента
|
||||
ItemEditor.propTypes = {
|
||||
item: ITEM_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Форма (редактор настроек)
|
||||
const FormEditor = ({
|
||||
id,
|
||||
title = "",
|
||||
orientation = ORIENTATION.V,
|
||||
autoApply = false,
|
||||
items = ITEMS_INITIAL,
|
||||
valueProviders = {},
|
||||
onSettingsChange = null
|
||||
} = {}) => {
|
||||
//Собственное состояние - текущие настройки
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
//Собственное состояние - редактор элементов формы
|
||||
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, closeEditor });
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, title, orientation, autoApply, items });
|
||||
}, [settings, id, title, orientation, autoApply, items]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
settings && (
|
||||
<P8PEditorBox title={"Параметры формы"} onSave={handleSave}>
|
||||
{itemEditor.display && (
|
||||
<ItemEditor
|
||||
item={itemEditor.index !== null ? { ...settings.items[itemEditor.index] } : null}
|
||||
valueProviders={valueProviders}
|
||||
onCancel={handleItemCancel}
|
||||
onOk={handleItemSave}
|
||||
/>
|
||||
)}
|
||||
<P8PEditorSubHeader 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={"Автоподтверждение"}
|
||||
/>
|
||||
<P8PEditorSubHeader 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>
|
||||
</P8PEditorBox>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Форма (редактор настроек)
|
||||
FormEditor.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
orientation: PropTypes.oneOf(Object.values(ORIENTATION)),
|
||||
autoApply: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(ITEM_SHAPE),
|
||||
valueProviders: PropTypes.object,
|
||||
onSettingsChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export default FormEditor;
|
||||
@ -1,174 +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 { P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||
import { ITEM_SHAPE, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
||||
import { APP_STYLES } from "../../../../../app.styles";
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { overflow: "auto", ...APP_STYLES.SCROLL }
|
||||
};
|
||||
|
||||
//Иконка компонента
|
||||
const COMPONENT_ICON = "fact_check";
|
||||
|
||||
//Наименование компонента
|
||||
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"}`} sx={STYLES.CONTAINER} 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>
|
||||
) : (
|
||||
<P8PComponentInlineMessage icon={COMPONENT_ICON} name={COMPONENT_NAME} message={P8P_COMPONENT_INLINE_MESSAGE.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;
|
||||
@ -1,39 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Индикатор (общие ресурсы действия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Доступные области действий индикатора
|
||||
const P8P_CA_INDICATOR_ACTION_AREAS = [
|
||||
{ name: "Компонент", area: "component", hasElement: false },
|
||||
{ name: "Заголовок", area: "inner_caption", hasElement: false },
|
||||
{ name: "Значение", area: "inner_value", hasElement: false }
|
||||
];
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Считывание обработчиков индикатора
|
||||
const getIndicatorHandlers = handlers => {
|
||||
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||
return {
|
||||
onComponentClick: handlers["component."]?.fn,
|
||||
onCaptionClick: handlers["inner_caption."]?.fn,
|
||||
onValueClick: handlers["inner_value."]?.fn
|
||||
};
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CA_INDICATOR_ACTION_AREAS, getIndicatorHandlers };
|
||||
@ -1,33 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Индикатор (общие ресурсы условия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Доступные поля условий индикатора
|
||||
const P8P_CC_INDICATOR_COND_FIELDS = [
|
||||
{ name: "Состояние", value: "state", hasElement: false },
|
||||
{ name: "Значение", value: "value", hasElement: false },
|
||||
{ name: "Подсказка", value: "hint", hasElement: false }
|
||||
];
|
||||
|
||||
//Доступные поля результата условия индикатора
|
||||
const P8P_CC_INDICATOR_RES_FIELDS = [
|
||||
{ name: "Подпись", value: "caption", hasElement: false, icon: "rtt" },
|
||||
{ name: "Значение", value: "value", hasElement: false, icon: "pin" },
|
||||
{ name: "Цвет заливки", value: "backgroundColor", hasElement: false, icon: "color_lens" },
|
||||
{ name: "Цвет шрифта", value: "color", hasElement: false, icon: "format_color_text" }
|
||||
];
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CC_INDICATOR_COND_FIELDS, P8P_CC_INDICATOR_RES_FIELDS };
|
||||
@ -1,97 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Индикатор (редактор настроек)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||
import { P8PConditions } from "../../../../components/editors/p8p_conditions"; //Условия
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { P8P_CCS_INITIAL, P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||
import { P8P_CA_INDICATOR_ACTION_AREAS } from "./action"; //Общие ресурсы действий индикатора
|
||||
import { P8P_CC_INDICATOR_COND_FIELDS, P8P_CC_INDICATOR_RES_FIELDS } from "./conditions"; //Общие ресурсы условий индикатора
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Индикатор (редактор настроек)
|
||||
const IndicatorEditor = ({
|
||||
id,
|
||||
dataSource = null,
|
||||
valueProviders = {},
|
||||
conditions = P8P_CCS_INITIAL,
|
||||
actions = P8P_CAS_INITIAL,
|
||||
onSettingsChange = null
|
||||
} = {}) => {
|
||||
//Собственное состояние - текущие настройки
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
//При сохранении изменений элемента
|
||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||
|
||||
//При изменении действий
|
||||
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||
|
||||
//При изменении условий
|
||||
const handleConditionsChange = conditions => setSettings(pv => ({ ...pv, conditions }));
|
||||
|
||||
//При сохранении настроек
|
||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource, conditions, actions });
|
||||
}, [settings, id, dataSource, conditions, actions]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PEditorBox title={"Параметры индикатора"} onSave={handleSave}>
|
||||
<P8PEditorSubHeader title={"Источник данных"} />
|
||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||
<P8PEditorSubHeader title={"Условия"} />
|
||||
<P8PConditions
|
||||
conditions={settings?.conditions}
|
||||
condFields={P8P_CC_INDICATOR_COND_FIELDS}
|
||||
resFields={P8P_CC_INDICATOR_RES_FIELDS}
|
||||
onChange={handleConditionsChange}
|
||||
/>
|
||||
<P8PEditorSubHeader title={"Действия"} />
|
||||
<P8PActions
|
||||
actions={settings?.actions}
|
||||
valueProviders={valueProviders}
|
||||
areas={P8P_CA_INDICATOR_ACTION_AREAS}
|
||||
onChange={handleActionsChange}
|
||||
/>
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Индикатор (редактор настроек)
|
||||
IndicatorEditor.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onSettingsChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export default IndicatorEditor;
|
||||
@ -1,111 +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 { useDataSource, useConditions, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import {
|
||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||
P8P_COMPONENT_INLINE_MESSAGE,
|
||||
P8PComponentInlineMessage
|
||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||
import { P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы условий
|
||||
import { P8P_CA_SHAPE } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||
import { getIndicatorHandlers } from "./action"; //Общие ресурсы действий индикатора
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Иконка компонента
|
||||
const COMPONENT_ICON = "speed";
|
||||
|
||||
//Наименование компонента
|
||||
const COMPONENT_NAME = "Индикатор";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: clickable => ({
|
||||
height: "100%",
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
})
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Индикатор (представление)
|
||||
const Indicator = ({ dataSource = null, values = {}, conditions = [], actions = [], onValuesChange = null }) => {
|
||||
//Собственное состояние - данные
|
||||
const [indicator, error, haveConfing, haveData] = useDataSource({
|
||||
dataSource,
|
||||
values,
|
||||
componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.INDICATOR
|
||||
});
|
||||
|
||||
//Собственное состояние - данные с учетом условий
|
||||
const [indicatorOrigin] = useConditions({ componentData: indicator, conditions });
|
||||
|
||||
//Собственное состояние - обработчики компонента
|
||||
const [handlers] = useComponentHandlers({ actions, onValuesChange });
|
||||
|
||||
//Обработчики областей
|
||||
const { onComponentClick, onCaptionClick, onValueClick } = getIndicatorHandlers(handlers);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Paper
|
||||
{...(haveConfing && haveData
|
||||
? { sx: { ...STYLES.CONTAINER(onComponentClick) } }
|
||||
: { className: "component-view__container component-view__container__empty" })}
|
||||
elevation={6}
|
||||
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||
>
|
||||
{haveConfing && haveData ? (
|
||||
<P8PIndicator
|
||||
{...indicatorOrigin}
|
||||
onValueClick={onValueClick ? event => onValueClick({ event, values }) : null}
|
||||
onCaptionClick={onCaptionClick ? event => onCaptionClick({ event, values }) : null}
|
||||
elevation={0}
|
||||
/>
|
||||
) : (
|
||||
<P8PComponentInlineMessage
|
||||
icon={COMPONENT_ICON}
|
||||
name={COMPONENT_NAME}
|
||||
message={!haveConfing ? P8P_COMPONENT_INLINE_MESSAGE.NO_SETTINGS : error ? error : P8P_COMPONENT_INLINE_MESSAGE.NO_DATA_FOUND}
|
||||
type={error ? P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR : P8P_COMPONENT_INLINE_MESSAGE_TYPE.COMMON}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Индикатор (представление)
|
||||
Indicator.propTypes = {
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
values: PropTypes.object,
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onValuesChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export default Indicator;
|
||||
@ -1,54 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компонент: Диалог добавления/исправления панели
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог добавления/исправления панели
|
||||
const PanelIUDialog = ({ code = "", name = "", insert = true, onOk, onCancel }) => {
|
||||
//Нажатие на кнопку "Ok"
|
||||
const handleOk = values => onOk && onOk({ ...values });
|
||||
|
||||
//Нажатие на кнопку "Отмена"
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<P8PDialog
|
||||
title={`${insert === true ? TITLES.INSERT : TITLES.UPDATE} панели`}
|
||||
inputs={[
|
||||
{ name: "code", value: code, label: "Мнемокод" },
|
||||
{ name: "name", value: name, label: "Наименование" }
|
||||
]}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог добавления/исправления панели
|
||||
PanelIUDialog.propTypes = {
|
||||
code: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
insert: PropTypes.bool,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelIUDialog };
|
||||
@ -1,158 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компонент: Список панелей
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
LIST_PANELS: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL },
|
||||
SMALL_TOOL_ICON: {
|
||||
fontSize: 20
|
||||
}
|
||||
};
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Структура данных о сущности панели
|
||||
const PANELS_LIST_ITEM_SHAPE = PropTypes.shape({
|
||||
rn: PropTypes.number.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
author: PropTypes.string.isRequired,
|
||||
chDate: PropTypes.string.isRequired,
|
||||
modify: PropTypes.oneOf([0, 1]).isRequired,
|
||||
pbl: PropTypes.oneOf([0, 1]).isRequired,
|
||||
ready: PropTypes.oneOf([0, 1]).isRequired
|
||||
});
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Диалог открытия панели
|
||||
const PanelsList = ({
|
||||
panels = [],
|
||||
isEditable = true,
|
||||
current = null,
|
||||
onSelect = null,
|
||||
onPbl = null,
|
||||
onReady = null,
|
||||
onEdit = null,
|
||||
onDelete = null
|
||||
} = {}) => {
|
||||
//При выборе элемента списка
|
||||
const handleSelectClick = query => {
|
||||
onSelect && onSelect(query);
|
||||
};
|
||||
|
||||
//При нажатии на общедоступность
|
||||
const handlePblClick = (e, query) => {
|
||||
e.stopPropagation();
|
||||
onPbl && onPbl(query);
|
||||
};
|
||||
|
||||
//При нажатии на готовность
|
||||
const handleReadyClick = (e, query) => {
|
||||
e.stopPropagation();
|
||||
onReady && onReady(query);
|
||||
};
|
||||
|
||||
//При нажатии на исправлении
|
||||
const handleEditClick = (e, query) => {
|
||||
e.stopPropagation();
|
||||
onEdit && onEdit(query);
|
||||
};
|
||||
|
||||
//При нажатии на удаление
|
||||
const handleDeleteClick = (e, query) => {
|
||||
e.stopPropagation();
|
||||
onDelete && onDelete(query);
|
||||
};
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<List sx={STYLES.LIST_PANELS}>
|
||||
{panels.map((panel, i) => {
|
||||
const selected = panel.rn === current || panel.code === current;
|
||||
const disabled = !panel.modify;
|
||||
const pblTitle = `${panel.pbl === 1 ? "Общедоступный" : "Приватный"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
||||
const pblIcon = panel.pbl === 1 ? "groups" : "lock_person";
|
||||
const readyTitle = `${panel.ready === 1 ? "Готов" : "Не готов"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
||||
const readyIcon = panel.ready === 1 ? "touch_app" : "do_not_touch";
|
||||
return (
|
||||
<ListItem key={i}>
|
||||
<ListItemButton onClick={() => handleSelectClick(panel)} selected={selected}>
|
||||
<ListItemText
|
||||
primary={panel.name}
|
||||
secondaryTypographyProps={{ component: "div" }}
|
||||
secondary={
|
||||
<Stack direction={"column"}>
|
||||
<Typography variant={"caption"}>{`${panel.code}, ${panel.author}, ${panel.chDate}`}</Typography>
|
||||
{isEditable ? (
|
||||
<Stack direction={"row"}>
|
||||
<div title={pblTitle}>
|
||||
<IconButton disabled={disabled} onClick={e => handlePblClick(e, panel)}>
|
||||
<Icon sx={STYLES.SMALL_TOOL_ICON}>{pblIcon}</Icon>
|
||||
</IconButton>
|
||||
</div>
|
||||
<div title={readyTitle}>
|
||||
<IconButton disabled={disabled} onClick={e => handleReadyClick(e, panel)}>
|
||||
<Icon sx={STYLES.SMALL_TOOL_ICON}>{readyIcon}</Icon>
|
||||
</IconButton>
|
||||
</div>
|
||||
</Stack>
|
||||
) : null}
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
{isEditable ? (
|
||||
<Stack direction={"row"}>
|
||||
<IconButton onClick={e => handleEditClick(e, panel)} disabled={disabled} title={BUTTONS.UPDATE}>
|
||||
<Icon>edit</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={e => handleDeleteClick(e, panel)} disabled={disabled} title={BUTTONS.DELETE}>
|
||||
<Icon>delete</Icon>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
) : null}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Список панелей
|
||||
PanelsList.propTypes = {
|
||||
panels: PropTypes.arrayOf(PANELS_LIST_ITEM_SHAPE),
|
||||
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
isEditable: PropTypes.bool,
|
||||
onSelect: PropTypes.func,
|
||||
onPbl: PropTypes.func,
|
||||
onReady: PropTypes.func,
|
||||
onEdit: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelsList };
|
||||
@ -1,118 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компонент: Менеджер панелей
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState, useContext } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений
|
||||
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Типовой диалог настройки
|
||||
import { usePanelManager } from "../components_hooks"; //Пользовательские хуки
|
||||
import { PanelsList } from "./panels_list"; //Список панелей
|
||||
import { PanelIUDialog } from "./panel_iu_dialog"; //Диалог добавления/исправления панели
|
||||
import { ImportPanelButton } from "../../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Менеджер панелей
|
||||
const PanelsManager = ({ current = null, isEditable = true, onPanelSelect = null, onCancel = null } = {}) => {
|
||||
//Собственное состояние - изменяемая панель
|
||||
const [modPanel, setModPanel] = useState(null);
|
||||
|
||||
//Работа со списком панелей
|
||||
const [panels, insertPanel, updatePanel, deletePanel, setPanelReady, setPanelPbl, importPanel] = usePanelManager();
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgWarn } = useContext(MessagingСtx);
|
||||
|
||||
//При добавлении панели
|
||||
const handlePanelAdd = () => setModPanel(true);
|
||||
|
||||
//При загрузке панели из файла
|
||||
const handlePanelImport = fileData => {
|
||||
importPanel(fileData);
|
||||
};
|
||||
|
||||
//При выборе панели
|
||||
const handlePanelSelect = panel => onPanelSelect && onPanelSelect(panel);
|
||||
|
||||
//При установке признака публичности
|
||||
const handlePanelPblSet = panel => setPanelPbl(panel.rn, panel.pbl === 1 ? 0 : 1);
|
||||
|
||||
//При установке признака готовности
|
||||
const handlePanelReadySet = panel => setPanelReady(panel.rn, panel.ready === 1 ? 0 : 1);
|
||||
|
||||
//При исправлении панели
|
||||
const handlePanelEdit = panel => setModPanel({ ...panel });
|
||||
|
||||
//При удалении панели
|
||||
const handlePanelDelete = panel => showMsgWarn("Удалить панель?", () => deletePanel(panel.rn));
|
||||
|
||||
//При закрытии диалога добавления/исправления по "ОК"
|
||||
const handleIUDialogOk = async values => {
|
||||
if (modPanel === true) await insertPanel(values.code, values.name);
|
||||
else await updatePanel(modPanel.rn, values.code, values.name);
|
||||
setModPanel(null);
|
||||
};
|
||||
|
||||
//При закрытии диалога добавления/исправления по "Отмена"
|
||||
const handleIUDialogCancel = () => setModPanel(null);
|
||||
|
||||
//При закрытии менеджера отменой
|
||||
const handleCancel = () => onCancel && onCancel();
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PConfigDialog title={"Панели"} onCancel={handleCancel}>
|
||||
{modPanel && (
|
||||
<PanelIUDialog
|
||||
code={modPanel?.code}
|
||||
name={modPanel?.name}
|
||||
insert={modPanel === true}
|
||||
onOk={handleIUDialogOk}
|
||||
onCancel={handleIUDialogCancel}
|
||||
/>
|
||||
)}
|
||||
{isEditable ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Button startIcon={<Icon>add</Icon>} onClick={handlePanelAdd}>
|
||||
{BUTTONS.INSERT}
|
||||
</Button>
|
||||
<ImportPanelButton id={"input-file-loader-manager"} startIcon={"file_upload"} title={"Загрузить"} onClick={handlePanelImport} />
|
||||
</Box>
|
||||
) : null}
|
||||
<PanelsList
|
||||
panels={panels || []}
|
||||
current={current}
|
||||
isEditable={isEditable}
|
||||
onSelect={handlePanelSelect}
|
||||
onPbl={handlePanelPblSet}
|
||||
onReady={handlePanelReadySet}
|
||||
onEdit={handlePanelEdit}
|
||||
onDelete={handlePanelDelete}
|
||||
/>
|
||||
</P8PConfigDialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Менеджер панелей
|
||||
PanelsManager.propTypes = {
|
||||
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
isEditable: PropTypes.bool,
|
||||
onPanelSelect: PropTypes.func,
|
||||
onCancel: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { PanelsManager };
|
||||
@ -1,58 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Таблица (общие ресурсы действия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Собственные значения типа действия
|
||||
const P8P_CA_TABLE_TYPE_VALUE = {
|
||||
COLUMN_VALUE: "Значение графы"
|
||||
};
|
||||
|
||||
//Типы значений действий таблицы
|
||||
const P8P_CA_TABLE_VALUE_TYPES = [...Object.values(P8P_CA_TABLE_TYPE_VALUE)];
|
||||
|
||||
//Доступные области действий таблицы
|
||||
const P8P_CA_TABLE_ACTION_AREAS = [
|
||||
{ name: "Компонент", area: "component", hasElement: false },
|
||||
{ name: "Графа таблицы", area: "column", hasElement: true },
|
||||
{ name: "Строка таблицы", area: "row", hasElement: false }
|
||||
];
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Получение значения собственного типа действия
|
||||
const getDataGridCustomTypeValue = ({ type, value, prms }) => {
|
||||
//Если это значение графы - возвращаем нужное значение, иначе - null
|
||||
return type === P8P_CA_TABLE_TYPE_VALUE.COLUMN_VALUE ? prms.row[value] : null;
|
||||
};
|
||||
|
||||
//Считывание обработчиков таблицы
|
||||
const getDataGridHandlers = (handlers, element = null) => {
|
||||
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||
return {
|
||||
onComponentClick: handlers["component."]?.fn,
|
||||
onRowClick: handlers["row."]?.fn,
|
||||
onColumnClick: element ? handlers[`column.${element}`]?.fn : null
|
||||
};
|
||||
};
|
||||
|
||||
//Проверка наличия обработчиков ячеек
|
||||
const isHasCellRender = (handlers, conditions) => {
|
||||
return Object.keys(handlers).some(handler => handler.startsWith("column.") || handler === "row.") || conditions.length !== 0;
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CA_TABLE_VALUE_TYPES, P8P_CA_TABLE_ACTION_AREAS, getDataGridCustomTypeValue, getDataGridHandlers, isHasCellRender };
|
||||
@ -1,38 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Индикатор (общие ресурсы условия)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
import { getConditionsValues } from "../../../../components/editors/p8p_component_condition/util"; //Вспомогательные ресурсы условий
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Доступные поля условий таблицы
|
||||
const P8P_CC_TABLE_COND_FIELDS = [{ name: "Графа", value: "column", hasElement: true }];
|
||||
|
||||
//Доступные поля результата условия таблицы
|
||||
const P8P_CC_TABLE_RES_FIELDS = [
|
||||
{ name: "Цвет заливки", value: "backgroundColor", hasElement: true, icon: "color_lens" },
|
||||
{ name: "Цвет шрифта", value: "color", hasElement: true, icon: "format_color_text" }
|
||||
];
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Считывание стилей колонки по условиям
|
||||
const getColumnStylesByConditions = (conditions, row, columnName) => {
|
||||
//Считываем стили строки
|
||||
return getConditionsValues(row, conditions, columnName);
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { P8P_CC_TABLE_COND_FIELDS, P8P_CC_TABLE_RES_FIELDS, getColumnStylesByConditions };
|
||||
@ -1,94 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Таблица (редактор настроек)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useEffect, useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер редактора
|
||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||
import { P8PConditions } from "../../../../components/editors/p8p_conditions"; //Условия
|
||||
import { P8P_CA_TABLE_VALUE_TYPES, P8P_CA_TABLE_ACTION_AREAS } from "./action"; //Общие ресурсы действий таблицы
|
||||
import { P8P_CC_TABLE_COND_FIELDS, P8P_CC_TABLE_RES_FIELDS } from "./conditions"; //Общие ресурсы условий таблицы
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { P8P_CCS_INITIAL, P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Таблица (редактор настроек)
|
||||
const TableEditor = ({
|
||||
id,
|
||||
dataSource = null,
|
||||
valueProviders = {},
|
||||
conditions = P8P_CCS_INITIAL,
|
||||
actions = P8P_CAS_INITIAL,
|
||||
onSettingsChange = null
|
||||
} = {}) => {
|
||||
//Собственное состояние - текущие настройки
|
||||
const [settings, setSettings] = useState(null);
|
||||
|
||||
//При сохранении изменений элемента
|
||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||
|
||||
//При изменении действий
|
||||
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||
|
||||
//При изменении условий
|
||||
const handleConditionsChange = conditions => setSettings(pv => ({ ...pv, conditions }));
|
||||
|
||||
//При сохранении настроек
|
||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||
|
||||
//При изменении компонента
|
||||
useEffect(() => {
|
||||
settings?.id != id && setSettings({ id, dataSource, conditions, actions });
|
||||
}, [settings, id, dataSource, conditions, actions]);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<P8PEditorBox title={"Параметры таблицы"} onSave={handleSave}>
|
||||
<P8PEditorSubHeader title={"Источник данных"} />
|
||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||
<P8PEditorSubHeader title={"Условия"} />
|
||||
<P8PConditions
|
||||
conditions={settings?.conditions}
|
||||
condFields={P8P_CC_TABLE_COND_FIELDS}
|
||||
resFields={P8P_CC_TABLE_RES_FIELDS}
|
||||
onChange={handleConditionsChange}
|
||||
/>
|
||||
<P8PEditorSubHeader title={"Действия"} />
|
||||
<P8PActions
|
||||
actions={settings?.actions}
|
||||
valueProviders={valueProviders}
|
||||
areas={P8P_CA_TABLE_ACTION_AREAS}
|
||||
valueTypes={P8P_CA_TABLE_VALUE_TYPES}
|
||||
onChange={handleActionsChange}
|
||||
/>
|
||||
</P8PEditorBox>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Таблица (редактор настроек)
|
||||
TableEditor.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
valueProviders: PropTypes.object,
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onSettingsChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export default TableEditor;
|
||||
@ -1,161 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Компоненты: Таблица (представление)
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Paper, Link } from "@mui/material"; //Интерфейсные элементы
|
||||
import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили
|
||||
import { P8PDataGrid } from "../../../../components/p8p_data_grid"; //Таблица данных
|
||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
import { useDataSource, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||
import {
|
||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||
P8P_COMPONENT_INLINE_MESSAGE,
|
||||
P8PComponentInlineMessage
|
||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||
import { P8P_CC_SHAPE, P8P_CCS_INITIAL } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы условий
|
||||
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||
import { getDataGridCustomTypeValue, getDataGridHandlers, isHasCellRender } from "./action"; //Общие ресурсы действий таблицы
|
||||
import { getColumnStylesByConditions } from "./conditions";
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Иконка компонента
|
||||
const COMPONENT_ICON = "table_view";
|
||||
|
||||
//Наименование компонента
|
||||
const COMPONENT_NAME = "Таблица";
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: clickable => ({
|
||||
display: "flex",
|
||||
height: "100%",
|
||||
overflow: "hidden",
|
||||
...(clickable
|
||||
? {
|
||||
cursor: "pointer"
|
||||
}
|
||||
: {})
|
||||
}),
|
||||
DATA_GRID: { width: "100%" },
|
||||
DATA_GRID_CONTAINER: {
|
||||
height: `calc(100%)`,
|
||||
...APP_STYLES.SCROLL
|
||||
},
|
||||
DATA_GRID_ROW_CLICABLE: { cursor: "pointer" }
|
||||
};
|
||||
|
||||
//---------------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//---------------------------------------------
|
||||
|
||||
//Форматирование значения ячейки
|
||||
const dataCellRender = ({ row, columnDef, values, handlers, conditions }) => {
|
||||
//Инициализируем обработчики строки
|
||||
const { onRowClick, onColumnClick } = getDataGridHandlers(handlers, columnDef.name);
|
||||
//Инициализируем стили по условию
|
||||
const condStyles = getColumnStylesByConditions(conditions, row, columnDef.name);
|
||||
//Накладываем нужные обработчики
|
||||
return {
|
||||
cellStyle: { ...(onRowClick ? { ...STYLES.DATA_GRID_ROW_CLICABLE } : {}), ...condStyles },
|
||||
cellProps: {
|
||||
...(onRowClick
|
||||
? {
|
||||
onClick: event => {
|
||||
onRowClick({ event, values, prms: { row, columnDef } });
|
||||
}
|
||||
}
|
||||
: {})
|
||||
},
|
||||
data: onColumnClick ? (
|
||||
<Link component="button" variant="body2" underline="hover" onClick={event => onColumnClick({ event, values, prms: { row, columnDef } })}>
|
||||
{row[columnDef.name]}
|
||||
</Link>
|
||||
) : (
|
||||
row[columnDef.name]
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Таблица (представление)
|
||||
const Table = ({ dataSource = null, values = {}, conditions = P8P_CCS_INITIAL, actions = P8P_CAS_INITIAL, onValuesChange = null } = {}) => {
|
||||
//Собственное состояние - данные
|
||||
const [dataGrid, error, haveConfing, haveData] = useDataSource({ dataSource, values, componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.TABLE });
|
||||
|
||||
//Собственное состояние - обработчики компонента
|
||||
const [handlers] = useComponentHandlers({ actions, onValuesChange, getCustomTypeValue: getDataGridCustomTypeValue });
|
||||
|
||||
//Признак необходимости рендера ячеек
|
||||
const hasCellRender = isHasCellRender(handlers, conditions);
|
||||
|
||||
//Обработчики областей
|
||||
const { onComponentClick } = getDataGridHandlers(handlers);
|
||||
|
||||
//Формирование представления
|
||||
return (
|
||||
<Paper
|
||||
{...(haveConfing && haveData
|
||||
? { sx: { ...STYLES.CONTAINER(onComponentClick) } }
|
||||
: { className: "component-view__container component-view__container__empty" })}
|
||||
elevation={6}
|
||||
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||
>
|
||||
{haveConfing && haveData ? (
|
||||
<P8PDataGrid
|
||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||
{...dataGrid}
|
||||
style={STYLES.DATA_GRID}
|
||||
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
|
||||
dataCellRender={
|
||||
hasCellRender
|
||||
? prms =>
|
||||
dataCellRender({
|
||||
...prms,
|
||||
values,
|
||||
handlers,
|
||||
conditions
|
||||
})
|
||||
: null
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<P8PComponentInlineMessage
|
||||
icon={COMPONENT_ICON}
|
||||
name={COMPONENT_NAME}
|
||||
message={!haveConfing ? P8P_COMPONENT_INLINE_MESSAGE.NO_SETTINGS : error ? error : P8P_COMPONENT_INLINE_MESSAGE.NO_DATA_FOUND}
|
||||
type={error ? P8P_COMPONENT_INLINE_MESSAGE_TYPE.ERROR : P8P_COMPONENT_INLINE_MESSAGE_TYPE.COMMON}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств компонента - Таблица (представление)
|
||||
Table.propTypes = {
|
||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||
values: PropTypes.object,
|
||||
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||
onValuesChange: PropTypes.func
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export default Table;
|
||||
@ -1,16 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Редактор панелей: точка входа
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { PanelsEditor } from "./panels_editor"; //Корневая панель редактора
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export const RootClass = PanelsEditor;
|
||||
@ -1,87 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Элемент макета
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные элементы
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
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,
|
||||
isDragging: PropTypes.bool
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { LayoutItem };
|
||||
@ -1,134 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - Редактор панелей
|
||||
Дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { InputLabel, Input, IconButton, Icon, Button } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { TEXTS } from "../../../app.text"; //Общие текстовые ресурсы приложения
|
||||
import { P8PAppProgress } from "../../components/p8p_app_progress"; //Индикатор процесса
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
INPUT_FILE: { display: "none" },
|
||||
INPUT_LABEL: { display: "contents" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Компонент "Загрузка из файла"
|
||||
const ImportPanel = ({ id, buttonView, onClick, disabled = false }) => {
|
||||
//Состояние загрузки файла
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
//При выборе файла
|
||||
const handleChange = event => {
|
||||
//Если файл выбран
|
||||
if (event.target.files[0]) {
|
||||
//Инициализируем считыватель файла
|
||||
const reader = new FileReader();
|
||||
//Обработчик начала считывания данных
|
||||
reader.onloadstart = () => setLoading(true);
|
||||
//Обработчик считывания данных
|
||||
reader.onload = async e => {
|
||||
//Обрабатываем текст файла
|
||||
onClick(e.target.result);
|
||||
setLoading(false);
|
||||
};
|
||||
//Обработчик ошибки считывания данных
|
||||
reader.onerror = () => setLoading(false);
|
||||
//Загружаем данные из файла
|
||||
reader.readAsText(event.target.files[0]);
|
||||
}
|
||||
//Очищаем значение
|
||||
event.target.value = null;
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<>
|
||||
{loading ? <P8PAppProgress open={true} text={TEXTS.LOADING} /> : null}
|
||||
<InputLabel htmlFor={id} sx={STYLES.INPUT_LABEL}>
|
||||
<Input
|
||||
id={id}
|
||||
type="file"
|
||||
hidden
|
||||
onChange={handleChange}
|
||||
sx={STYLES.INPUT_FILE}
|
||||
inputProps={{ accept: ".xml" }}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{buttonView}
|
||||
</InputLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Компонент "Загрузка из файла"
|
||||
ImportPanel.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
buttonView: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool
|
||||
};
|
||||
|
||||
//Генерация действия "Загрузить из файла" меню действий
|
||||
export const toolbarImportRenderer = ({ icon, title, disabled, onClick }) => {
|
||||
//Представление кнопки загрузки
|
||||
let importButtonView = (
|
||||
<IconButton title={title} disabled={disabled}>
|
||||
<Icon>{icon}</Icon>
|
||||
</IconButton>
|
||||
);
|
||||
//Генерация содержимого
|
||||
return <ImportPanel id={"input-file-loader-toolbar"} buttonView={importButtonView} onClick={onClick} disabled={disabled} />;
|
||||
};
|
||||
|
||||
//Компонент кнопки импорта панели из файла
|
||||
export const ImportPanelButton = ({ id, startIcon, title, onClick }) => {
|
||||
//Представление кнопки загрузки
|
||||
let importButtonView = (
|
||||
<Button startIcon={startIcon ? <Icon>{startIcon}</Icon> : null} component="span">
|
||||
{title}
|
||||
</Button>
|
||||
);
|
||||
//Генерация содержимого
|
||||
return <ImportPanel id={id} buttonView={importButtonView} onClick={onClick} />;
|
||||
};
|
||||
|
||||
//Контроль свойств - Компонент кнопки импорта панели из файла
|
||||
ImportPanelButton.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
startIcon: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Выгрузка XML файла
|
||||
export const exportXMLFile = (data, fileName) => {
|
||||
//Переводим в данные для файла
|
||||
const blobData = new Blob([data], { type: "text/plain" });
|
||||
//Формируем URL
|
||||
const url = URL.createObjectURL(blobData);
|
||||
//Делаем линк и устанавливаем параметры, после добавляем в DOM
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${fileName.replace(/[/\\?%*:|"<>.,;= ]/g, "_")}.xml`;
|
||||
document.body.appendChild(a);
|
||||
//Выполняем открытие ссылки
|
||||
a.click();
|
||||
//Очищаем от лишних данных
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user