Каскадное удаление класса из выборки, отображение карточки технического объекта в виде диалога, косметика на закладке "Обучение"

This commit is contained in:
Mikhail Chechnev 2024-08-10 18:25:18 +03:00
parent b31fea736a
commit 6e3fd86e89
7 changed files with 149 additions and 243 deletions

View File

@ -351,6 +351,16 @@ create or replace package body UDO_PKG_EQUIPDS_BASE as
)
is
begin
/* Удалим файлы */
for C in (select T.RN from UDO_T_EQUIPDSCMFL T where T.PRN = NRN)
loop
CMFL_DEL(NRN => C.RN);
end loop;
/* Удалим модели */
for C in (select T.RN from UDO_T_EQUIPDSCMML T where T.PRN = NRN)
loop
CMML_DEL(NRN => C.RN);
end loop;
/* Удалим запись */
delete from UDO_T_EQUIPDSCM T where T.RN = NRN;
end CM_DEL;
@ -457,6 +467,11 @@ create or replace package body UDO_PKG_EQUIPDS_BASE as
)
is
begin
/* Удалим историю */
for C in (select T.RN from UDO_T_EQUIPDSCMMLH T where T.PRN = NRN)
loop
CMMLH_DEL(NRN => C.RN);
end loop;
/* Удалим запись */
delete from UDO_T_EQUIPDSCMML T where T.RN = NRN;
end CMML_DEL;

View File

@ -81,8 +81,7 @@ text="Проверка прав доступа при формировании
else
0
end NHASCHILDREN,
M.OBJ_KIND NOBJ_KIND,
0 NSHOW_CARD
M.OBJ_KIND NOBJ_KIND
from EQCONFIG M,
EQOBJLEVEL OL
where M.PR_OBJ_LEVEL = OL.RN(+)
@ -105,13 +104,6 @@ text="Проверка прав доступа при формировании
RATTRIBUTE01 => PKG_XMAKE.ATTRIBUTE(ICURSOR => NCUR,
SNAME => 'label',
SVALUE => 'Минуточку...')));
else
XCHILD := null;
end if;
/* Для технического объекта, не имеющего дочерних - соберём признак отображения карточки */
if ((C.NHASCHILDREN = 0) and (C.NOBJ_KIND is not null)) then
C.NSHOW_CARD := 1;
end if;
/* Соберём лист */
XLEAF := PKG_XMAKE.ELEMENT(ICURSOR => NCUR,
SNAME => 'children',
@ -121,13 +113,11 @@ text="Проверка прав доступа при формировании
SVALUE => C.NRN),
RATTRIBUTE01 => PKG_XMAKE.ATTRIBUTE(ICURSOR => NCUR,
SNAME => 'label',
SVALUE => C.SNAME),
RATTRIBUTE02 => PKG_XMAKE.ATTRIBUTE(ICURSOR => NCUR,
SNAME => 'showCard',
NVALUE => C.NSHOW_CARD)),
SVALUE => C.SNAME)),
RNODE00 => XCHILD);
/* Соберём листы в ветку */
XRES := PKG_XMAKE.CONCAT(ICURSOR => NCUR, RNODE00 => XRES, RNODE01 => XLEAF);
end if;
end loop;
/* Вернём собранное */
return XRES;

View File

@ -264,6 +264,7 @@ const AdminTab = ({ dataSelection = DS_RN_DEFAULT, dataSelectionClassMachine = n
<Grid item xs={12}>
<Stack direction="row" spacing={2} p={2} sx={STYLES.DATA_SELECTION_STACK}>
<EquipDataSelectionList list={equipDataSelectionList} value={equipDataSelection} onChange={handleDataSelectionChange} />
<Stack direction="row" spacing={0}>
<IconButton onClick={handleAddEquipDataSelection}>
<Icon>add</Icon>
</IconButton>
@ -273,6 +274,7 @@ const AdminTab = ({ dataSelection = DS_RN_DEFAULT, dataSelectionClassMachine = n
</IconButton>
) : null}
</Stack>
</Stack>
</Grid>
<Grid item xs={12}>
<Grid container>

View File

@ -102,34 +102,6 @@ const STYLES = {
//Вспомогательные функции и компоненты
//------------------------------------
//Кнопка с дополнительной подписью
const ExtraCaptionButton = ({ caption, title, subtitle, maxWidth, onClick, theme }) => {
//При нажатии на кнопку
const handleClick = () => (onClick ? onClick() : null);
//Генерация содержимого
return (
<Stack sx={STYLES.EXTRA_CAPTION_BUTTON_CONTAINER(theme, maxWidth)}>
<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>
</Stack>
);
};
//Контроль свойств - Кнопка с дополнительной подписью
ExtraCaptionButton.propTypes = {
caption: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
subtitle: PropTypes.string.isRequired,
maxWidth: PropTypes.string,
onClick: PropTypes.func,
theme: PropTypes.object.isRequired
};
//Формирование значения для колонки "Состояние" файла класса оборудования выборки данных
const formatFileStateValue = (theme, value, err) => {
const [text, icon, color] =
@ -166,7 +138,7 @@ const filesListDataCellRender = ({ row, columnDef, theme }) => {
};
//Форматирование колонок таблицы моделей класса оборудования выборки данных
const modelsListDataCellRender = ({ row, columnDef, theme, card, onDelete, onSendRq }) => {
const modelsListDataCellRender = ({ row, columnDef, theme, onDelete, onSendRq }) => {
switch (columnDef.name) {
case "NSTATUS":
return {
@ -180,16 +152,7 @@ const modelsListDataCellRender = ({ row, columnDef, theme, card, onDelete, onSen
<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}
{row.NSTATUS == 0 ? <Button onClick={() => (onSendRq ? onSendRq(row.NRN) : null)}>Обучить</Button> : null}
</Stack>
)
};
@ -541,30 +504,11 @@ const EquipDataSelectionClassMachineCard = ({
<Typography variant="h6" component="div">
Файлы данных
</Typography>
<ExtraCaptionButton
caption="Сформировать"
title="ПП:"
subtitle={card.SUSERPROCS_DATA}
maxWidth="150px"
onClick={handleClassMachineFilesMakeClick}
theme={theme}
/>
<ExtraCaptionButton
caption="Загрузить на сервер"
title="СО:"
subtitle={card.SEXSSERVICE_UPLOAD}
maxWidth="210px"
onClick={handleClassMachineFilesUploadClick}
theme={theme}
/>
<ExtraCaptionButton
caption="Передать внешней системе"
title="СО:"
subtitle={card.SEXSSERVICE_SEND_MD}
maxWidth="260px"
onClick={handleClassMachineFilesSendMdClick}
theme={theme}
/>
<Stack spacing={0} direction={"row"}>
<Button onClick={handleClassMachineFilesMakeClick}>Сформировать</Button>
<Button onClick={handleClassMachineFilesUploadClick}>Загрузить на сервер</Button>
<Button onClick={handleClassMachineFilesSendMdClick}>Передать фреймворку</Button>
</Stack>
</Stack>
<P8PDataGrid
{...{ ...P8P_DATA_GRID_CONFIG_PROPS }}
@ -604,7 +548,6 @@ const EquipDataSelectionClassMachineCard = ({
modelsListDataCellRender({
...prms,
theme,
card,
onDelete: handleClassMachineModelDeleteClick,
onSendRq: handleClassMachineModelSendRqClick
})

View File

@ -11,7 +11,6 @@ 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 { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с серверомs
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
@ -25,7 +24,6 @@ import {
useEqConfigTechObjCard,
useTechObjModelsList,
useTechObjForecastHistList,
findTreeItem,
needLoadLevel
} from "./forecast_tab_hooks"; //Вспомогательные хуки
@ -79,9 +77,6 @@ const ForecastTab = ({ onGoToAdmin }) => {
//Подключение к контексту приложения
const { pOnlineShowUnit } = useContext(ApplicationСtx);
//Подключение к API дерева
const apiRef = useTreeViewApiRef();
//Собственное состояние - текущая загружаемая ветка
const [loadingTreeItem, setLoadingTreeItem] = useState(0);
@ -122,14 +117,7 @@ const ForecastTab = ({ onGoToAdmin }) => {
isExpanded && needLoadLevel(eQconfigTree, itemId) ? setLoadingTreeItem(parseInt(itemId)) : null;
//Обработка фокусировки на элементе дерева
const handleTreeItemFocus = (event, itemId) => {
const item = findTreeItem(eQconfigTree, itemId);
if (item && item?.showCard) setTechObjCardId(parseInt(itemId));
else {
setTechObjCardId(null);
setTechObjSpec({ ...TECH_OBJ_SPEC_INIT, parent: parseInt(itemId) });
}
};
const handleTreeItemFocus = (event, itemId) => setTechObjSpec({ ...TECH_OBJ_SPEC_INIT, parent: parseInt(itemId) });
//При изменении состояния фильтров спецификации технических объектов
const handleTechObjSpecFilterChanged = ({ filters }) => setTechObjSpec(pv => ({ ...pv, filters: [...filters], pageNumber: 1 }));
@ -144,15 +132,14 @@ const ForecastTab = ({ onGoToAdmin }) => {
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));
}
} else setTechObjCardId(parseInt(row.NRN));
};
//При переходе к закладке администрирования из карточки технического объекта
const handleTechObjeCardGoToModel = model => (onGoToAdmin ? onGoToAdmin(model.NEQUIPDS, model.NEQUIPDSCM) : null);
const handleTechObjeCardGoToModel = model => {
handleTechObjeCardClose();
if (onGoToAdmin) onGoToAdmin(model.NEQUIPDS, model.NEQUIPDSCM);
};
//При запросе прогноза по модели из карточки технического объекта
const handleTechObjeCardGetPrediction = async model => {
@ -199,17 +186,11 @@ const ForecastTab = ({ onGoToAdmin }) => {
<Grid container>
<Grid item xs={3} sx={STYLES.LEFT_SIDE_GRID}>
<Box sx={STYLES.TREE_BOX}>
<RichTreeView
apiRef={apiRef}
items={eQconfigTree}
onItemExpansionToggle={handleTreeItemExpansionToggle}
onItemFocus={handleTreeItemFocus}
/>
<RichTreeView items={eQconfigTree} onItemExpansionToggle={handleTreeItemExpansionToggle} onItemFocus={handleTreeItemFocus} />
</Box>
</Grid>
<Grid item xs={9} sx={STYLES.RIGHT_SIDE_GRID}>
{techObjCardId ? (
!techObjCardIsLoading ? (
{techObjCardId && !techObjCardIsLoading && techObjCard?.SCODE ? (
<TechObjCard
cardData={techObjCard}
modelsList={techObjModelsList}
@ -219,8 +200,8 @@ const ForecastTab = ({ onGoToAdmin }) => {
onGetPrediction={handleTechObjeCardGetPrediction}
onMakeEqRpSheet={handleTechObjeCardMakeEqRpSheet}
/>
) : null
) : techObjsDataGrid.init ? (
) : null}
{techObjsDataGrid.init ? (
<P8PDataGrid
{...{ ...P8P_DATA_GRID_CONFIG_PROPS, noDataFoundText }}
containerComponentProps={{

View File

@ -164,11 +164,9 @@ const useEqConfigTechObjCard = id => {
setLoading(true);
const data = await executeStored({
stored: "UDO_PKG_EQUIPTCF.EQCONFIG_THOBJ_CARD",
args: {
NEQCONFIG: id
},
args: { NEQCONFIG: id },
respArg: "COUT",
loader: false
loader: true
});
setCard(data.techObj ? data.techObj : {});
} finally {

View File

@ -9,23 +9,7 @@
import React, { useState, useEffect, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Stack,
Icon,
Box,
Card,
CardContent,
Typography,
CardActions,
Button,
IconButton,
Divider,
Link,
Dialog,
DialogTitle,
DialogContent,
DialogActions
} from "@mui/material"; //Интерфейсные компоненты
import { Stack, Card, CardContent, Typography, Button, Link, Dialog, DialogTitle, DialogContent, DialogActions } from "@mui/material"; //Интерфейсные компоненты
import { useTheme } from "@mui/material/styles"; //Темы оформления
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { BUTTONS } from "../../../app.text"; //Текстовые ресурсы и константы
@ -41,18 +25,17 @@ import { P8PChart } from "../../components/p8p_chart"; //График
//Стили
const STYLES = {
TECH_OBJ_CARD_TITLE: { fontSize: 14 },
TECH_OBJ_CARD_SUB_TITLE: { mb: 1.5 },
EQ_ML_TABLE: {
maxHeight: "200px",
TECH_OBJ_CARD_DIALOG: { maxWidth: "800px" },
TECH_OBJ_CARD_ML_TABLE: {
maxHeight: "300px",
...SCROLL_STYLES
},
EQ_FORECAST_TABLE: {
maxHeight: "200px",
TECH_OBJ_CARD_FORECAST_TABLE: {
maxHeight: "300px",
...SCROLL_STYLES
},
EQ_FORECAST_DETAIL_DIALOG: { maxWidth: "600px" },
EQ_FORECAST_DETAIL_CHART: { width: "550px", display: "flex", justifyContent: "center" }
TECH_OBJ_FORECAST_DETAIL_DIALOG: { maxWidth: "600px" },
TECH_OBJ_FORECAST_DETAIL_CHART: { width: "550px", display: "flex", justifyContent: "center" }
};
//------------------------------------
@ -139,9 +122,9 @@ const ForecastDetail = ({ date, forecast, onClose }) => {
//Генерация содержимого
return (
<Dialog open={true} onClose={() => (onClose ? onClose() : null)} {...STYLES.EQ_FORECAST_DETAIL_DIALOG}>
<Dialog open={true} onClose={() => (onClose ? onClose() : null)} {...STYLES.TECH_OBJ_FORECAST_DETAIL_DIALOG}>
<DialogTitle>{`Детали прогноза от ${date}`}</DialogTitle>
<DialogContent>{chart.loaded ? <P8PChart {...chart} style={STYLES.EQ_FORECAST_DETAIL_CHART} /> : null}</DialogContent>
<DialogContent>{chart.loaded ? <P8PChart {...chart} style={STYLES.TECH_OBJ_FORECAST_DETAIL_CHART} /> : null}</DialogContent>
<DialogActions>
<Button onClick={() => (onClose ? onClose() : null)}>{BUTTONS.CLOSE}</Button>
</DialogActions>
@ -190,39 +173,27 @@ const TechObjCard = ({ cardData, modelsList, forecastHistList, onClose, onGoToMo
//Генерация содержимого
return (
<Box p={2}>
<>
{state.forecastDetail ? (
<ForecastDetail date={state.forecastDate} forecast={state.forecastDetail} onClose={handleCloseForecastDetailClick} />
) : null}
<Card>
<CardContent>
<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" variant="caption" gutterBottom>
{`Введён в эксплуатацию: ${formatDateRF(cardData.DOPER_DATE)}`}
<br />
{`Класс: ${cardData.SEQOBJKIND}`}
</Typography>
<Dialog open={true} onClose={handleClose} {...STYLES.TECH_OBJ_CARD_DIALOG}>
<DialogTitle>{cardData.SCODE}</DialogTitle>
<DialogContent>
<Typography variant="h6">{cardData.SNAME}</Typography>
<br />
<Stack spacing={2}>
<Typography color="text.secondary" variant="caption" gutterBottom>
{`${formatDateRF(cardData.DOPER_DATE)}, ${cardData.SEQOBJKIND}`}
</Typography>
{modelsList.init ? (
<>
<Divider />
<Card elevation={6}>
<CardContent>
<Typography variant="h6" component="div">
Модели
</Typography>
<P8PDataGrid
{...{ ...P8P_DATA_GRID_CONFIG_PROPS }}
containerComponentProps={{ sx: STYLES.EQ_ML_TABLE, elevation: 0 }}
containerComponentProps={{ sx: STYLES.TECH_OBJ_CARD_ML_TABLE, elevation: 0 }}
columnsDef={modelsList.columnsDef}
rows={modelsList.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
@ -239,17 +210,18 @@ const TechObjCard = ({ cardData, modelsList, forecastHistList, onClose, onGoToMo
})
}
/>
</>
</CardContent>
</Card>
) : null}
{forecastHistList.init ? (
<>
<Divider />
<Card elevation={6}>
<CardContent>
<Typography variant="h6" component="div">
Прогнозы
</Typography>
<P8PDataGrid
{...{ ...P8P_DATA_GRID_CONFIG_PROPS }}
containerComponentProps={{ sx: STYLES.EQ_FORECAST_TABLE, elevation: 0 }}
containerComponentProps={{ sx: STYLES.TECH_OBJ_CARD_FORECAST_TABLE, elevation: 0 }}
columnsDef={forecastHistList.columnsDef}
rows={forecastHistList.rows}
size={P8P_DATA_GRID_SIZE.SMALL}
@ -264,14 +236,19 @@ const TechObjCard = ({ cardData, modelsList, forecastHistList, onClose, onGoToMo
})
}
/>
</>
) : null}
</CardContent>
<CardActions>
<Button onClick={handleMakeEqRpSheetClick}>Ремонтировать</Button>
</CardActions>
</Card>
</Box>
) : null}
</Stack>
</DialogContent>
<DialogActions>
<Button onClick={handleMakeEqRpSheetClick} color={"error"}>
Ремонтировать
</Button>
<Button onClick={handleClose}>{BUTTONS.CLOSE}</Button>
</DialogActions>
</Dialog>
</>
);
};