Действия с моделями в карточке класса выборки данных, карточка технического объекта

This commit is contained in:
Mikhail Chechnev 2024-08-09 00:46:25 +03:00
parent 1cb78fc446
commit f7c0dd0e10
11 changed files with 700 additions and 139 deletions

View File

@ -31,7 +31,7 @@ create or replace package UDO_PKG_EQUIPDS as
procedure CM_LIST
(
NEQUIPDS in number, -- Рег. номер выборки данных
COUT out clob -- Сериализованная таблица данных
COUT out clob -- Сериализованный список
);
/* Клиентское добавление "Выборки данных оборудования (классы оборудования)" */
@ -102,17 +102,18 @@ create or replace package UDO_PKG_EQUIPDS as
NEQUIPDSCMML in number -- Рег. номер модели класса оборудования выборки данных
);
/* Код доступного действия с моделью по единице оборудования */
function CMML_ACT_BY_EQCONFIG
/* Состояние моделей по единице оборудования */
function CMML_STATUS_BY_EQCONFIG
(
NEQCONFIG in number -- Рег. номер позиции состава оборудования
) return number; -- Код действия с моделью (0 - нет доступных, 1 - запросить прогноз, 2 - обучить)
) return number; -- Код действия с моделью (0 - нет моделей, 1 - есть модели в процессе обучения, 2 - есть обученные модели)
/* Список моделей по единице оборудования */
function CMML_LIST_BY_EQCONFIG
procedure CMML_LIST_BY_EQCONFIG
(
NEQCONFIG in number -- Рег. номер позиции состава оборудования
) return varchar2; -- Список моделей с системным разделителем по умолчанию
NEQCONFIG in number, -- Рег. номер позиции состава оборудования
COUT out clob -- Сериализованная таблица данных
);
end UDO_PKG_EQUIPDS;
/
@ -273,7 +274,7 @@ text="Проверка прав доступа на работу с ""Выбор
procedure CM_LIST
(
NEQUIPDS in number, -- Рег. номер выборки данных
COUT out clob -- Сериализованная таблица данных
COUT out clob -- Сериализованный список
)
is
NCUR integer; -- Курсор документа для результата
@ -493,7 +494,8 @@ text="Проверка прав доступа на работу с ""Выбор
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'STASK',
SCAPTION => 'Задача',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR);
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
SHINT => 'Алгоритм прогонозирования модели:<br>' || UDO_PKG_EQUIPDS_BASE.CMML_TASK_HINT());
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NPRECISION_P',
SCAPTION => 'Точность (план)',
@ -511,6 +513,10 @@ text="Проверка прав доступа на работу с ""Выбор
SCAPTION => 'Сообщение об ошибке',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
BVISIBLE => false);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SACTIONS',
SCAPTION => 'Действия',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR);
/* Обходим данные */
for C in (select T.RN NRN,
T.TASK STASK,
@ -529,6 +535,7 @@ text="Проверка прав доступа на работу с ""Выбор
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NPRECISION_F', NVALUE => C.NPRECISION_F);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NSTATUS', NVALUE => C.NSTATUS);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SERR', SVALUE => C.SERR);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SACTIONS');
/* Добавляем строку в таблицу */
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_ROW(RDATA_GRID => RDG, RROW => RDG_ROW);
end loop;
@ -585,25 +592,128 @@ text="Проверка прав доступа на работу с ""Выбор
end loop;
end CMML_SEND_RQ;
/* Код доступного действия с моделью по единице оборудования */
function CMML_ACT_BY_EQCONFIG
/* Состояние моделей по единице оборудования */
function CMML_STATUS_BY_EQCONFIG
(
NEQCONFIG in number -- Рег. номер позиции состава оборудования
) return number -- Код действия с моделью (0 - нет доступных, 1 - запросить прогноз, 2 - обучить)
NEQCONFIG in number -- Рег. номер позиции состава оборудования
) return number -- Код действия с моделью (0 - нет моделей, 1 - есть модели в процессе обучения, 2 - есть обученные модели)
is
NRES PKG_STD.TNUMBER; -- Буфер для результата
begin
return 2;
end CMML_ACT_BY_EQCONFIG;
/* По умолчанию - моделей нет */
NRES := 0;
/* Обходим модели */
for C in (select ML.STATUS
from UDO_T_EQUIPDSCM CM,
UDO_T_EQUIPDSCMML ML
where CM.EQOBJKIND = (select CF.OBJ_KIND from EQCONFIG CF where CF.RN = NEQCONFIG)
and CM.RN = ML.PRN)
loop
/* Модель есть */
NRES := 1;
/* Если она уже обучена */
if (C.STATUS = 2) then
NRES := 2;
end if;
end loop;
/* Возвращаем результат */
return NRES;
end CMML_STATUS_BY_EQCONFIG;
/* Список моделей по единице оборудования */
function CMML_LIST_BY_EQCONFIG
procedure CMML_LIST_BY_EQCONFIG
(
NEQCONFIG in number -- Рег. номер позиции состава оборудования
) return varchar2 -- Список моделей с системным разделителем по умолчанию
NEQCONFIG in number, -- Рег. номер позиции состава оборудования
COUT out clob -- Сериализованная таблица данных
)
is
RDG PKG_P8PANELS_VISUAL.TDATA_GRID; -- Описание таблицы
RDG_ROW PKG_P8PANELS_VISUAL.TROW; -- Строка таблицы
begin
return 'TCF;RUL;FP';
end CMML_LIST_BY_EQCONFIG;
/* Инициализируем таблицу данных */
RDG := PKG_P8PANELS_VISUAL.TDATA_GRID_MAKE();
/* Добавляем в таблицу описание колонок */
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NRN',
SCAPTION => 'Рег. номер модели',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB,
BVISIBLE => false);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NEQUIPDS',
SCAPTION => 'Рег. номер выборки',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB,
BVISIBLE => false);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NEQUIPDSCM',
SCAPTION => 'Рег. номер класса оборудования выборки',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB,
BVISIBLE => false);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SNEQUIPDS',
SCAPTION => 'Выборка',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'STASK',
SCAPTION => 'Задача',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
SHINT => 'Алгоритм прогонозирования модели:<br>' ||
UDO_PKG_EQUIPDS_BASE.CMML_TASK_HINT());
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NPRECISION_P',
SCAPTION => 'Точность (план)',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NPRECISION_F',
SCAPTION => 'Точность (факт)',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NSTATUS',
SCAPTION => 'Состояние',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SERR',
SCAPTION => 'Сообщение об ошибке',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
BVISIBLE => false);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SACTIONS',
SCAPTION => 'Действия',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR);
/* Обходим данные */
for C in (select T.RN NRN,
CM.PRN NEQUIPDS,
T.PRN NEQUIPDSCM,
DS.NAME SNEQUIPDS,
T.TASK STASK,
T.PRECISION_P NPRECISION_P,
T.PRECISION_F NPRECISION_F,
T.STATUS NSTATUS,
T.ERR SERR
from UDO_T_EQUIPDSCMML T,
UDO_T_EQUIPDSCM CM,
UDO_T_EQUIPDS DS
where T.PRN = CM.RN
and CM.PRN = DS.RN
and CM.EQOBJKIND = (select CF.OBJ_KIND from EQCONFIG CF where CF.RN = NEQCONFIG)
order by T.RN)
loop
/* Добавляем колонки с данными */
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NRN', NVALUE => C.NRN, BCLEAR => true);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NEQUIPDS', NVALUE => C.NEQUIPDS);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NEQUIPDSCM', NVALUE => C.NEQUIPDSCM);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SNEQUIPDS', SVALUE => C.SNEQUIPDS);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'STASK', SVALUE => C.STASK);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NPRECISION_P', NVALUE => C.NPRECISION_P);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NPRECISION_F', NVALUE => C.NPRECISION_F);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'NSTATUS', NVALUE => C.NSTATUS);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SERR', SVALUE => C.SERR);
PKG_P8PANELS_VISUAL.TROW_ADD_COL(RROW => RDG_ROW, SNAME => 'SACTIONS');
/* Добавляем строку в таблицу */
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_ROW(RDATA_GRID => RDG, RROW => RDG_ROW);
end loop;
/* Сериализуем описание */
COUT := PKG_P8PANELS_VISUAL.TDATA_GRID_TO_XML(RDATA_GRID => RDG, NINCLUDE_DEF => 1);
end CMML_LIST_BY_EQCONFIG;
end UDO_PKG_EQUIPDS;
/

View File

@ -142,6 +142,10 @@ create or replace package UDO_PKG_EQUIPDS_BASE as
SQUEUE_ID in varchar2, -- Идентификатор очереди обработки
SERR in varchar2 -- Сообщение об ошибке
);
/* Формирование подсказки для задачи "Выборки данных оборудования (классы оборудования, модели)" */
function CMML_TASK_HINT
return varchar2; -- Подсказка для задачи
end UDO_PKG_EQUIPDS_BASE;
/
@ -455,6 +459,14 @@ create or replace package body UDO_PKG_EQUIPDS_BASE as
T.ERR = SERR
where T.RN = NRN;
end CMML_SET_STATUS;
/* Формирование подсказки для задачи "Выборки данных оборудования (классы оборудования, модели)" */
function CMML_TASK_HINT
return varchar2 -- Подсказка для задачи
is
begin
return '<b>TCF</b> - Оценка технического состояния (<b>T</b>echnical <b>C</b>ondition <b>F</b>orecast)<br>' || '<b>RUL</b> - Прогнозирование остаточного ресурса (<b>R</b>emaining <b>U</b>seful <b>L</b>ife)<br>' || '<b>FP</b> - Прогнозирование отказа (<b>F</b>ailure <b>P</b>redict)';
end CMML_TASK_HINT;
end UDO_PKG_EQUIPDS_BASE;
/

View File

@ -166,13 +166,12 @@ text="Проверка прав доступа при формировании
PKG_COND_BROKER.ADD_CONDITION_CODE(SCOLUMN_NAME => 'NAME',
SCONDITION_NAME => 'SOBJ_KINDFrom',
SJOINS => 'OBJ_KIND <- RN;EQOBJKIND');
/* Модели прогнозирования */
if (PKG_COND_BROKER.CONDITION_EXISTS(SCONDITION_NAME => 'SEQUIPDSCMMLFrom') = 1) then
PKG_COND_BROKER.ADD_CLAUSE(SCLAUSE => 'UPPER(' ||
PKG_SQL_BUILD.PKG_NAME(SNAME => 'UDO_PKG_EQUIPDS.CMML_LIST_BY_EQCONFIG') ||
'(RN)) like REPLACE(REPLACE(UPPER(:SEQUIPDSCMML), ''?'', ''_''), ''*'', ''%'')');
PKG_COND_BROKER.BIND_VARIABLE(SVARIABLE_NAME => 'SEQUIPDSCMML',
SVALUE => PKG_COND_BROKER.GET_CONDITION_STR(SCONDITION_NAME => 'SEQUIPDSCMMLFrom'));
/* Действия с моделями прогнозирования */
if (PKG_COND_BROKER.CONDITION_EXISTS(SCONDITION_NAME => 'NCMML_STATUSFrom') = 1) then
PKG_COND_BROKER.ADD_CLAUSE(SCLAUSE => PKG_SQL_BUILD.PKG_NAME(SNAME => 'UDO_PKG_EQUIPDS.CMML_STATUS_BY_EQCONFIG') ||
'(RN) = :NCMML_STATUS');
PKG_COND_BROKER.BIND_VARIABLE(SVARIABLE_NAME => 'NCMML_STATUS',
SVALUE => PKG_COND_BROKER.GET_CONDITION_NUM(SCONDITION_NAME => 'NCMML_STATUSFrom'));
end if;
end EQCONFIG_THOBJ_LIST_COND;
@ -194,6 +193,7 @@ text="Проверка прав доступа при формировании
RO PKG_P8PANELS_VISUAL.TORDERS; -- Сортировки
RDG PKG_P8PANELS_VISUAL.TDATA_GRID; -- Описание таблицы
RDG_ROW PKG_P8PANELS_VISUAL.TROW; -- Строка таблицы
RCOL_VALS PKG_P8PANELS_VISUAL.TCOL_VALS; -- Предопределённые значения столбцов
NROW_FROM PKG_STD.TREF; -- Номер строки с
NROW_TO PKG_STD.TREF; -- Номер строки по
CSQL clob; -- Буфер для запроса
@ -251,21 +251,16 @@ text="Проверка прав доступа при формировании
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
BORDER => true,
BFILTER => true);
PKG_P8PANELS_VISUAL.TCOL_VALS_ADD(RCOL_VALS => RCOL_VALS, NVALUE => 0, BCLEAR => true);
PKG_P8PANELS_VISUAL.TCOL_VALS_ADD(RCOL_VALS => RCOL_VALS, NVALUE => 1);
PKG_P8PANELS_VISUAL.TCOL_VALS_ADD(RCOL_VALS => RCOL_VALS, NVALUE => 2);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'SEQUIPDSCMML',
SCAPTION => 'Модели прогнозирования',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_STR,
SHINT => '<b>Модели прогнозирования</b> - доступные для использования модели прогнозирования и задачи, ими решаемые:<br>' ||
'<b>TCF</b> - Оценка технического состояния (<b>T</b>echnical <b>C</b>ondition <b>F</b>orecast)<br>' ||
'<b>RUL</b> - Прогнозирование остаточного ресурса (<b>R</b>emaining <b>U</b>seful <b>L</b>ife)<br>' ||
'<b>FP</b> - Прогнозирование отказа (<b>F</b>ailure <b>P</b>redict)',
BORDER => true,
BFILTER => true);
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_COL_DEF(RDATA_GRID => RDG,
SNAME => 'NEQUIPDSCMML_ACTION',
SCAPTION => 'Действия с моделью',
SNAME => 'NCMML_STATUS',
SCAPTION => 'Модель',
SDATA_TYPE => PKG_P8PANELS_VISUAL.SDATA_TYPE_NUMB,
BVISIBLE => false);
BORDER => true,
BFILTER => true,
RCOL_VALS => RCOL_VALS);
/* Обходим данные */
begin
/* Добавляем подсказку совместимости */
@ -281,8 +276,7 @@ text="Проверка прав доступа при формировании
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' ST.CODE SUSE_KIND,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' C.OBJ_KIND NOBJ_KIND,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' OK.NAME SOBJ_KIND,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => PKG_SQL_BUILD.PKG_NAME(SNAME => 'UDO_PKG_EQUIPDS.CMML_LIST_BY_EQCONFIG') || '(C.RN) SEQUIPDSCMML,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => PKG_SQL_BUILD.PKG_NAME(SNAME => 'UDO_PKG_EQUIPDS.CMML_ACT_BY_EQCONFIG') || '(C.RN) NEQUIPDSCMML_ACTION');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => PKG_SQL_BUILD.PKG_NAME(SNAME => 'UDO_PKG_EQUIPDS.CMML_STATUS_BY_EQCONFIG') || '(C.RN) NCMML_STATUS');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' from EQCONFIG C');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' left outer join HLSTATETYPES ST on C.HLSTATETYPES = ST.RN,');
PKG_SQL_BUILD.APPEND(SSQL => CSQL, SELEMENT1 => ' EQOBJKIND OK');
@ -315,9 +309,8 @@ text="Проверка прав доступа при формировании
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 5);
PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 6);
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 7);
PKG_SQL_DML.DEFINE_COLUMN_STR(ICURSOR => ICURSOR, IPOSITION => 8);
PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 8);
PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 9);
PKG_SQL_DML.DEFINE_COLUMN_NUM(ICURSOR => ICURSOR, IPOSITION => 10);
/* Делаем выборку */
if (PKG_SQL_DML.EXECUTE(ICURSOR => ICURSOR) = 0) then
null;
@ -349,14 +342,10 @@ text="Проверка прав доступа при формировании
SNAME => 'SOBJ_KIND',
ICURSOR => ICURSOR,
NPOSITION => 7);
PKG_P8PANELS_VISUAL.TROW_ADD_CUR_COLS(RROW => RDG_ROW,
SNAME => 'SEQUIPDSCMML',
PKG_P8PANELS_VISUAL.TROW_ADD_CUR_COLN(RROW => RDG_ROW,
SNAME => 'NCMML_STATUS',
ICURSOR => ICURSOR,
NPOSITION => 8);
PKG_P8PANELS_VISUAL.TROW_ADD_CUR_COLN(RROW => RDG_ROW,
SNAME => 'NEQUIPDSCMML_ACTION',
ICURSOR => ICURSOR,
NPOSITION => 9);
/* Добавляем строку в таблицу */
PKG_P8PANELS_VISUAL.TDATA_GRID_ADD_ROW(RDATA_GRID => RDG, RROW => RDG_ROW);
end loop;
@ -388,9 +377,12 @@ text="Проверка прав доступа при формировании
for C in (select T.RN NRN,
T.CODE SCODE,
T.NAME SNAME,
T.OPER_DATE DOPER_DATE
from EQCONFIG T
where T.RN = NEQCONFIG)
T.OPER_DATE DOPER_DATE,
OK.NAME SEQOBJKIND
from EQCONFIG T,
EQOBJKIND OK
where T.RN = NEQCONFIG
and T.OBJ_KIND = OK.RN)
loop
/* Соберем карточку */
XCARD := PKG_XMAKE.ELEMENT(ICURSOR => NCUR,
@ -407,7 +399,10 @@ text="Проверка прав доступа при формировании
SVALUE => C.SNAME),
RATTRIBUTE03 => PKG_XMAKE.ATTRIBUTE(ICURSOR => NCUR,
SNAME => 'DOPER_DATE',
DVALUE => C.DOPER_DATE)));
DVALUE => C.DOPER_DATE),
RATTRIBUTE04 => PKG_XMAKE.ATTRIBUTE(ICURSOR => NCUR,
SNAME => 'SEQOBJKIND',
SVALUE => C.SEQOBJKIND)));
end loop;
/* Формируем XML-представление документа ответа */
XDOC := PKG_XMAKE.ELEMENT(ICURSOR => NCUR, SNAME => 'XDATA', RNODE00 => XCARD);

View File

@ -8,6 +8,7 @@
//---------------------
import React, { useState, useEffect, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Grid, Stack, Icon, Button, IconButton } from "@mui/material"; //Интерфейсные компоненты
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с серверомs
@ -16,7 +17,8 @@ import {
EquipDataSelectionList,
EquipDataSelectionClassMachineIU,
EquipDataSelectionClassMachineList,
EquipDataSelectionClassMachineCard
EquipDataSelectionClassMachineCard,
EquipDataSelectionClassMachineModelIU
} from "./admin_tab_layout"; //Вспомогательные компоненты и вёрстка
import {
DS_RN_DEFAULT,
@ -37,7 +39,8 @@ const REFRESH_INITIAL = {
dataSelection: null,
dataSelectionClassMachine: null,
dataSelectionClassMachineCard: 0,
dataSelectionClassMachineFilesList: 0
dataSelectionClassMachineFilesList: 0,
dataSelectionClassMachineModelsList: 0
};
//Стили
@ -51,7 +54,7 @@ const STYLES = {
//-----------
//Закладка администрирования
const AdminTab = () => {
const AdminTab = ({ dataSelection = DS_RN_DEFAULT, dataSelectionClassMachine = null }) => {
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
@ -59,16 +62,16 @@ const AdminTab = () => {
const { pOnlineUserProcedure } = useContext(ApplicationСtx);
//Собственное состояние - отображаемые диалоги
const [dialogs, setDialogs] = useState({ dataSelectionIU: false, dataSelectionClassMachineIU: false });
const [dialogs, setDialogs] = useState({ dataSelectionIU: false, dataSelectionClassMachineIU: false, dataSelectionClassMachineModelIU: false });
//Собственное состояние - флаги обновления данных
const [refresh, setRefresh] = useState(REFRESH_INITIAL);
//Собственное состояние - выбранная выборка данных
const [equipDataSelection, setDataSelection] = useState(DS_RN_DEFAULT);
const [equipDataSelection, setDataSelection] = useState(dataSelection);
//Собственное состояние - выбранный класс оборудования
const [equipDataSelectionClassMachine, setDataSelectionClassMachine] = useState(null);
const [equipDataSelectionClassMachine, setDataSelectionClassMachine] = useState(dataSelectionClassMachine);
//Загрузка списка выборок данных
const { equipDataSelectionList } = useEquipDataSelectionList(refresh.dataSelection);
@ -88,8 +91,11 @@ const AdminTab = () => {
refresh.dataSelectionClassMachineFilesList
);
//Загрузка файлов класса оборудования
const { equipDataSelectionClassMachineModelsList } = useEquipDataSelectionClassMachineModelsList(equipDataSelectionClassMachine);
//Загрузка моделей класса оборудования
const { equipDataSelectionClassMachineModelsList } = useEquipDataSelectionClassMachineModelsList(
equipDataSelectionClassMachine,
refresh.dataSelectionClassMachineModelsList
);
//При смене выборки данных
const handleDataSelectionChange = value => {
@ -166,37 +172,71 @@ const AdminTab = () => {
//При нажатии "Загрузить на сервер" в списке файлов карточки класса оборудования
const handleUploadEquipDataSelectionClassMachineFiles = async equipDSCM => {
await executeStored({
stored: "UDO_PKG_EQUIPDS.CMFL_UPLOAD",
args: {
NEQUIPDSCM: equipDSCM
}
});
await executeStored({ stored: "UDO_PKG_EQUIPDS.CMFL_UPLOAD", args: { NEQUIPDSCM: equipDSCM } });
setRefresh(pv => ({ ...pv, dataSelectionClassMachineFilesList: pv.dataSelectionClassMachineFilesList + 1 }));
};
//При нажатии "Передать внешней системе" в списке файлов карточки класса оборудования
const handleSendMdEquipDataSelectionClassMachineFiles = async equipDSCM => {
await executeStored({
stored: "UDO_PKG_EQUIPDS.CMFL_SEND_MD",
args: {
NEQUIPDSCM: equipDSCM
}
});
await executeStored({ stored: "UDO_PKG_EQUIPDS.CMFL_SEND_MD", args: { NEQUIPDSCM: equipDSCM } });
setRefresh(pv => ({ ...pv, dataSelectionClassMachineFilesList: pv.dataSelectionClassMachineFilesList + 1 }));
};
//При нажатии на "Добавить модель класса оборудования"
const handleAddEquipDataSelectionClassMachineModel = () => setDialogs(pv => ({ ...pv, dataSelectionClassMachineModelIU: true }));
//При нажатии на "Удалить модель класса оборудования"
const handleDeleteEquipDataSelectionClassMachineModel = async equipDSCMML => {
await executeStored({ stored: "UDO_PKG_EQUIPDS.CMML_DEL", args: { NRN: equipDSCMML } });
setRefresh(pv => ({ ...pv, dataSelectionClassMachineModelsList: pv.dataSelectionClassMachineModelsList + 1 }));
};
//При отмене диалога IU модели класса оборудования
const handleEquipDataSelectionClassMachineModelIUCancel = () => setDialogs(pv => ({ ...pv, dataSelectionClassMachineModelIU: false }));
//При сохранении диалога IU модели класса оборудования
const handleEquipDataSelectionClassMachineModelIUOk = async values => {
await executeStored({
stored: "UDO_PKG_EQUIPDS.CMML_INS",
args: {
NPRN: equipDataSelectionClassMachine,
STASK: values.task,
NPRECISION_P: parseInt(values.precisionP)
}
});
setDialogs(pv => ({ ...pv, dataSelectionClassMachineModelIU: false }));
setRefresh(pv => ({ ...pv, dataSelectionClassMachineModelsList: pv.dataSelectionClassMachineModelsList + 1 }));
};
//При нажатии "Обучить" в списке моделей карточки класса оборудования
const handleSendRqEquipDataSelectionClassMachineModel = async equipDSCMML => {
await executeStored({ stored: "UDO_PKG_EQUIPDS.CMML_SEND_RQ", args: { NEQUIPDSCMML: equipDSCMML } });
setRefresh(pv => ({ ...pv, dataSelectionClassMachineModelsList: pv.dataSelectionClassMachineModelsList + 1 }));
};
//При измении выборки данных через свойства
useEffect(() => {
setDataSelection(dataSelection);
}, [dataSelection]);
//При изменеии класса оборудования через свойства
useEffect(() => {
setDataSelectionClassMachine(dataSelectionClassMachine);
}, [dataSelectionClassMachine]);
//При изменении списка выборок данных
useEffect(() => {
if (refresh.action == "INS" && refresh.dataSelection) {
setDataSelection(refresh.dataSelection);
setDataSelectionClassMachine(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [equipDataSelectionList]);
//При изменении списка классов оборудования выборки данных
useEffect(() => {
if (refresh.action == "INS" && refresh.dataSelectionClassMachine) setDataSelectionClassMachine(refresh.dataSelectionClassMachine);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [equipDataSelectionClassMachineList]);
//Генерация содержимого
@ -211,6 +251,12 @@ const AdminTab = () => {
onCancel={handleEquipDataSelectionClassMachineIUCancel}
/>
) : null}
{dialogs.dataSelectionClassMachineModelIU ? (
<EquipDataSelectionClassMachineModelIU
onOk={handleEquipDataSelectionClassMachineModelIUOk}
onCancel={handleEquipDataSelectionClassMachineModelIUCancel}
/>
) : null}
<Grid container>
<Grid item xs={12}>
<Stack direction="row" spacing={2} p={2} sx={STYLES.DATA_SELECTION_STACK}>
@ -251,6 +297,9 @@ const AdminTab = () => {
onClassMachineFilesMake={handleMakeEquipDataSelectionClassMachineFiles}
onClassMachineFilesUpload={handleUploadEquipDataSelectionClassMachineFiles}
onClassMachineFilesSendMd={handleSendMdEquipDataSelectionClassMachineFiles}
onClassMachineModelAdd={handleAddEquipDataSelectionClassMachineModel}
onClassMachineModelDelete={handleDeleteEquipDataSelectionClassMachineModel}
onClassMachineModelSendRq={handleSendRqEquipDataSelectionClassMachineModel}
/>
</Grid>
</Grid>
@ -260,6 +309,12 @@ const AdminTab = () => {
);
};
//Контроль свойств - Закладка администрирования
AdminTab.propTypes = {
dataSelection: PropTypes.number,
dataSelectionClassMachine: PropTypes.number
};
//----------------
//Интерфейс модуля
//----------------

View File

@ -176,7 +176,7 @@ const useEquipDataSelectionClassMachineFilesList = (classMachine, refresh) => {
};
//Загрузка списка моделей класса оборудования
const useEquipDataSelectionClassMachineModelsList = classMachine => {
const useEquipDataSelectionClassMachineModelsList = (classMachine, refresh) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
@ -212,7 +212,7 @@ const useEquipDataSelectionClassMachineModelsList = classMachine => {
}
};
if (classMachine) loadData();
}, [classMachine, executeStored]);
}, [classMachine, refresh, executeStored]);
//Вернём данные
return { equipDataSelectionClassMachineModelsList: data, equipDataSelectionClassMachineModelsListIsLoading: isLoading };

View File

@ -36,9 +36,10 @@ import {
import { useTheme } from "@mui/material/styles"; //Темы оформления
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы и константы
import { APP_BAR_HEIGHT, TABS_HEIGHT, SCROLL_STYLES } from "./eqs_tech_cond_forecast_lyaout"; //Общие вспомогательные компоненты и вёрстка
import { APP_BAR_HEIGHT, TABS_HEIGHT, SCROLL_STYLES, formatModelStateValue } from "./eqs_tech_cond_forecast_lyaout"; //Общие вспомогательные компоненты и вёрстка
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { DS_RN_DEFAULT } from "./admin_tab_hooks"; //Вспомогательные хуки
//---------
//Константы
@ -104,7 +105,7 @@ const STYLES = {
//------------------------------------
//Поле ввода из словаря
const IUDFormTextField = ({ elementCode, elementValue, labelText, onChange, dictionary, ...other }) => {
const IUDFormTextField = ({ elementCode, elementValue, labelText, onChange, dictionary, list, ...other }) => {
//Значение элемента
const [value, setValue] = useState(elementValue);
@ -119,6 +120,7 @@ const IUDFormTextField = ({ elementCode, elementValue, labelText, onChange, dict
//Изменение значения элемента
const handleChange = e => {
console.log(e.target.name, e.target.value);
setValue(e.target.value);
if (onChange) onChange(e.target.name, e.target.value);
};
@ -127,22 +129,45 @@ const IUDFormTextField = ({ elementCode, elementValue, labelText, onChange, dict
return (
<Box p={1}>
<FormControl variant="standard" fullWidth {...other}>
<InputLabel htmlFor={elementCode}>{labelText}</InputLabel>
<Input
id={elementCode}
name={elementCode}
value={value ? value : ""}
endAdornment={
dictionary ? (
<InputAdornment position="end">
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
<Icon>list</Icon>
</IconButton>
</InputAdornment>
) : null
}
onChange={handleChange}
/>
{list ? (
<>
<InputLabel id={`${elementCode}Lable`}>{labelText}</InputLabel>
<Select
labelId={`${elementCode}Lable`}
id={elementCode}
name={elementCode}
label={labelText}
value={value ? value : ""}
onChange={handleChange}
displayEmpty
>
{list.map((item, i) => (
<MenuItem key={i} value={item.value ? item.value : ""}>
{item.name}
</MenuItem>
))}
</Select>
</>
) : (
<>
<InputLabel htmlFor={elementCode}>{labelText}</InputLabel>
<Input
id={elementCode}
name={elementCode}
value={value ? value : ""}
endAdornment={
dictionary ? (
<InputAdornment position="end">
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
<Icon>list</Icon>
</IconButton>
</InputAdornment>
) : null
}
onChange={handleChange}
/>
</>
)}
</FormControl>
</Box>
);
@ -154,7 +179,8 @@ IUDFormTextField.propTypes = {
elementValue: PropTypes.string,
labelText: PropTypes.string.isRequired,
onChange: PropTypes.func,
dictionary: PropTypes.func
dictionary: PropTypes.func,
list: PropTypes.array
};
//Кнопка с дополнительной подписью
@ -165,7 +191,9 @@ const ExtraCaptionButton = ({ caption, title, subtitle, maxWidth, onClick, theme
//Генерация содержимого
return (
<Stack sx={STYLES.EXTRA_CAPTION_BUTTON_CONTAINER(theme, maxWidth)}>
<Button onClick={handleClick}>{caption}</Button>
<Button onClick={handleClick} size={"small"}>
{caption}
</Button>
<Typography variant="caption" color="text.secondary" sx={STYLES.EXTRA_CAPTION_BUTTON_TITLE} title={subtitle}>
<b>{title}</b> {subtitle}
</Typography>
@ -218,6 +246,37 @@ const filesListDataCellRender = ({ row, columnDef, theme }) => {
}
};
//Форматирование колонок таблицы моделей класса оборудования выборки данных
const modelsListDataCellRender = ({ row, columnDef, theme, card, onDelete, onSendRq }) => {
switch (columnDef.name) {
case "NSTATUS":
return {
cellProps: { align: "left" },
data: formatModelStateValue(theme, row.NSTATUS, row.SERR)
};
case "SACTIONS":
return {
data: (
<Stack spacing={1} direction={"row"} alignItems={"center"}>
<IconButton onClick={() => (onDelete ? onDelete(row.NRN) : null)}>
<Icon>delete</Icon>
</IconButton>
{row.NSTATUS == 0 ? (
<ExtraCaptionButton
caption="Запрос на обучение"
title="СО:"
subtitle={card.SEXSSERVICE_SEND_RQ}
maxWidth="200px"
onClick={() => (onSendRq ? onSendRq(row.NRN) : null)}
theme={theme}
/>
) : null}
</Stack>
)
};
}
};
//Форматирование ячеек заголовка таблиц файлов и моделей класса оборудования выборки данных
const classMachineCardTableHeadCellRender = ({ columnDef }) => {
switch (columnDef.name) {
@ -226,6 +285,8 @@ const classMachineCardTableHeadCellRender = ({ columnDef }) => {
stackProps: { justifyContent: "left" },
cellProps: { align: "left" }
};
case "SACTIONS":
return { data: " " };
}
};
@ -401,6 +462,50 @@ EquipDataSelectionClassMachineIU.propTypes = {
onCancel: PropTypes.func
};
//Диалог IU модели класса оборудования выборки данных
const EquipDataSelectionClassMachineModelIU = ({ onOk, onCancel }) => {
//Собственное состояние - значения формы
const [values, setValues] = useState({
task: "RUL",
precisionP: ""
});
//Отработка воода значения в форму
const handleValueChanged = (name, value) => setValues(pv => ({ ...pv, [name]: value }));
//Генерация содержимого
return (
<Dialog open={true} onClose={() => (onOk ? onCancel() : null)}>
<DialogTitle>Модель класса оборудования выборки данных</DialogTitle>
<DialogContent>
<IUDFormTextField
elementCode={"task"}
elementValue={values.task}
labelText={"Задача"}
onChange={handleValueChanged}
list={[
{ value: null, name: "Не указана" },
{ value: "TCF", name: "TCF - оценка технического состояния (Technical Condition Forecast)" },
{ value: "RUL", name: "RUL - прогнозирование остаточного ресурса (Remaining Useful Life)" },
{ value: "FP", name: "FP - Прогнозирование отказа (Failure Predict)" }
]}
/>
<IUDFormTextField elementCode={"precisionP"} elementValue={values.precisionP} labelText={"Точность"} onChange={handleValueChanged} />
</DialogContent>
<DialogActions>
<Button onClick={() => (onOk ? onOk(values) : null)}>{BUTTONS.OK}</Button>
<Button onClick={() => (onOk ? onCancel() : null)}>{BUTTONS.CANCEL}</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог IU модели класса оборудования выборки данных
EquipDataSelectionClassMachineModelIU.propTypes = {
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//Список выборок данных оборудования
const EquipDataSelectionList = ({ list, value, onChange }) => {
//При выборе элемента
@ -410,7 +515,14 @@ const EquipDataSelectionList = ({ list, value, onChange }) => {
return (
<FormControl variant="standard" sx={STYLES.EQUIP_DS_LIST_FORM_CONTROL}>
<InputLabel id="equipDSlabel">Выборка данных оборудования</InputLabel>
<Select labelId="equipDSlabel" id="equipDS" label="Выборка данных оборудования" value={value} onChange={handleChange}>
<Select
labelId="equipDSlabel"
id="equipDS"
label="Выборка данных оборудования"
value={list.length == 1 ? DS_RN_DEFAULT : value}
onChange={handleChange}
displayEmpty
>
{list.map((item, i) => (
<MenuItem key={i} value={item.NRN}>
{item.SNAME}
@ -462,7 +574,10 @@ const EquipDataSelectionClassMachineCard = ({
onCardDelete,
onClassMachineFilesMake,
onClassMachineFilesUpload,
onClassMachineFilesSendMd
onClassMachineFilesSendMd,
onClassMachineModelAdd,
onClassMachineModelDelete,
onClassMachineModelSendRq
}) => {
//Подключаемся к теме
const theme = useTheme();
@ -479,6 +594,15 @@ const EquipDataSelectionClassMachineCard = ({
//При нажатии на "Загрузить на сервер" для файлов данных
const handleClassMachineFilesSendMdClick = () => (onClassMachineFilesSendMd ? onClassMachineFilesSendMd(card.NRN) : null);
//При нажатии на "Добавить" для моделей
const handleClassMachineModelAddClick = () => (onClassMachineModelAdd ? onClassMachineModelAdd() : null);
//При нажатии на "Удалить" для моделей
const handleClassMachineModelDeleteClick = equipDSCMML => (onClassMachineModelDelete ? onClassMachineModelDelete(equipDSCMML) : null);
//При нажатии на "Обучить" для моделей
const handleClassMachineModelSendRqClick = equipDSCMML => (onClassMachineModelSendRq ? onClassMachineModelSendRq(equipDSCMML) : null);
//Генерация содержимого
return (
<Box p={2} sx={STYLES.EQUIP_DSCM_CARD}>
@ -525,10 +649,7 @@ const EquipDataSelectionClassMachineCard = ({
</Stack>
<P8PDataGrid
{...{ ...P8P_DATA_GRID_CONFIG_PROPS }}
containerComponentProps={{
sx: STYLES.FL_ML_TABLE,
elevation: 0
}}
containerComponentProps={{ sx: STYLES.FL_ML_TABLE, elevation: 0 }}
columnsDef={filesList.columnsDef}
rows={filesList.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
@ -546,13 +667,13 @@ const EquipDataSelectionClassMachineCard = ({
<Typography variant="h6" component="div">
Модели
</Typography>
<IconButton onClick={handleClassMachineModelAddClick}>
<Icon>add</Icon>
</IconButton>
</Stack>
<P8PDataGrid
{...{ ...P8P_DATA_GRID_CONFIG_PROPS }}
containerComponentProps={{
sx: STYLES.FL_ML_TABLE,
elevation: 0
}}
containerComponentProps={{ sx: STYLES.FL_ML_TABLE, elevation: 0 }}
columnsDef={modelsList.columnsDef}
rows={modelsList.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
@ -560,6 +681,15 @@ const EquipDataSelectionClassMachineCard = ({
fixedHeader={true}
reloading={false}
headCellRender={classMachineCardTableHeadCellRender}
dataCellRender={prms =>
modelsListDataCellRender({
...prms,
theme,
card,
onDelete: handleClassMachineModelDeleteClick,
onSendRq: handleClassMachineModelSendRqClick
})
}
/>
</CardContent>
</Card>
@ -577,7 +707,10 @@ EquipDataSelectionClassMachineCard.propTypes = {
onCardDelete: PropTypes.func,
onClassMachineFilesMake: PropTypes.func,
onClassMachineFilesUpload: PropTypes.func,
onClassMachineFilesSendMd: PropTypes.func
onClassMachineFilesSendMd: PropTypes.func,
onClassMachineModelAdd: PropTypes.func,
onClassMachineModelDelete: PropTypes.func,
onClassMachineModelSendRq: PropTypes.func
};
//----------------
@ -587,6 +720,7 @@ EquipDataSelectionClassMachineCard.propTypes = {
export {
EquipDataSelectionIU,
EquipDataSelectionClassMachineIU,
EquipDataSelectionClassMachineModelIU,
EquipDataSelectionList,
EquipDataSelectionClassMachineList,
EquipDataSelectionClassMachineCard

View File

@ -17,6 +17,13 @@ import { ForecastTab } from "./forecast_tab"; //Интерфейс прогно
//Константы
//---------
//Состояние по умолчанию
const STATE_INITIAL = {
mode: MODES.FORECAST,
dataSelection: -1,
dataSelectionClassMachine: null
};
//Стили
const STYLES = {
TABS_CONTAINER_BOX: { borderBottom: 1, borderColor: "divider" }
@ -29,25 +36,34 @@ const STYLES = {
//Корневая панель прогнозирования технического состояния
const EqsTechCondForecast = () => {
//Собственное состояние
const [mode, setMode] = useState(MODES.ADMIN);
const [state, setState] = useState(STATE_INITIAL);
//При переключении закладки
const handleTabChange = (e, newValue) => setMode(newValue);
const handleTabChange = (e, newValue) => setState(pv => ({ ...pv, mode: newValue }));
//При запросе перехода на закладку администрирования
const handleGoToAdmin = (dataSelection, dataSelectionClassMachine) =>
setState(pv => ({
...pv,
mode: MODES.ADMIN,
dataSelection: dataSelection ? dataSelection : STATE_INITIAL.dataSelection,
dataSelectionClassMachine: dataSelectionClassMachine ? dataSelectionClassMachine : STATE_INITIAL.dataSelectionClassMachine
}));
//Генерация содержимого
return (
<>
<Box sx={STYLES.TABS_CONTAINER_BOX}>
<Tabs value={mode} onChange={handleTabChange}>
<Tabs value={state.mode} onChange={handleTabChange}>
<Tab label="Прогноз" value={MODES.FORECAST} />
<Tab label="Обучение" value={MODES.ADMIN} />
</Tabs>
</Box>
<TabPanel mode={mode} value={MODES.FORECAST}>
<ForecastTab />
<TabPanel mode={state.mode} value={MODES.FORECAST}>
<ForecastTab onGoToAdmin={handleGoToAdmin} />
</TabPanel>
<TabPanel mode={mode} value={MODES.ADMIN}>
<AdminTab />
<TabPanel mode={state.mode} value={MODES.ADMIN}>
<AdminTab dataSelection={state.dataSelection} dataSelectionClassMachine={state.dataSelectionClassMachine} />
</TabPanel>
</>
);

View File

@ -9,7 +9,7 @@
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box } from "@mui/material"; //Интерфейсные компоненты
import { Box, Stack, Icon } from "@mui/material"; //Интерфейсные компоненты
//---------
//Константы
@ -56,6 +56,24 @@ const SCROLL_STYLES = {
//Тело модуля
//-----------
//Формирование значения для колонки "Состояние" модели класса оборудования выборки данных
const formatModelStateValue = (theme, value, err) => {
const [text, icon, color] =
value == 0
? ["Зарегистрирована", "app_registration", null]
: value == 1
? ["Обрабатывается внешней системой", "manage_history", theme.palette.warning.main]
: value == 2
? ["Успешно обработана внешней системой", "check_circle", theme.palette.primary.main]
: [`Ошибка обработки внешней системой: ${err}`, "error", theme.palette.error.main];
return (
<Stack direction="row" gap={0.5} alignItems="center" justifyContent="left" color={color}>
<Icon title={text}>{icon}</Icon>
{text}
</Stack>
);
};
//Закладка
const TabPanel = ({ mode, value, children }) => <Box hidden={mode != value}>{children}</Box>;
@ -70,4 +88,4 @@ TabPanel.propTypes = {
//Интерфейс модуля
//----------------
export { MODES, APP_BAR_HEIGHT, TABS_HEIGHT, TABLE_MORE_HEIGHT, TABLE_FILTERS_HEIGHT, SCROLL_STYLES, TabPanel };
export { MODES, APP_BAR_HEIGHT, TABS_HEIGHT, TABLE_MORE_HEIGHT, TABLE_FILTERS_HEIGHT, SCROLL_STYLES, formatModelStateValue, TabPanel };

View File

@ -8,14 +8,23 @@
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Grid } from "@mui/material"; //Интерфейсные компоненты
import { RichTreeView } from "@mui/x-tree-view/RichTreeView"; //Дерево
import { useTreeViewApiRef } from "@mui/x-tree-view/hooks/useTreeViewApiRef"; //API дерева
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { APP_BAR_HEIGHT, TABS_HEIGHT, TABLE_FILTERS_HEIGHT, TABLE_MORE_HEIGHT, SCROLL_STYLES } from "./eqs_tech_cond_forecast_lyaout"; //Общие Вспомогательные компоненты и вёрстка
import { TechObjCard, eqConfigTechObjTableValueFormatter } from "./forecast_tab_layout"; //Вспомогательные компоненты и вёрстка
import { useEqConfigTree, useEqConfigTechObjTable, useEqConfigTechObjCard, findTreeItem, needLoadLevel } from "./forecast_tab_hooks"; //Вспомогательные хуки
import { TechObjCard, eqConfigTechObjTableValueFormatter, eqConfigTechObjTableDataCellRender } from "./forecast_tab_layout"; //Вспомогательные компоненты и вёрстка
import {
useEqConfigTree,
useEqConfigTechObjTable,
useEqConfigTechObjCard,
useTechObjModelsList,
findTreeItem,
needLoadLevel
} from "./forecast_tab_hooks"; //Вспомогательные хуки
//---------
//Константы
@ -49,10 +58,13 @@ const TECH_OBJ_SPEC_INIT = { parent: null, filters: [], orders: [], pageNumber:
//-----------
//Закладка прогнозирования
const ForecastTab = () => {
const ForecastTab = ({ onGoToAdmin }) => {
//Подключение к контексту сообщений
const { InlineMsgInfo } = useContext(MessagingСtx);
//Подключение к API дерева
const apiRef = useTreeViewApiRef();
//Собственное состояние - текущая загружаемая ветка
const [loadingTreeItem, setLoadingTreeItem] = useState(0);
@ -73,9 +85,12 @@ const ForecastTab = () => {
techObjSpec.pageNumber
);
//Загрузчик карточки выбоанного технического объекта
//Загрузчик карточки выбранного технического объекта
const { techObjCard, techObjCardIsLoading } = useEqConfigTechObjCard(techObjCardId);
//Загрузчик моделей выбранного технического объекта
const { techObjModelsList, techObjModelsListIsLoading } = useTechObjModelsList(techObjCardId);
//Обработка развёртывания/свёртывания уровня дерева
const handleTreeItemExpansionToggle = (event, itemId, isExpanded) =>
isExpanded && needLoadLevel(eQconfigTree, itemId) ? setLoadingTreeItem(parseInt(itemId)) : null;
@ -99,6 +114,23 @@ const ForecastTab = () => {
//При изменении количества отображаемых страниц спецификации технических объектов
const handleTechObjSpecPagesCountChanged = () => setTechObjSpec(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));
//При выполнении действия в колоке "Модели" таблицы технических объектов
const handleCMMLStatusClick = (event, row) => {
if (row.NCMML_STATUS == 0) {
if (onGoToAdmin) onGoToAdmin();
} else {
const item = findTreeItem(eQconfigTree, row.NRN);
if (item && item?.showCard) apiRef.current?.focusItem(event, item.id);
else setTechObjCardId(parseInt(row.NRN));
}
};
//При переходе к закладке администрирования из карточки технического объекта
const handleTechObjeCardGoToModel = model => (onGoToAdmin ? onGoToAdmin(model.NEQUIPDS, model.NEQUIPDSCM) : null);
//При закрытии карточки технического объекта
const handleTechObjeCardClose = () => setTechObjCardId(null);
//Текст при отсутствии данных в списке технических объектов
const noDataFoundText =
techObjsDataGridIsLoading || !techObjsDataGrid.init
@ -113,13 +145,23 @@ const ForecastTab = () => {
<Grid container>
<Grid item xs={3} sx={STYLES.LEFT_SIDE_GRID}>
<Box sx={STYLES.TREE_BOX}>
<RichTreeView items={eQconfigTree} onItemExpansionToggle={handleTreeItemExpansionToggle} onItemFocus={handleTreeItemFocus} />
<RichTreeView
apiRef={apiRef}
items={eQconfigTree}
onItemExpansionToggle={handleTreeItemExpansionToggle}
onItemFocus={handleTreeItemFocus}
/>
</Box>
</Grid>
<Grid item xs={9} sx={STYLES.RIGHT_SIDE_GRID}>
{techObjCardId ? (
!techObjCardIsLoading ? (
<TechObjCard cardData={techObjCard} />
!techObjCardIsLoading && !techObjModelsListIsLoading ? (
<TechObjCard
cardData={techObjCard}
modelsList={techObjModelsList}
onClose={handleTechObjeCardClose}
onGoToModel={handleTechObjeCardGoToModel}
/>
) : null
) : techObjsDataGrid.init ? (
<P8PDataGrid
@ -136,6 +178,7 @@ const ForecastTab = () => {
fixedHeader={true}
reloading={false}
valueFormatter={eqConfigTechObjTableValueFormatter}
dataCellRender={prms => eqConfigTechObjTableDataCellRender({ ...prms, onCMMLStatus: handleCMMLStatusClick })}
onFilterChanged={handleTechObjSpecFilterChanged}
onOrderChanged={handleTechObjSpecOrderChanged}
onPagesCountChanged={handleTechObjSpecPagesCountChanged}
@ -149,6 +192,11 @@ const ForecastTab = () => {
);
};
//Контроль свойств - Закладка прогнозирования
ForecastTab.propTypes = {
onGoToAdmin: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------

View File

@ -126,7 +126,7 @@ const useEqConfigTechObjTable = (parent, filters, orders, pageNumber) => {
NINCLUDE_DEF: pageNumber == 1 ? 1 : 0
},
respArg: "COUT",
loader: false
loader: true
});
setData(pv => ({
...pv,
@ -182,8 +182,51 @@ const useEqConfigTechObjCard = id => {
return { techObjCard: card, techObjCardIsLoading: isLoading };
};
//Загрузка списка моделей единицы оборудования
const useTechObjModelsList = (id, refresh) => {
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - таблица данных
const [data, setData] = useState({
init: false,
columnsDef: [],
rows: []
});
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Загрузка данных при изменении зависимостей
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "UDO_PKG_EQUIPDS.CMML_LIST_BY_EQCONFIG",
args: { NEQCONFIG: id },
respArg: "COUT",
loader: false
});
setData(pv => ({
...pv,
columnsDef: [...data.XCOLUMNS_DEF],
rows: [...(data.XROWS || [])],
init: true
}));
} finally {
setLoading(false);
}
};
if (id) loadData();
}, [id, refresh, executeStored]);
//Вернём данные
return { techObjModelsList: data, techObjModelsListIsLoading: isLoading };
};
//----------------
//Интерфейс модуля
//----------------
export { useEqConfigTree, useEqConfigTechObjTable, useEqConfigTechObjCard, findTreeItem, needLoadLevel };
export { useEqConfigTree, useEqConfigTechObjTable, useEqConfigTechObjCard, findTreeItem, needLoadLevel, useTechObjModelsList };

View File

@ -9,8 +9,13 @@
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Card, CardContent, Typography, CardActions, Button } from "@mui/material"; //Интерфейсные компоненты
import { Stack, Icon, Box, Card, CardContent, Typography, CardActions, Button, IconButton, Divider } from "@mui/material"; //Интерфейсные компоненты
import { useTheme } from "@mui/material/styles"; //Темы оформления
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { formatDateRF } from "../../core/utils"; //Вспомогательные функции
import { SCROLL_STYLES, formatModelStateValue } from "./eqs_tech_cond_forecast_lyaout"; //Общие вспомогательные компоненты и вёрстка
import { Link } from "react-router-dom";
//---------
//Константы
@ -19,7 +24,59 @@ import { formatDateRF } from "../../core/utils"; //Вспомогательны
//Стили
const STYLES = {
TECH_OBJ_CARD_TITLE: { fontSize: 14 },
TECH_OBJ_CARD_SUB_TITLE: { mb: 1.5 }
TECH_OBJ_CARD_SUB_TITLE: { mb: 1.5 },
EQ_ML_TABLE: {
maxHeight: "200px",
...SCROLL_STYLES
}
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Форматирование ячеек заголовка таблицы моделей единицы оборудования
const techObjCardModelsTableHeadCellRender = ({ columnDef }) => {
switch (columnDef.name) {
case "NSTATUS":
return {
stackProps: { justifyContent: "left" },
cellProps: { align: "left" }
};
case "SACTIONS":
return { data: " " };
}
};
//Форматирование колонок таблицы моделей класса оборудования выборки данных
const techObjCardModelsTableDataCellRender = ({ row, columnDef, theme, onGoToModel, onGetPrediction }) => {
switch (columnDef.name) {
case "SNEQUIPDS":
case "STASK":
case "NPRECISION_P":
case "NPRECISION_F":
return {
data: (
<Link component="button" variant="body2" align="left" underline="hover" onClick={() => onGoToModel(row)}>
{row[columnDef.name]}
</Link>
)
};
case "NSTATUS":
return {
cellProps: { align: "left" },
data: formatModelStateValue(theme, row.NSTATUS, row.SERR)
};
case "SACTIONS":
return {
data:
row.NSTATUS == 2 ? (
<Button variant="outlined" color="success" onClick={() => onGetPrediction(row)}>
Прогноз
</Button>
) : null
};
}
};
//-----------
@ -27,27 +84,71 @@ const STYLES = {
//-----------
//Карточка технического объекта
const TechObjCard = ({ cardData }) => {
const TechObjCard = ({ cardData, modelsList, onClose, onGoToModel }) => {
//Подключаемся к теме
const theme = useTheme();
//При нажатии на кнопку закрытия карточки
const handleClose = () => (onClose ? onClose() : null);
//При нажатии на переход к модели
const handleGoToModelClick = model => (onGoToModel ? onGoToModel(model) : null);
//При нажатии на запрос предсказания
const handleGetPredictionClick = model => {
console.log(model);
};
//Генерация содержимого
return (
<Box p={2}>
<Card>
<CardContent>
<Typography sx={STYLES.TECH_OBJ_CARD_TITLE} color="text.secondary" gutterBottom>
Технический объект
</Typography>
<Stack spacing={2} direction={"row"} alignItems={"center"}>
<IconButton onClick={handleClose}>
<Icon>close</Icon>
</IconButton>
<Typography sx={STYLES.TECH_OBJ_CARD_TITLE} color="text.secondary" variant="caption" gutterBottom>
Технический объект
</Typography>
</Stack>
<Typography variant="h5" component="div">
{cardData.SCODE}
</Typography>
<Typography sx={STYLES.TECH_OBJ_CARD_SUB_TITLE} color="text.secondary">
{cardData.DOPER_DATE}
<Typography sx={STYLES.TECH_OBJ_CARD_SUB_TITLE} color="text.secondary" variant="caption" gutterBottom>
{`Введён в эксплуатацию: ${formatDateRF(cardData.DOPER_DATE)}`}
<br />
{`Класс: ${cardData.SEQOBJKIND}`}
</Typography>
<Typography variant="body2">{cardData.SNAME}</Typography>
<Typography variant="h6">{cardData.SNAME}</Typography>
<br />
<Divider />
<Typography variant="h6" component="div">
Модели
</Typography>
<P8PDataGrid
{...{ ...P8P_DATA_GRID_CONFIG_PROPS }}
containerComponentProps={{ sx: STYLES.EQ_ML_TABLE, elevation: 0 }}
columnsDef={modelsList.columnsDef}
rows={modelsList.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
morePages={false}
fixedHeader={true}
reloading={false}
headCellRender={techObjCardModelsTableHeadCellRender}
dataCellRender={prms =>
techObjCardModelsTableDataCellRender({
...prms,
theme,
onGoToModel: handleGoToModelClick,
onGetPrediction: handleGetPredictionClick
})
}
/>
<Divider />
</CardContent>
<CardActions>
<Button size="large">Прогнозировать</Button>
<Button size="large">Ремонтировать</Button>
<Button size="large">Обучать</Button>
<Button>Ремонтировать</Button>
</CardActions>
</Card>
</Box>
@ -56,20 +157,49 @@ const TechObjCard = ({ cardData }) => {
//Контроль свойств - Карточка технического объекта
TechObjCard.propTypes = {
cardData: PropTypes.object
cardData: PropTypes.object.isRequired,
modelsList: PropTypes.object.isRequired,
onClose: PropTypes.func,
onGoToModel: PropTypes.func
};
//Формирование значения для колонки "Медель" таблицы технических объектов
const formatTechObjCMMLStatusValue = value => (value == 0 ? "Обучить" : value == 1 ? "Обучается" : "Прогнозировать");
//Формирование цвета для колонки "Медель" таблицы технических объектов
const formatTechObjCMMLStatusColor = value => (value == 0 ? "primary" : value == 1 ? "warning" : "success");
//Форматирование значений колонок таблицы технических объектов
const eqConfigTechObjTableValueFormatter = ({ value, columnDef }) => {
switch (columnDef.name) {
case "DOPER_DATE":
return formatDateRF(value);
case "NCMML_STATUS":
return formatTechObjCMMLStatusValue(value);
}
return value;
};
//Форматирование колонок таблицы технических объектов
const eqConfigTechObjTableDataCellRender = ({ row, columnDef, onCMMLStatus }) => {
switch (columnDef.name) {
case "NCMML_STATUS":
return {
data: (
<Button
variant="outlined"
color={formatTechObjCMMLStatusColor(row[columnDef.name])}
onClick={event => (onCMMLStatus ? onCMMLStatus(event, row) : null)}
>
{formatTechObjCMMLStatusValue(row[columnDef.name])}
</Button>
)
};
}
};
//----------------
//Интерфейс модуля
//----------------
export { TechObjCard, eqConfigTechObjTableValueFormatter };
export { TechObjCard, eqConfigTechObjTableValueFormatter, eqConfigTechObjTableDataCellRender };