forked from CITKParus/P8-Panels
ЦИТК-823 - Изменение панели "Редактор настройки регламентированного отчёта"
This commit is contained in:
parent
331fc33839
commit
1a01536b35
@ -10,8 +10,8 @@
|
||||
import React, { useState, useContext, useCallback, useEffect } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Dialog, DialogTitle, IconButton, Icon, DialogContent, Typography, DialogActions, Button } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { IUDFormTextField } from "./iud_form_text_field"; //Компонент поля ввода
|
||||
|
||||
//---------
|
||||
@ -65,7 +65,9 @@ const IUDFormDialog = ({ initial, onClose, onReload }) => {
|
||||
};
|
||||
|
||||
//При изменении значения элемента
|
||||
const handleDialogItemChange = (item, value) => setFormData(pv => ({ ...pv, [item]: value }));
|
||||
const handleDialogItemChange = (item, value) => {
|
||||
setFormData(pv => ({ ...pv, [item]: value }));
|
||||
};
|
||||
|
||||
//Отработка изменений в разделе или показателе раздела
|
||||
const changeSections = useCallback(async () => {
|
||||
@ -139,13 +141,11 @@ const IUDFormDialog = ({ initial, onClose, onReload }) => {
|
||||
NPRN: formData.prn,
|
||||
SCODE: formData.code,
|
||||
SNAME: formData.name,
|
||||
SCOLCODE: formData.colCode,
|
||||
SCOLVER: formData.colVCode,
|
||||
SROWCODE: formData.rowCode,
|
||||
SROWVER: formData.rowVCode
|
||||
NRRPROW: formData.rowRn,
|
||||
NRRPCOLUMN: formData.colRn
|
||||
}
|
||||
});
|
||||
}, [executeStored, formData.code, formData.colVCode, formData.colCode, formData.name, formData.prn, formData.rowCode, formData.rowVCode]);
|
||||
}, [executeStored, formData.code, formData.colRn, formData.name, formData.prn, formData.rowRn]);
|
||||
|
||||
//Исправление показателя раздела
|
||||
const editRRPCONFSCTNMRK = useCallback(async () => {
|
||||
@ -210,14 +210,15 @@ const IUDFormDialog = ({ initial, onClose, onReload }) => {
|
||||
const selectRow = (showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "RRPRow",
|
||||
inputParameters: [{ name: "in_RN", value: formData.rowRn }],
|
||||
callBack: res => {
|
||||
if (res.success === true) {
|
||||
callBack(res.outParameters.out_CODE, res.outParameters.out_RRPVERSION_CODE, res.outParameters.out_RRPVERSION);
|
||||
callBack(res.outParameters.out_CODE, res.outParameters.out_RN);
|
||||
setFormData(pv => ({
|
||||
...pv,
|
||||
reload: true,
|
||||
rowCode: res.outParameters.out_CODE,
|
||||
rowVCode: res.outParameters.out_RRPVERSION_CODE,
|
||||
rowVRn: res.outParameters.out_RRPVERSION
|
||||
rowRn: res.outParameters.out_RN
|
||||
}));
|
||||
} else callBack(null);
|
||||
}
|
||||
@ -228,14 +229,15 @@ const IUDFormDialog = ({ initial, onClose, onReload }) => {
|
||||
const selectColumn = (showDictionary, callBack) => {
|
||||
showDictionary({
|
||||
unitCode: "RRPColumn",
|
||||
inputParameters: [{ name: "in_RN", value: formData.colRn }],
|
||||
callBack: res => {
|
||||
if (res.success === true) {
|
||||
callBack(res.outParameters.out_CODE, res.outParameters.out_RRPVERSION_CODE, res.outParameters.out_RRPVERSION);
|
||||
callBack(res.outParameters.out_CODE, res.outParameters.out_RN);
|
||||
setFormData(pv => ({
|
||||
...pv,
|
||||
reload: true,
|
||||
colCode: res.outParameters.out_CODE,
|
||||
colVCode: res.outParameters.out_RRPVERSION_CODE,
|
||||
colVRn: res.outParameters.out_RRPVERSION
|
||||
colRn: res.outParameters.out_RN
|
||||
}));
|
||||
} else callBack(null);
|
||||
}
|
||||
@ -247,26 +249,41 @@ const IUDFormDialog = ({ initial, onClose, onReload }) => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_GET_CODE_NAME",
|
||||
args: {
|
||||
SSCTNCODE: formData.sctnCode,
|
||||
SROWCODE: formData.rowCode,
|
||||
NROWVER: formData.rowVRn,
|
||||
SCOLUMNCODE: formData.colCode,
|
||||
NCOLUMNVER: formData.colVRn
|
||||
NRRPCONFSCTN: formData.prn,
|
||||
NRRPROW: formData.rowRn,
|
||||
NRRPCOLUMN: formData.colRn
|
||||
}
|
||||
});
|
||||
setFormData(pv => ({
|
||||
...pv,
|
||||
reload: false,
|
||||
code: data.SCODE,
|
||||
name: data.SNAME
|
||||
}));
|
||||
}, [executeStored, formData.colCode, formData.colVRn, formData.rowCode, formData.rowVRn, formData.sctnCode]);
|
||||
}, [executeStored, formData.colRn, formData.prn, formData.rowRn]);
|
||||
|
||||
//Считывание наименования показателя
|
||||
const getMarkName = useCallback(async () => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_GET_NAME",
|
||||
args: {
|
||||
NRRPCONFSCTNMRK: formData.rn
|
||||
}
|
||||
});
|
||||
setFormData(pv => ({
|
||||
...pv,
|
||||
reload: false,
|
||||
name: data.SNAME
|
||||
}));
|
||||
}, [executeStored, formData.rn]);
|
||||
|
||||
//Получение наименования и мнемокода показателя раздела при заполнении необходимых полей
|
||||
useEffect(() => {
|
||||
formData.status == STATUSES.RRPCONFSCTNMRK_CREATE && formData.sctnName && formData.sctnCode && formData.colCode && formData.rowCode
|
||||
? getSctnMrkCodeName()
|
||||
: null;
|
||||
}, [formData.colCode, formData.rowCode, formData.sctnCode, formData.sctnName, formData.status, getSctnMrkCodeName]);
|
||||
//Если это добавление показателя и требуется сформировать мнемокод и наименование
|
||||
formData.status == STATUSES.RRPCONFSCTNMRK_CREATE && formData.reload && formData.rowRn && formData.colRn ? getSctnMrkCodeName() : null;
|
||||
//Если это исправление и требуется инициализировать наименование показателя
|
||||
formData.status == STATUSES.RRPCONFSCTNMRK_EDIT && formData.reload ? getMarkName() : null;
|
||||
}, [formData.status, formData.reload, formData.rowRn, formData.colRn, getSctnMrkCodeName, getMarkName]);
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
344
app/panels/rrp_conf_editor/components/layouts.js
Normal file
344
app/panels/rrp_conf_editor/components/layouts.js
Normal file
@ -0,0 +1,344 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
|
||||
Дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import {
|
||||
IconButton,
|
||||
Icon,
|
||||
Link,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Table,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
Box,
|
||||
Typography
|
||||
} from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
export const STYLES = {
|
||||
BOX_ROW: { display: "flex", justifyContent: "center", alignItems: "center" },
|
||||
LINK_STYLE: { component: "button", cursor: "pointer", width: "-webkit-fill-available" },
|
||||
DATA_CELL: columnDef => ({
|
||||
padding: "5px 5px",
|
||||
fontSize: "0.775rem",
|
||||
letterSpacing: "0.005em",
|
||||
textAlign: "center",
|
||||
wordBreak: "break-all",
|
||||
backgroundColor: columnDef.name === "SROW_NAME" ? "#b4b4b4" : "trasparent"
|
||||
}),
|
||||
DATA_CELL_CARD: {
|
||||
padding: "0px 3px 3px 0px",
|
||||
border: "1px solid lightgrey",
|
||||
borderRadius: "5%"
|
||||
},
|
||||
DATA_CELL_CARD_HEADER: {
|
||||
padding: "0px"
|
||||
},
|
||||
DATA_CELL_CARD_SUBHEADER: {
|
||||
textAlign: "left",
|
||||
paddingLeft: "10px",
|
||||
fontSize: "1rem",
|
||||
fontWeight: "450"
|
||||
},
|
||||
DATA_CELL_CARD_CONTENT: listLength => {
|
||||
return {
|
||||
fontSize: "0.75rem",
|
||||
padding: "5px 0px",
|
||||
minHeight: "105px",
|
||||
maxHeight: "105px",
|
||||
overflowY: "auto",
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "8px"
|
||||
},
|
||||
"&::-webkit-scrollbar-track": {
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#EBEBEB"
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#b4b4b4"
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb:hover": {
|
||||
backgroundColor: "#808080"
|
||||
},
|
||||
"&:last-child": {
|
||||
paddingBottom: "0px"
|
||||
},
|
||||
...(listLength === 0 ? { display: "flex", justifyContent: "center", alignItems: "center" } : null)
|
||||
};
|
||||
},
|
||||
DATA_CELL_CARD_CONTEXT_FONT: {
|
||||
fontSize: "0.75rem"
|
||||
},
|
||||
DATA_CELL_CARD_CONTEXT_MARK: {
|
||||
padding: "0px 0px 0px 10px",
|
||||
borderBottom: "1px solid #EBEBEB"
|
||||
},
|
||||
DATA_CELL_CN: {
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "pre",
|
||||
padding: "0px 5px",
|
||||
maxWidth: "100px",
|
||||
border: "none"
|
||||
},
|
||||
GRID_PANEL_CARD: { maxWidth: 400, flexDirection: "column", display: "flex" },
|
||||
MARK_INFO: {
|
||||
fontSize: "0.8rem",
|
||||
textAlign: "left",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "pre",
|
||||
maxWidth: "max-content",
|
||||
width: "-webkit-fill-available"
|
||||
},
|
||||
BUTTON_CN_INSERT: {
|
||||
padding: "0px 8px",
|
||||
marginBottom: "2px",
|
||||
"& .MuiIcon-root": {
|
||||
fontWeight: "bold",
|
||||
fontSize: "1rem"
|
||||
}
|
||||
},
|
||||
HEAD_CELL: {
|
||||
backgroundColor: "#b4b4b4",
|
||||
textAlign: "center"
|
||||
},
|
||||
HEAD_CELL_STACK: {
|
||||
justifyContent: "space-around"
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//------------------------------------
|
||||
|
||||
//Действия карты показателя
|
||||
const DataCellCardActions = ({ columnDef, menuItems, cellData, markRn }) => {
|
||||
//Собственное состояние
|
||||
const [cardActions, setCardActions] = useState({ anchorMenuMethods: null, openMethods: false });
|
||||
|
||||
//По нажатию на открытие меню действий
|
||||
const handleMethodsMenuButtonClick = event => {
|
||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: event.currentTarget, openMethods: true }));
|
||||
};
|
||||
|
||||
//При закрытии меню
|
||||
const handleMethodsMenuClose = () => {
|
||||
setCardActions(pv => ({ ...pv, anchorMenuMethods: null, openMethods: false }));
|
||||
};
|
||||
return (
|
||||
<Box sx={STYLES.BOX_ROW}>
|
||||
<IconButton id={`${columnDef.name}_menu_button`} aria-haspopup="true" onClick={handleMethodsMenuButtonClick}>
|
||||
<Icon>more_vert</Icon>
|
||||
</IconButton>
|
||||
<Menu
|
||||
id={`${columnDef.name}_menu`}
|
||||
anchorEl={cardActions.anchorMenuMethods}
|
||||
open={cardActions.openMethods}
|
||||
onClose={handleMethodsMenuClose}
|
||||
>
|
||||
{menuItems.map(el => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={`${cellData}_${el.method}`}
|
||||
onClick={() => {
|
||||
el.func(markRn);
|
||||
handleMethodsMenuClose();
|
||||
}}
|
||||
>
|
||||
<Icon>{el.icon}</Icon>
|
||||
{el.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Действия карты показателя
|
||||
DataCellCardActions.propTypes = {
|
||||
columnDef: PropTypes.object.isRequired,
|
||||
menuItems: PropTypes.array,
|
||||
cellData: PropTypes.any,
|
||||
markRn: PropTypes.number
|
||||
};
|
||||
|
||||
//Таблица составов показателя
|
||||
const MarkCnList = ({ markRn, list, handleMarkCnOpen }) => {
|
||||
return (
|
||||
<Table>
|
||||
<TableBody>
|
||||
{list.map((el, index) => {
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
<TableCell
|
||||
sx={{ ...STYLES.DATA_CELL_CN, ...STYLES.DATA_CELL_CARD_CONTEXT_FONT }}
|
||||
title={el.SDESC}
|
||||
align="left"
|
||||
size="small"
|
||||
>
|
||||
<Link sx={STYLES.LINK_STYLE} onClick={() => handleMarkCnOpen(markRn, el.NRN)}>
|
||||
{el.SDESC}
|
||||
</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Таблица составов показателя
|
||||
MarkCnList.propTypes = {
|
||||
markRn: PropTypes.number.isRequired,
|
||||
list: PropTypes.array.isRequired,
|
||||
handleMarkCnOpen: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//Ячейка таблицы строки
|
||||
const DataCellContent = ({ row, columnDef, menuItems, sectionRn, handleMarkAdd, handleMarkOpen, handleMarkCnOpen, handleMarkCnInsert }) => {
|
||||
//Считываем информацию о показателе
|
||||
let mark = {
|
||||
sectionRn: sectionRn,
|
||||
data: row[columnDef.name],
|
||||
nRn: row["NMARK_RN_" + columnDef.name.substring(5)],
|
||||
sCode: row["SMARK_CODE_" + columnDef.name.substring(5)],
|
||||
sRowCode: row["SROW_CODE"],
|
||||
nRowRn: row["NROW_RN"],
|
||||
sColCode: columnDef.name.substring(5),
|
||||
nColRn: row["NCOL_RN_" + columnDef.name.substring(5)],
|
||||
rCnList: row["MARK_CNS_" + columnDef.name.substring(5)] ? [...row["MARK_CNS_" + columnDef.name.substring(5)]] : []
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{mark.nRn ? (
|
||||
<Card variant={"plain"} sx={STYLES.DATA_CELL_CARD}>
|
||||
<CardHeader
|
||||
sx={STYLES.DATA_CELL_CARD_HEADER}
|
||||
subheader={
|
||||
<Box>
|
||||
<Link sx={STYLES.LINK_STYLE} onClick={() => (handleMarkOpen ? handleMarkOpen(mark.nRn) : null)}>
|
||||
Состав
|
||||
</Link>
|
||||
{mark.rCnList.length !== 0 ? (
|
||||
<IconButton sx={STYLES.BUTTON_CN_INSERT} aria-haspopup="true" onClick={() => handleMarkCnInsert(mark.nRn)}>
|
||||
<Icon>add</Icon>
|
||||
</IconButton>
|
||||
) : null}
|
||||
</Box>
|
||||
}
|
||||
subheaderTypographyProps={STYLES.DATA_CELL_CARD_SUBHEADER}
|
||||
action={<DataCellCardActions columnDef={columnDef} menuItems={menuItems} cellData={mark.data} markRn={mark.nRn} />}
|
||||
></CardHeader>
|
||||
<CardContent sx={STYLES.DATA_CELL_CARD_CONTEXT_MARK}>
|
||||
<Typography sx={STYLES.MARK_INFO} title={mark.sCode}>
|
||||
{mark.sCode}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardContent sx={STYLES.DATA_CELL_CARD_CONTENT(mark.rCnList.length)}>
|
||||
{mark.rCnList.length !== 0 ? (
|
||||
<MarkCnList markRn={mark.nRn} list={mark.rCnList} handleMarkCnOpen={handleMarkCnOpen} />
|
||||
) : (
|
||||
<Box>
|
||||
<Typography sx={STYLES.DATA_CELL_CARD_CONTEXT_FONT}>Показатель не имеет состава</Typography>
|
||||
<Link sx={STYLES.LINK_STYLE} onClick={() => (handleMarkCnInsert ? handleMarkCnInsert(mark.nRn) : null)}>
|
||||
Добавить
|
||||
</Link>
|
||||
</Box>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Box>
|
||||
<Typography sx={STYLES.DATA_CELL_CARD_CONTEXT_FONT}>Показатель отсутствует</Typography>
|
||||
<Link
|
||||
sx={STYLES.LINK_STYLE}
|
||||
onClick={() =>
|
||||
handleMarkOpen ? handleMarkAdd(mark.sectionRn, mark.nRowRn, mark.sRowCode, mark.nColRn, mark.sColCode) : null
|
||||
}
|
||||
>
|
||||
Добавить
|
||||
</Link>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Ячейка таблицы строки
|
||||
DataCellContent.propTypes = {
|
||||
row: PropTypes.object.isRequired,
|
||||
columnDef: PropTypes.object.isRequired,
|
||||
menuItems: PropTypes.array,
|
||||
sectionRn: PropTypes.number.isRequired,
|
||||
handleMarkAdd: PropTypes.func,
|
||||
handleMarkOpen: PropTypes.func,
|
||||
handleMarkCnOpen: PropTypes.func,
|
||||
handleMarkCnInsert: PropTypes.func
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Генерация представления ячейки c данными показателя раздела регламентированного отчета
|
||||
export const confSctnMrkCellRender = ({
|
||||
row,
|
||||
columnDef,
|
||||
sectionRn,
|
||||
handleMarkAdd,
|
||||
handleMarkOpen,
|
||||
handleMarkCnOpen,
|
||||
handleMarkCnInsert,
|
||||
menuItems
|
||||
}) => {
|
||||
//Иницализируем стили
|
||||
let cellStyle = STYLES.DATA_CELL(columnDef);
|
||||
//Считываем значение
|
||||
let data = row[columnDef.name];
|
||||
//Если это не наименование строки и есть значение
|
||||
columnDef.name != "SROW_NAME" && data != undefined && columnDef.visible == true
|
||||
? (data = (
|
||||
<DataCellContent
|
||||
row={row}
|
||||
columnDef={columnDef}
|
||||
menuItems={menuItems}
|
||||
sectionRn={sectionRn}
|
||||
handleMarkAdd={handleMarkAdd}
|
||||
handleMarkOpen={handleMarkOpen}
|
||||
handleMarkCnOpen={handleMarkCnOpen}
|
||||
handleMarkCnInsert={handleMarkCnInsert}
|
||||
/>
|
||||
))
|
||||
: null;
|
||||
return { cellStyle: { ...cellStyle }, data: data };
|
||||
};
|
||||
|
||||
//Генерация представления ячейки заголовка группы c данными показателя раздела регламентированного отчета
|
||||
export const confSctnMrkHeadCellRender = ({ columnDef }) => {
|
||||
return {
|
||||
cellStyle: STYLES.HEAD_CELL,
|
||||
stackStyle: STYLES.HEAD_CELL_STACK,
|
||||
data: columnDef.caption
|
||||
};
|
||||
};
|
243
app/panels/rrp_conf_editor/components/rrp_section.js
Normal file
243
app/panels/rrp_conf_editor/components/rrp_section.js
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
|
||||
Компонент панели: Раздел настройки
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useState } from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, IconButton, Icon, Dialog, DialogTitle, DialogContent, Typography, List, ListItem } from "@mui/material"; //Интерфейсные элементы
|
||||
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../../components/p8p_data_grid"; //Таблица данных
|
||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
import { SectionTabPanel } from "./section_tab_panel"; //Компонент вкладки раздела
|
||||
import { confSctnMrkCellRender, confSctnMrkHeadCellRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
GRID_SIZES: (height, pxOuterMenuH, pxPanelHeaderH, pxTabsH) => ({
|
||||
padding: 0,
|
||||
minWidth: "98vw",
|
||||
minHeight: (height - pxOuterMenuH - pxPanelHeaderH - pxTabsH) * 0.93,
|
||||
maxWidth: "98vw",
|
||||
maxHeight: (height - pxOuterMenuH - pxPanelHeaderH - pxTabsH) * 0.93
|
||||
}),
|
||||
TABLE_CONTAINER: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingTop: 1,
|
||||
paddingBottom: 1
|
||||
},
|
||||
SECTION_ACTIONS: { display: "flex", justifyContent: "space-between", padding: "0px 5px" },
|
||||
TABLE_SCROLL: {
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "12px",
|
||||
height: "12px"
|
||||
},
|
||||
"&::-webkit-scrollbar-track": {
|
||||
borderRadius: "88px",
|
||||
backgroundColor: "#EBEBEB"
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
borderRadius: "88px",
|
||||
backgroundColor: "#b4b4b4",
|
||||
backgroundClip: "padding-box",
|
||||
border: "3px solid #EBEBEB"
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb:hover": {
|
||||
backgroundColor: "#808080"
|
||||
}
|
||||
},
|
||||
HELP_LIST_ITEM: {
|
||||
padding: "0px 0px 0px 5px",
|
||||
whiteSpace: "pre",
|
||||
fontSize: "0.95rem"
|
||||
},
|
||||
HELP_LIST_ITEM_NAME: {
|
||||
fontWeight: "bold",
|
||||
fontSize: "inherit",
|
||||
minWidth: "45px"
|
||||
},
|
||||
HELP_LIST_ITEM_DESC: {
|
||||
fontSize: "inherit"
|
||||
}
|
||||
};
|
||||
|
||||
//---------------------------------------------
|
||||
//Вспомогательные функции и компоненты
|
||||
//---------------------------------------------
|
||||
|
||||
//Элемент списка расшифровки состава
|
||||
const HelpListItem = ({ name, desc }) => {
|
||||
return (
|
||||
<ListItem sx={STYLES.HELP_LIST_ITEM}>
|
||||
<Typography sx={STYLES.HELP_LIST_ITEM_NAME}>{name}</Typography>
|
||||
<Typography sx={STYLES.HELP_LIST_ITEM_DESC}>{` - ${desc}`}</Typography>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Элемент списка расшифровки состава
|
||||
HelpListItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
desc: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
//Диалог дополнительной информации
|
||||
const HelpDialog = ({ handleOpenHelpChange }) => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Dialog open onClose={handleOpenHelpChange}>
|
||||
<DialogTitle>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box flexGrow={1} textAlign="center">
|
||||
Информация
|
||||
</Box>
|
||||
<Box>
|
||||
<IconButton aria-label="close" onClick={handleOpenHelpChange}>
|
||||
<Icon>close</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>Карточки показателей содержат сокращенную информацию о типе состава показателя.</Typography>
|
||||
<Typography>Список сокращений:</Typography>
|
||||
<List disablePadding={true}>
|
||||
<HelpListItem name="fx" desc="формула" />
|
||||
<HelpListItem name="СЗ" desc="статическое значение" />
|
||||
<HelpListItem name="ХП" desc="хранимая процедура" />
|
||||
<HelpListItem name="РП" desc="расчетный показатель" />
|
||||
<HelpListItem name="ХО" desc="хозяйственные операции" />
|
||||
<HelpListItem name="РСДК" desc="расчёты с дебиторами/кредиторами" />
|
||||
<HelpListItem name="ОС" desc="остатки средств по счетам" />
|
||||
<HelpListItem name="ТМЦ" desc="остатки товарно-материальных ценностей" />
|
||||
<HelpListItem name="ДКЗ" desc="дебиторская/кредиторская задолженность" />
|
||||
<HelpListItem name="ИК" desc="инвентарная картотека" />
|
||||
<HelpListItem name="МБП" desc="картотека МБП" />
|
||||
<HelpListItem name="КОБП" desc="картотека операций будущих периодов" />
|
||||
<HelpListItem name="ДПНП" desc="декларация по налогу на прибыль" />
|
||||
<HelpListItem name="РО" desc="регламентированный отчет" />
|
||||
</List>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Диалог дополнительной информации
|
||||
HelpDialog.propTypes = {
|
||||
handleOpenHelpChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Раздел настройки
|
||||
const SectionTab = ({
|
||||
section,
|
||||
tabValue,
|
||||
index,
|
||||
containerProps,
|
||||
handleMarkAdd,
|
||||
handleReload,
|
||||
handleMarkOpen,
|
||||
handleMarkCnOpen,
|
||||
handleMarkCnInsert,
|
||||
menuItems
|
||||
}) => {
|
||||
//Состояние - диалог информации
|
||||
const [openHelp, setOpenHelp] = useState(false);
|
||||
|
||||
//Изменение состояния диалога информации
|
||||
const handleOpenHelpChange = () => {
|
||||
setOpenHelp(!openHelp);
|
||||
};
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<>
|
||||
<SectionTabPanel key={section.rn} value={tabValue} index={index}>
|
||||
<Box sx={STYLES.SECTION_ACTIONS}>
|
||||
<Box>
|
||||
<IconButton onClick={() => handleMarkAdd(section.rn)}>
|
||||
<Icon>add</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={() => handleReload()}>
|
||||
<Icon>refresh</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box>
|
||||
<IconButton onClick={() => handleOpenHelpChange()}>
|
||||
<Icon>help</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{section.dataLoaded && section.columnsDef.length > 3 ? (
|
||||
<Box sx={STYLES.TABLE_CONTAINER}>
|
||||
<P8PDataGrid
|
||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||
containerComponentProps={{
|
||||
elevation: 6,
|
||||
sx: { ...STYLES.TABLE_SCROLL },
|
||||
style: STYLES.GRID_SIZES(
|
||||
containerProps.height,
|
||||
containerProps.pxOuterMenuH,
|
||||
containerProps.pxPanelHeaderH,
|
||||
containerProps.pxTabsH
|
||||
)
|
||||
}}
|
||||
columnsDef={section.columnsDef}
|
||||
groups={section.groups}
|
||||
rows={section.rows}
|
||||
fixedHeader={section.fixedHeader}
|
||||
fixedColumns={section.fixedColumns}
|
||||
size={P8P_DATA_GRID_SIZE.LARGE}
|
||||
reloading={section.reload}
|
||||
dataCellRender={prms =>
|
||||
confSctnMrkCellRender({
|
||||
...prms,
|
||||
sectionRn: section.rn,
|
||||
handleMarkAdd: handleMarkAdd,
|
||||
handleMarkOpen: handleMarkOpen,
|
||||
handleMarkCnOpen: handleMarkCnOpen,
|
||||
handleMarkCnInsert: handleMarkCnInsert,
|
||||
menuItems: menuItems
|
||||
})
|
||||
}
|
||||
headCellRender={confSctnMrkHeadCellRender}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
</SectionTabPanel>
|
||||
{openHelp ? <HelpDialog handleOpenHelpChange={handleOpenHelpChange} /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
//Контроль свойств - Раздел настройки
|
||||
SectionTab.propTypes = {
|
||||
section: PropTypes.object.isRequired,
|
||||
tabValue: PropTypes.number,
|
||||
index: PropTypes.number,
|
||||
containerProps: PropTypes.object,
|
||||
handleMarkAdd: PropTypes.func,
|
||||
handleReload: PropTypes.func,
|
||||
handleMarkOpen: PropTypes.func,
|
||||
handleMarkCnOpen: PropTypes.func,
|
||||
handleMarkCnInsert: PropTypes.func,
|
||||
menuItems: PropTypes.array
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { SectionTab };
|
@ -9,7 +9,17 @@
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||
import { Box, Typography } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { Box } from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
const STYLES = {
|
||||
SECTION_INFO: {
|
||||
padding: "24px 5px 0px 5px"
|
||||
}
|
||||
};
|
||||
|
||||
//---------------
|
||||
//Тело компонента
|
||||
@ -21,11 +31,7 @@ const SectionTabPanel = props => {
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<div role="tabpanel" hidden={value !== index} id={`tabpanel-${index}`} aria-labelledby={`tab-${index}`} {...other}>
|
||||
{value === index && (
|
||||
<Box p={3}>
|
||||
<Typography component="span">{children}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{value === index && <Box sx={STYLES.SECTION_INFO}>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -7,7 +7,17 @@
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import { useState, useLayoutEffect } from "react"; //Классы React
|
||||
import { useState, useContext, useEffect, useCallback, useLayoutEffect } from "react"; //Классы React
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
import { STATUSES } from "./IUD/iud_form_dialog"; //Статусы диалогов
|
||||
import { TEXTS } from "../../../app.text"; //Тексты для ошибок
|
||||
|
||||
//---------------------------------------------
|
||||
//Вспомогательные функции форматирования данных
|
||||
//---------------------------------------------
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
@ -32,8 +42,359 @@ const useWindowResize = () => {
|
||||
return size;
|
||||
};
|
||||
|
||||
//Хук для настройки регламентированного отчета
|
||||
const useConf = (currentTab, handleSectionChange) => {
|
||||
//Собственное состояние - таблица данных
|
||||
const dataGrid = {
|
||||
rn: 0,
|
||||
code: "",
|
||||
name: "",
|
||||
dataLoaded: false,
|
||||
columnsDef: [],
|
||||
groups: [],
|
||||
rows: [],
|
||||
fixedHeader: false,
|
||||
fixedColumns: 0,
|
||||
reload: false
|
||||
};
|
||||
|
||||
//Собственное состояние
|
||||
const [rrpConf, setRrpConf] = useState({
|
||||
docLoaded: false,
|
||||
sections: [],
|
||||
reload: true
|
||||
});
|
||||
|
||||
//Состояние массива данных разделов
|
||||
const [dataGrids] = useState([]);
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту навигации
|
||||
const { getNavigationSearch } = useContext(NavigationCtx);
|
||||
|
||||
//При необходимости обновить
|
||||
const handleReload = useCallback(async () => {
|
||||
setRrpConf(pv => ({ ...pv, reload: true }));
|
||||
}, []);
|
||||
|
||||
//Загрузка данных разделов регламентированного отчёта
|
||||
const loadData = useCallback(async () => {
|
||||
if (rrpConf.reload) {
|
||||
//Переменная номера раздела с фокусом
|
||||
let tabFocus = currentTab ? currentTab : 0;
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_RRPCONFED.RRPCONF_GET_SECTIONS",
|
||||
args: {
|
||||
NRN_RRPCONF: Number(getNavigationSearch().NRN)
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Флаг первой загрузки данных
|
||||
let firstLoad = dataGrids.length == 0 ? true : false;
|
||||
//Копирование массива уже загруженных разделов
|
||||
let cloneDGs = dataGrids.slice();
|
||||
//Массив из нескольких разделов и из одного
|
||||
const sections = data.SECTIONS ? (data.SECTIONS.length ? data.SECTIONS : [data.SECTIONS]) : [];
|
||||
//Заполнение очередного раздела по шаблону
|
||||
sections.map(s => {
|
||||
let dg = {};
|
||||
Object.assign(dg, dataGrid, {
|
||||
rn: s.NRN,
|
||||
code: s.SCODE,
|
||||
name: s.SNAME,
|
||||
delete_allow: s.NDELETE_ALLOW,
|
||||
dataLoaded: true,
|
||||
columnsDef: [...(s.XDATA.XCOLUMNS_DEF || [])],
|
||||
groups: [...(s.XDATA.XGROUPS || [])],
|
||||
rows: [...(s.XDATA.XROWS || [])],
|
||||
fixedHeader: s.XDATA.XDATA_GRID.fixedHeader,
|
||||
fixedColumns: s.XDATA.XDATA_GRID.fixedColumns,
|
||||
reload: false
|
||||
});
|
||||
//Если раздел имеет составы показателей
|
||||
if (s.MARK_CNS.MARK_CN) {
|
||||
//Обходим строки раздела
|
||||
dg.rows.map(row => {
|
||||
//Цикл по ключам строки
|
||||
for (let key in row) {
|
||||
//Если это ключ для группы составов показателей
|
||||
if (key.match(/MARK_CNS_.*/)) {
|
||||
//Считываем рег. номер показателя
|
||||
let markRn = key.substring(9);
|
||||
//Переносим из раздела
|
||||
row[key] = Array.isArray(s.MARK_CNS.MARK_CN)
|
||||
? [...s.MARK_CNS.MARK_CN].filter(el => el.NPRN === row[`NMARK_RN_${markRn}`])
|
||||
: s.MARK_CNS.MARK_CN.NPRN === row[`NMARK_RN_${markRn}`]
|
||||
? [s.MARK_CNS.MARK_CN]
|
||||
: null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
//Ищем загружен ли уже раздел с таким же ид.
|
||||
const dgItem = dataGrids.find(x => x.rn === dg.rn);
|
||||
//Его индекс, если нет соответствия, то -1
|
||||
let index = dataGrids.indexOf(dgItem);
|
||||
//Если было соответствие
|
||||
if (dgItem) {
|
||||
//Если в нём не найдено изменений
|
||||
if (JSON.stringify(dgItem, null, 4) === JSON.stringify(dg, null, 4)) {
|
||||
//То из копированного массива его удаляем
|
||||
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dgItem.rn)), 1);
|
||||
} else {
|
||||
//Иначе обновляем раздел в массиве
|
||||
dataGrids[index] = dg;
|
||||
//Удаляем из копированного массива
|
||||
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dg.rn)), 1);
|
||||
//Устанавливаем фокус на обновлённый раздел
|
||||
tabFocus = index;
|
||||
}
|
||||
} else {
|
||||
//Если раздел новый, то добавляем его в массив данных
|
||||
dataGrids.push(dg);
|
||||
//И устанавливаем на него фокус, если флаг первой загрузки = false
|
||||
tabFocus = !firstLoad ? dataGrids.length - 1 : 0;
|
||||
}
|
||||
});
|
||||
//Обходим разделы, что остались в копированном массиве (на удаление)
|
||||
cloneDGs.map(s => {
|
||||
let curIndex = dataGrids.indexOf(dataGrids.find(x => x.rn === s.rn));
|
||||
//Устаревший раздел удаляем из массива данных
|
||||
dataGrids.splice(curIndex, 1);
|
||||
//Фокус на предшествующий раздел
|
||||
if (curIndex > 0) tabFocus = curIndex - 1;
|
||||
//Иначе фокус на следующий, если был удалён первый раздел
|
||||
else tabFocus = curIndex;
|
||||
});
|
||||
setRrpConf(pv => ({
|
||||
...pv,
|
||||
docLoaded: true,
|
||||
reload: false,
|
||||
sections: dataGrids
|
||||
}));
|
||||
handleSectionChange(tabFocus);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [rrpConf.reload, rrpConf.docLoaded, dataGrid.reload, dataGrid.docLoaded, executeStored]);
|
||||
|
||||
//При необходимости обновить данные таблицы
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [rrpConf.reload, dataGrid.reload, loadData]);
|
||||
|
||||
return [rrpConf, handleReload];
|
||||
};
|
||||
|
||||
//Хук для вкладки
|
||||
const useTab = () => {
|
||||
//Состояние раздела
|
||||
const [tabValue, setTabValue] = useState("");
|
||||
|
||||
//Переключение раздела
|
||||
const handleSectionChange = useCallback(newValue => {
|
||||
setTabValue(newValue);
|
||||
}, []);
|
||||
|
||||
return [tabValue, handleSectionChange];
|
||||
};
|
||||
|
||||
//Хук для функций открытия записей
|
||||
const useRecOpen = handleReload => {
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgErr } = useContext(MessagingСtx);
|
||||
|
||||
//Отображение показателя раздела
|
||||
const handleMarkOpen = useCallback(
|
||||
async nRrpConfSctnMrk => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_GET_CODES",
|
||||
args: {
|
||||
NRN: nRrpConfSctnMrk
|
||||
},
|
||||
tagValueProcessor: () => undefined
|
||||
});
|
||||
if (data) {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "RRPConfig",
|
||||
showMethod: "main_mrk_settings",
|
||||
inputParameters: [
|
||||
{ name: "in_CODE", value: data.SRRPCONF },
|
||||
{ name: "in_SCTN_CODE", value: data.SRRPCONFSCTN },
|
||||
{ name: "in_MRK_CODE", value: data.SRRPCONFSCTNMRK }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success ? handleReload() : null;
|
||||
}
|
||||
});
|
||||
} else showMsgErr(TEXTS.NO_DATA_FOUND);
|
||||
},
|
||||
[executeStored, handleReload, pOnlineShowDictionary, showMsgErr]
|
||||
);
|
||||
|
||||
//Отображение показателя раздела
|
||||
const handleMarkCnOpen = useCallback(
|
||||
async (nRrpConfSctnMrk, nRrpConfSctnMrkCn) => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "RRPConfigSectionMark",
|
||||
showMethod: "link_cn",
|
||||
inputParameters: [
|
||||
{ name: "in_RN", value: nRrpConfSctnMrk },
|
||||
{ name: "in_RRPCONFSCTNMRKCN", value: nRrpConfSctnMrkCn }
|
||||
],
|
||||
callBack: res => {
|
||||
res.success ? handleReload() : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[handleReload, pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
//Отображение показателя раздела
|
||||
const handleMarkCnInsert = useCallback(
|
||||
async nRrpConfSctnMrk => {
|
||||
pOnlineShowDictionary({
|
||||
unitCode: "RRPConfigSectionMarkConstitution",
|
||||
showMethod: "link_add",
|
||||
inputParameters: [{ name: "in_PRN", value: nRrpConfSctnMrk }],
|
||||
callBack: res => {
|
||||
res.success ? handleReload() : null;
|
||||
}
|
||||
});
|
||||
},
|
||||
[handleReload, pOnlineShowDictionary]
|
||||
);
|
||||
|
||||
return [handleMarkOpen, handleMarkCnOpen, handleMarkCnInsert];
|
||||
};
|
||||
|
||||
//Хук для форм диалогового окна
|
||||
const useFormDialog = () => {
|
||||
//Состояние открытия диалогового окна
|
||||
const [formOpen, setForm] = useState(false);
|
||||
|
||||
//Состояние диалогового окна
|
||||
const [formData, setFormData] = useState({
|
||||
reload: false,
|
||||
rn: "",
|
||||
prn: "",
|
||||
sctnName: "",
|
||||
sctnCode: "",
|
||||
status: "",
|
||||
code: "",
|
||||
name: "",
|
||||
colCode: "",
|
||||
colRn: null,
|
||||
rowCode: "",
|
||||
rowRn: null
|
||||
});
|
||||
|
||||
//Подключение к контексту навигации
|
||||
const { getNavigationSearch } = useContext(NavigationCtx);
|
||||
|
||||
//Открытие диалогового окна
|
||||
const openForm = () => {
|
||||
setForm(true);
|
||||
};
|
||||
|
||||
//Очистка диалогового окна
|
||||
const clearFormData = () => {
|
||||
setFormData({
|
||||
reload: false,
|
||||
rn: "",
|
||||
prn: "",
|
||||
sctnName: "",
|
||||
sctnCode: "",
|
||||
status: "",
|
||||
code: "",
|
||||
name: "",
|
||||
colCode: "",
|
||||
colRn: null,
|
||||
rowCode: "",
|
||||
rowRn: null
|
||||
});
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку добавления секции
|
||||
const handleSectionAdd = () => {
|
||||
setFormData({ status: STATUSES.CREATE, prn: Number(getNavigationSearch().NRN) });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку исправления секции
|
||||
const handleSectionEdit = (rn, code, name) => {
|
||||
setFormData({ rn: rn, code: code, name: name, status: STATUSES.EDIT });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку удаления секции
|
||||
const handleSectionDelete = (rn, code, name) => {
|
||||
setFormData({ rn: rn, code: code, name: name, status: STATUSES.DELETE });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку добавления показателя раздела
|
||||
const handleMarkAdd = (prn, rowRn = null, rowCode = "", colRn = null, colCode = "") => {
|
||||
setFormData({
|
||||
reload: rowRn && colRn ? true : false,
|
||||
prn: prn,
|
||||
rowRn: rowRn,
|
||||
rowCode: rowCode,
|
||||
colRn: colRn,
|
||||
colCode: colCode,
|
||||
status: STATUSES.RRPCONFSCTNMRK_CREATE
|
||||
});
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку исправления показателя раздела
|
||||
const handleMarkEdit = rn => {
|
||||
setFormData({ reload: true, rn: rn, status: STATUSES.RRPCONFSCTNMRK_EDIT });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку удаления показателя раздела
|
||||
const handleMarkDelete = rn => {
|
||||
setFormData({ rn: rn, status: STATUSES.RRPCONFSCTNMRK_DELETE });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//При закрытии диалога
|
||||
const handleDialogClose = () => {
|
||||
setForm(false);
|
||||
clearFormData();
|
||||
};
|
||||
|
||||
return [
|
||||
formOpen,
|
||||
formData,
|
||||
handleSectionAdd,
|
||||
handleSectionEdit,
|
||||
handleSectionDelete,
|
||||
handleMarkAdd,
|
||||
handleMarkEdit,
|
||||
handleMarkDelete,
|
||||
handleDialogClose
|
||||
];
|
||||
};
|
||||
|
||||
//Формирование разделов
|
||||
const a11yProps = index => {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`
|
||||
};
|
||||
};
|
||||
|
||||
//----------------
|
||||
//Интерфейс модуля
|
||||
//----------------
|
||||
|
||||
export { useWindowResize };
|
||||
export { useWindowResize, useConf, useTab, useRecOpen, useFormDialog, a11yProps };
|
||||
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
|
||||
Дополнительная разметка и вёрстка клиентских элементов
|
||||
*/
|
||||
|
||||
//---------------------
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React from "react"; //Классы React
|
||||
import { Box, IconButton, Icon, Link } from "@mui/material"; //Интерфейсные компоненты
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
//---------
|
||||
|
||||
//Стили
|
||||
export const STYLES = {
|
||||
BOX_ROW: { display: "flex", justifyContent: "center", alignItems: "center" },
|
||||
LINK_STYLE: { component: "button", cursor: "pointer", width: "-webkit-fill-available" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
//Тело модуля
|
||||
//-----------
|
||||
|
||||
//Генерация представления ячейки c данными показателя раздела регламентированного отчета
|
||||
export const confSctnMrkCellRender = ({ row, columnDef, onLinkClick, onEditClick, onDeleteClick }) => {
|
||||
let data = row[columnDef.name];
|
||||
columnDef.name != "SROW_NAME" && data != undefined && columnDef.visible == true
|
||||
? (data = (
|
||||
<Box sx={STYLES.BOX_ROW}>
|
||||
<Link sx={STYLES.LINK_STYLE} onClick={() => (onLinkClick ? onLinkClick(row["NRN_" + columnDef.name.substring(5)]) : null)}>
|
||||
{row[columnDef.name]}
|
||||
</Link>
|
||||
<IconButton onClick={() => (onEditClick ? onEditClick(row["NRN_" + columnDef.name.substring(5)], row[columnDef.name]) : null)}>
|
||||
<Icon>edit</Icon>
|
||||
</IconButton>
|
||||
<IconButton onClick={() => (onDeleteClick ? onDeleteClick(row["NRN_" + columnDef.name.substring(5)], row[columnDef.name]) : null)}>
|
||||
<Icon>delete</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
))
|
||||
: null;
|
||||
return { data };
|
||||
};
|
@ -7,21 +7,11 @@
|
||||
//Подключение библиотек
|
||||
//---------------------
|
||||
|
||||
import React, { useCallback, useContext, useState, useEffect } from "react"; //Классы React
|
||||
import { Box, Tab, Tabs, IconButton, Icon, Stack, Button } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
|
||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
|
||||
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||
import { SectionTabPanel } from "./section_tab_panel"; //Компонент вкладки раздела
|
||||
import { IUDFormDialog } from "./iud_form_dialog"; //Диалог добавления/исправления/удаления компонентов настройки регламентированного отчёта
|
||||
import { confSctnMrkCellRender } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||
import { STATUSES } from "./iud_form_dialog"; //Статусы диалогового окна
|
||||
import { TEXTS } from "../../../app.text"; //Текстовые константы
|
||||
import { STYLES as COMMON_STYLES } from "./layouts"; //Общие стили
|
||||
import { useWindowResize } from "./hooks"; //Пользовательские хуки
|
||||
import React, { useState, useEffect } from "react"; //Классы React
|
||||
import { Box, Tab, Tabs, IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты
|
||||
import { IUDFormDialog } from "./IUD/iud_form_dialog"; //Диалог добавления/исправления/удаления компонентов настройки регламентированного отчёта
|
||||
import { useWindowResize, useTab, useConf, useRecOpen, useFormDialog, a11yProps } from "./hooks"; //Пользовательские хуки
|
||||
import { SectionTab } from "./components/rrp_section"; //Компонент раздела настройки
|
||||
|
||||
//---------
|
||||
//Константы
|
||||
@ -36,20 +26,13 @@ const pxSectionAddButtonW = 40;
|
||||
|
||||
//Стили
|
||||
const STYLES = {
|
||||
CONTAINER: { width: "100%" },
|
||||
CONTAINER: { width: "100%", minHeight: `calc(100vh - ${pxPanelHeaderH})`, maxHeight: `calc(100vh - ${pxPanelHeaderH})` },
|
||||
PANELS_MAIN_COLOR: { backgroundColor: "#1976d2" },
|
||||
ICON_WHITE: { color: "white" },
|
||||
TABS_BOTTOM_LINE: { borderBottom: 1, borderColor: "divider" },
|
||||
TABS_PADDING: { padding: "5px" },
|
||||
TABS_SIZES: (width, pxSectionAddButtonW) => ({ maxHeight: 150, maxWidth: width - pxSectionAddButtonW }),
|
||||
GRID_PADDING: { paddingTop: 1, paddingBottom: 1 },
|
||||
GRID_SIZES: (width, height, pxOuterMenuH, pxPanelHeaderH, pxTabsH) => ({
|
||||
padding: 0,
|
||||
minWidth: width * 0.95,
|
||||
minHeight: (height - pxOuterMenuH - pxPanelHeaderH - pxTabsH) * 0.88,
|
||||
maxWidth: width * 0.95,
|
||||
maxHeight: (height - pxOuterMenuH - pxPanelHeaderH - pxTabsH) * 0.88
|
||||
})
|
||||
SECTION_ACTIONS: { display: "flex", justifyContent: "center", alignItems: "center" }
|
||||
};
|
||||
|
||||
//-----------
|
||||
@ -58,257 +41,27 @@ const STYLES = {
|
||||
|
||||
//Редактор настройки регламентированного отчёта
|
||||
const RrpConfEditor = () => {
|
||||
const dataGrid = {
|
||||
rn: 0,
|
||||
code: "",
|
||||
name: "",
|
||||
dataLoaded: false,
|
||||
columnsDef: [],
|
||||
groups: [],
|
||||
rows: [],
|
||||
fixedHeader: false,
|
||||
fixedColumns: 0,
|
||||
reload: false
|
||||
};
|
||||
//Состояние вкладки
|
||||
const [tabValue, handleSectionChange] = useTab("");
|
||||
|
||||
//Собственное состояние
|
||||
const [rrpDoc, setRrpDoc] = useState({
|
||||
docLoaded: false,
|
||||
sections: [],
|
||||
reload: true
|
||||
});
|
||||
//Состояние настройки
|
||||
const [rrpConf, handleReload] = useConf(tabValue, handleSectionChange);
|
||||
|
||||
//Состояние массива данных разделов
|
||||
const [dataGrids] = useState([]);
|
||||
//Функции открытия разделов
|
||||
const [handleMarkOpen, handleMarkCnOpen, handleMarkCnInsert] = useRecOpen(handleReload);
|
||||
|
||||
//Состояние раздела
|
||||
const [tabValue, setTabValue] = useState("");
|
||||
|
||||
//Состояние открытия диалогового окна
|
||||
const [formOpen, setForm] = useState(false);
|
||||
|
||||
//Состояние диалогового окна
|
||||
const [formData, setFormData] = useState({
|
||||
rn: "",
|
||||
prn: "",
|
||||
sctnName: "",
|
||||
sctnCode: "",
|
||||
status: "",
|
||||
code: "",
|
||||
name: "",
|
||||
colName: "",
|
||||
colCode: "",
|
||||
colVCode: "",
|
||||
colVRn: 0,
|
||||
rowName: "",
|
||||
rowCode: "",
|
||||
rowVCode: "",
|
||||
rowVRn: 0
|
||||
});
|
||||
|
||||
//Открытие диалогового окна
|
||||
const openForm = () => {
|
||||
setForm(true);
|
||||
};
|
||||
|
||||
//Очистка диалогового окна
|
||||
const clearFormData = () => {
|
||||
setFormData({
|
||||
rn: "",
|
||||
prn: "",
|
||||
sctnName: "",
|
||||
sctnCode: "",
|
||||
status: "",
|
||||
code: "",
|
||||
name: "",
|
||||
colName: "",
|
||||
colCode: "",
|
||||
colVCode: "",
|
||||
colVRn: 0,
|
||||
rowName: "",
|
||||
rowCode: "",
|
||||
rowVCode: "",
|
||||
rowVRn: 0
|
||||
});
|
||||
};
|
||||
|
||||
//Подключение к контексту взаимодействия с сервером
|
||||
const { executeStored } = useContext(BackEndСtx);
|
||||
|
||||
//Подключение к контексту приложения
|
||||
const { pOnlineShowUnit } = useContext(ApplicationСtx);
|
||||
|
||||
//Подключение к контексту навигации
|
||||
const { getNavigationSearch } = useContext(NavigationCtx);
|
||||
|
||||
//Подключение к контексту сообщений
|
||||
const { showMsgErr } = useContext(MessagingСtx);
|
||||
|
||||
//Переключение раздела
|
||||
const handleSectionChange = (event, newValue) => {
|
||||
setTabValue(newValue);
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку добавления секции
|
||||
const addSectionClick = () => {
|
||||
setFormData({ status: STATUSES.CREATE, prn: Number(getNavigationSearch().NRN) });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку исправления секции
|
||||
const editSectionClick = (rn, code, name) => {
|
||||
setFormData({ rn: rn, code: code, name: name, status: STATUSES.EDIT });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку удаления секции
|
||||
const deleteSectionClick = (rn, code, name) => {
|
||||
setFormData({ rn: rn, code: code, name: name, status: STATUSES.DELETE });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку добавления показателя раздела
|
||||
const addSectionMarkClick = (prn, sctnCode, sctnName) => {
|
||||
setFormData({ status: STATUSES.RRPCONFSCTNMRK_CREATE, prn: prn, sctnCode: sctnCode, sctnName: sctnName });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку исправления показателя раздела
|
||||
const editSectionMarkClick = (rn, name) => {
|
||||
setFormData({ status: STATUSES.RRPCONFSCTNMRK_EDIT, rn: rn, name: name });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отработка нажатия на кнопку удаления показателя раздела
|
||||
const deleteSectionMarkClick = (rn, name) => {
|
||||
setFormData({ status: STATUSES.RRPCONFSCTNMRK_DELETE, rn: rn, name: name });
|
||||
openForm();
|
||||
};
|
||||
|
||||
//Отображение показателя раздела
|
||||
const showSectionMark = async rn => {
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_GET_CODES",
|
||||
args: {
|
||||
NRN: rn
|
||||
},
|
||||
tagValueProcessor: () => undefined
|
||||
});
|
||||
if (data) {
|
||||
pOnlineShowUnit({
|
||||
unitCode: "RRPConfig",
|
||||
showMethod: "main_mrk_settings",
|
||||
inputParameters: [
|
||||
{ name: "in_CODE", value: data.SRRPCONF },
|
||||
{ name: "in_SCTN_CODE", value: data.SRRPCONFSCTN },
|
||||
{ name: "in_MRK_CODE", value: data.SRRPCONFSCTNMRK }
|
||||
]
|
||||
});
|
||||
} else showMsgErr(TEXTS.NO_DATA_FOUND);
|
||||
};
|
||||
|
||||
//Формирование разделов
|
||||
const a11yProps = index => {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
"aria-controls": `simple-tabpanel-${index}`
|
||||
};
|
||||
};
|
||||
|
||||
//Загрузка данных разделов регламентированного отчёта
|
||||
const loadData = useCallback(async () => {
|
||||
if (rrpDoc.reload) {
|
||||
//Переменная номера раздела с фокусом
|
||||
let tabFocus = 0;
|
||||
const data = await executeStored({
|
||||
stored: "PKG_P8PANELS_RRPCONFED.RRPCONF_GET_SECTIONS",
|
||||
args: {
|
||||
NRN_RRPCONF: Number(getNavigationSearch().NRN)
|
||||
},
|
||||
respArg: "COUT"
|
||||
});
|
||||
//Флаг первой загрузки данных
|
||||
let firstLoad = dataGrids.length == 0 ? true : false;
|
||||
//Копирование массива уже загруженных разделов
|
||||
let cloneDGs = dataGrids.slice();
|
||||
//Массив из нескольких разделов и из одного
|
||||
const sections = data.SECTIONS ? (data.SECTIONS.length ? data.SECTIONS : [data.SECTIONS]) : [];
|
||||
//Заполнение очередного раздела по шаблону
|
||||
sections.map(s => {
|
||||
let dg = {};
|
||||
Object.assign(dg, dataGrid, {
|
||||
rn: s.NRN,
|
||||
code: s.SCODE,
|
||||
name: s.SNAME,
|
||||
dataLoaded: true,
|
||||
columnsDef: [...(s.XDATA.XCOLUMNS_DEF || [])],
|
||||
groups: [...(s.XDATA.XGROUPS || [])],
|
||||
rows: [...(s.XDATA.XROWS || [])],
|
||||
fixedHeader: s.XDATA.XDATA_GRID.fixedHeader,
|
||||
fixedColumns: s.XDATA.XDATA_GRID.fixedColumns,
|
||||
reload: false
|
||||
});
|
||||
//Ищем загружен ли уже раздел с таким же ид.
|
||||
const dgItem = dataGrids.find(x => x.rn === dg.rn);
|
||||
//Его индекс, если нет соответствия, то -1
|
||||
let index = dataGrids.indexOf(dgItem);
|
||||
//Если было соответствие
|
||||
if (dgItem) {
|
||||
//Если в нём не найдено изменений
|
||||
if (JSON.stringify(dgItem, null, 4) === JSON.stringify(dg, null, 4)) {
|
||||
//То из копированного массива его удаляем
|
||||
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dgItem.rn)), 1);
|
||||
} else {
|
||||
//Иначе обновляем раздел в массиве
|
||||
dataGrids[index] = dg;
|
||||
//Удаляем из копированного массива
|
||||
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dg.rn)), 1);
|
||||
//Устанавливаем фокус на обновлённый раздел
|
||||
tabFocus = index;
|
||||
}
|
||||
} else {
|
||||
//Если раздел новый, то добавляем его в массив данных
|
||||
dataGrids.push(dg);
|
||||
//И устанавливаем на него фокус, если флаг первой загрузки = false
|
||||
tabFocus = !firstLoad ? dataGrids.length - 1 : 0;
|
||||
}
|
||||
});
|
||||
//Обходим разделы, что остались в копированном массиве (на удаление)
|
||||
cloneDGs.map(s => {
|
||||
let curIndex = dataGrids.indexOf(dataGrids.find(x => x.rn === s.rn));
|
||||
//Устаревший раздел удаляем из массива данных
|
||||
dataGrids.splice(curIndex, 1);
|
||||
//Фокус на предшествующий раздел
|
||||
if (curIndex > 0) tabFocus = curIndex - 1;
|
||||
//Иначе фокус на следующий, если был удалён первый раздел
|
||||
else tabFocus = curIndex;
|
||||
});
|
||||
setRrpDoc(pv => ({
|
||||
...pv,
|
||||
docLoaded: true,
|
||||
reload: false,
|
||||
sections: dataGrids
|
||||
}));
|
||||
setTabValue(tabFocus);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [rrpDoc.reload, rrpDoc.docLoaded, dataGrid.reload, dataGrid.docLoaded, executeStored]);
|
||||
|
||||
//При необходимости обновить данные таблицы
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [rrpDoc.reload, dataGrid.reload, loadData]);
|
||||
|
||||
//При изменениях элемента
|
||||
const handleDialogReload = () => {
|
||||
setRrpDoc(pv => ({ ...pv, reload: true }));
|
||||
};
|
||||
|
||||
//При закрытии диалога
|
||||
const handleDialogClose = () => {
|
||||
setForm(false);
|
||||
clearFormData();
|
||||
};
|
||||
//Состояние форм диалога
|
||||
const [
|
||||
formOpen,
|
||||
formData,
|
||||
handleSectionAdd,
|
||||
handleSectionEdit,
|
||||
handleSectionDelete,
|
||||
handleMarkAdd,
|
||||
handleMarkEdit,
|
||||
handleMarkDelete,
|
||||
handleDialogClose
|
||||
] = useFormDialog();
|
||||
|
||||
//Состояние ширины и высоты рабочей области окна
|
||||
const [width, height] = useWindowResize();
|
||||
@ -318,39 +71,49 @@ const RrpConfEditor = () => {
|
||||
|
||||
//При рендере данных
|
||||
useEffect(() => {
|
||||
rrpDoc.docLoaded ? setPxTabsH(document.getElementById("sectionTabs").offsetHeight) : null;
|
||||
}, [rrpDoc.docLoaded]);
|
||||
rrpConf.docLoaded ? setPxTabsH(document.getElementById("sectionTabs").offsetHeight) : null;
|
||||
}, [rrpConf.docLoaded]);
|
||||
|
||||
//Формируем меню показателей
|
||||
const markMenuItems = [
|
||||
{ method: "EDIT", name: "Исправить", icon: "edit", func: handleMarkEdit },
|
||||
{ method: "DELETE", name: "Удалить", icon: "delete", func: handleMarkDelete }
|
||||
];
|
||||
|
||||
//Генерация содержимого
|
||||
return (
|
||||
<Box sx={STYLES.CONTAINER}>
|
||||
{formOpen ? <IUDFormDialog initial={formData} onClose={handleDialogClose} onReload={handleDialogReload} /> : null}
|
||||
{rrpDoc.docLoaded ? (
|
||||
{formOpen ? <IUDFormDialog initial={formData} onClose={handleDialogClose} onReload={handleReload} /> : null}
|
||||
{rrpConf.docLoaded ? (
|
||||
<Box>
|
||||
<Stack direction="row" sx={STYLES.TABS_BOTTOM_LINE}>
|
||||
<Tabs
|
||||
id="sectionTabs"
|
||||
value={tabValue}
|
||||
onChange={handleSectionChange}
|
||||
onChange={(event, newValue) => handleSectionChange(newValue)}
|
||||
variant="scrollable"
|
||||
scrollButtons={false}
|
||||
visibleScrollbar
|
||||
aria-label="section tab"
|
||||
sx={STYLES.TABS_SIZES(width, pxSectionAddButtonW)}
|
||||
>
|
||||
{rrpDoc.sections.map((s, i) => {
|
||||
{rrpConf.sections.map((s, i) => {
|
||||
return (
|
||||
<Tab
|
||||
key={s.rn}
|
||||
{...a11yProps(i)}
|
||||
sx={STYLES.TABS_PADDING}
|
||||
label={
|
||||
<Box sx={COMMON_STYLES.BOX_ROW}>
|
||||
<Box sx={STYLES.SECTION_ACTIONS}>
|
||||
{s.name}
|
||||
<IconButton component="span" onClick={() => editSectionClick(s.rn, s.code, s.name)}>
|
||||
<IconButton component="span" onClick={() => handleSectionEdit(s.rn, s.code, s.name)}>
|
||||
<Icon>edit</Icon>
|
||||
</IconButton>
|
||||
<IconButton component="span" onClick={() => deleteSectionClick(s.rn, s.code, s.name)}>
|
||||
<IconButton
|
||||
disabled={s.delete_allow === 0}
|
||||
component="span"
|
||||
onClick={() => handleSectionDelete(s.rn, s.code, s.name)}
|
||||
>
|
||||
<Icon>delete</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
@ -361,42 +124,26 @@ const RrpConfEditor = () => {
|
||||
})}
|
||||
</Tabs>
|
||||
<Box display="flex" justifyContent="center" alignItems="center" sx={STYLES.PANELS_MAIN_COLOR}>
|
||||
<IconButton onClick={addSectionClick}>
|
||||
<IconButton onClick={handleSectionAdd}>
|
||||
<Icon sx={STYLES.ICON_WHITE}>add</Icon>
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Stack>
|
||||
{rrpDoc.sections.map((s, i) => {
|
||||
{rrpConf.sections.map((s, i) => {
|
||||
return (
|
||||
<SectionTabPanel key={s.rn} value={tabValue} index={i}>
|
||||
<Button onClick={() => addSectionMarkClick(s.rn, s.code, s.name)}>Добавить</Button>
|
||||
{s.dataLoaded && s.columnsDef.length > 1 ? (
|
||||
<Box sx={{ ...STYLES.GRID_PADDING, ...COMMON_STYLES.BOX_ROW }}>
|
||||
<P8PDataGrid
|
||||
{...P8P_DATA_GRID_CONFIG_PROPS}
|
||||
containerComponentProps={{
|
||||
elevation: 6,
|
||||
style: STYLES.GRID_SIZES(width, height, pxOuterMenuH, pxPanelHeaderH, pxTabsH)
|
||||
}}
|
||||
columnsDef={s.columnsDef}
|
||||
groups={s.groups}
|
||||
rows={s.rows}
|
||||
fixedHeader={s.fixedHeader}
|
||||
fixedColumns={s.fixedColumns}
|
||||
size={P8P_DATA_GRID_SIZE.LARGE}
|
||||
reloading={s.reload}
|
||||
dataCellRender={prms =>
|
||||
confSctnMrkCellRender({
|
||||
...prms,
|
||||
onLinkClick: showSectionMark,
|
||||
onEditClick: editSectionMarkClick,
|
||||
onDeleteClick: deleteSectionMarkClick
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
) : null}
|
||||
</SectionTabPanel>
|
||||
<SectionTab
|
||||
key={s.rn}
|
||||
section={s}
|
||||
tabValue={tabValue}
|
||||
index={i}
|
||||
containerProps={{ height, pxOuterMenuH, pxPanelHeaderH, pxTabsH }}
|
||||
handleReload={handleReload}
|
||||
handleMarkOpen={handleMarkOpen}
|
||||
handleMarkAdd={handleMarkAdd}
|
||||
handleMarkCnOpen={handleMarkCnOpen}
|
||||
handleMarkCnInsert={handleMarkCnInsert}
|
||||
menuItems={markMenuItems}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user