ЦИТК-899 - рефакторинг панели, управление сортировкой строк/граф

This commit is contained in:
Mikhail Chechnev 2025-07-07 10:27:31 +03:00
parent 6958cfd904
commit 077582bd4c
25 changed files with 2636 additions and 2462 deletions

View File

@ -1,358 +0,0 @@
/*
Парус 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 { IUDFormTextField } from "./iud_form_text_field"; //Компонент поля ввода
//---------
//Константы
//---------
//Стили
const STYLES = {
CLOSE_BUTTON: {
position: "absolute",
right: 8,
top: 8,
color: theme => theme.palette.grey[500]
},
PADDING_DIALOG_BUTTONS_RIGHT: { paddingRight: "32px" }
};
//Статусы диалогового окна
export const STATUSES = { CREATE: 0, EDIT: 1, DELETE: 2, RRPCONFSCTNMRK_CREATE: 3, RRPCONFSCTNMRK_EDIT: 4, RRPCONFSCTNMRK_DELETE: 5 };
//---------------
//Тело компонента
//---------------
const IUDFormDialog = ({ initial, onClose, onReload }) => {
//Собственное состояние
const [formData, setFormData] = useState({ ...initial });
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//При закрытии диалога без изменений
const handleCancel = () => (onClose ? onClose() : null);
//При закрытии диалога с изменениями
const handleOK = () => {
if (onClose) {
changeSections();
onClose();
} else null;
};
//Отработка добавления/изсправления/удаления элемента
const handleReload = () => {
if (onReload) {
onReload();
} else null;
};
//При изменении значения элемента
const handleDialogItemChange = (item, value) => {
setFormData(pv => ({ ...pv, [item]: value }));
};
//Отработка изменений в разделе или показателе раздела
const changeSections = useCallback(async () => {
switch (formData.status) {
case STATUSES.CREATE:
await insertSections();
break;
case STATUSES.EDIT:
await updateSections();
break;
case STATUSES.DELETE:
await deleteSections();
break;
case STATUSES.RRPCONFSCTNMRK_CREATE:
await addRRPCONFSCTNMRK();
break;
case STATUSES.RRPCONFSCTNMRK_EDIT:
await editRRPCONFSCTNMRK();
break;
case STATUSES.RRPCONFSCTNMRK_DELETE:
await deleteRRPCONFSCTNMRK();
break;
}
handleReload();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formData]);
//Добавление раздела
const insertSections = useCallback(async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTN_INSERT",
args: {
NPRN: formData.prn,
SCODE: formData.code,
SNAME: formData.name
}
});
setFormData(pv => ({
...pv,
rn: Number(data.NRN)
}));
}, [formData.prn, formData.code, formData.name, executeStored]);
//Исправление раздела
const updateSections = useCallback(async () => {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTN_UPDATE",
args: {
NRN: formData.rn,
SCODE: formData.code,
SNAME: formData.name
}
});
}, [formData.name, formData.code, formData.rn, executeStored]);
//Удаление раздела
const deleteSections = useCallback(async () => {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTN_DELETE",
args: {
NRN: formData.rn
}
});
}, [formData.rn, executeStored]);
//Добавление показателя раздела
const addRRPCONFSCTNMRK = useCallback(async () => {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_INSERT",
args: {
NPRN: formData.prn,
SCODE: formData.code,
SNAME: formData.name,
NRRPROW: formData.rowRn,
NRRPCOLUMN: formData.colRn
}
});
}, [executeStored, formData.code, formData.colRn, formData.name, formData.prn, formData.rowRn]);
//Исправление показателя раздела
const editRRPCONFSCTNMRK = useCallback(async () => {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_UPDATE",
args: { NRN: formData.rn, SNAME: formData.name }
});
}, [executeStored, formData.name, formData.rn]);
//Удаление показателя раздела
const deleteRRPCONFSCTNMRK = useCallback(async () => {
await executeStored({ stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_DELETE", args: { NRN: formData.rn } });
}, [executeStored, formData.rn]);
//Формирование заголовка диалогового окна
const formTitle = () => {
switch (formData.status) {
case STATUSES.CREATE:
return "Добавление раздела";
case STATUSES.EDIT:
return "Исправление раздела";
case STATUSES.DELETE:
return "Удаление раздела";
case STATUSES.RRPCONFSCTNMRK_CREATE:
return "Добавление показателя раздела";
case STATUSES.RRPCONFSCTNMRK_EDIT:
return "Исправление показателя раздела";
case STATUSES.RRPCONFSCTNMRK_DELETE:
return "Удаление показателя раздела";
}
};
//Отрисовка диалогового окна
const renderSwitch = () => {
let btnText = "";
switch (formData.status) {
case STATUSES.CREATE:
case STATUSES.RRPCONFSCTNMRK_CREATE:
btnText = "Добавить";
break;
case STATUSES.EDIT:
case STATUSES.RRPCONFSCTNMRK_EDIT:
btnText = "Исправить";
break;
case STATUSES.DELETE:
case STATUSES.RRPCONFSCTNMRK_DELETE:
btnText = "Удалить";
break;
}
return (
<Button
onClick={() => {
handleOK({ formData });
}}
>
{btnText}
</Button>
);
};
//Выбор строки
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_RN);
setFormData(pv => ({
...pv,
reload: true,
rowCode: res.outParameters.out_CODE,
rowRn: res.outParameters.out_RN
}));
} else callBack(null);
}
});
};
//Выбор графы
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_RN);
setFormData(pv => ({
...pv,
reload: true,
colCode: res.outParameters.out_CODE,
colRn: res.outParameters.out_RN
}));
} else callBack(null);
}
});
};
//Получение мнемокода и наименования показателя раздела
const getSctnMrkCodeName = useCallback(async () => {
const data = await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_GET_CODE_NAME",
args: {
NRRPCONFSCTN: formData.prn,
NRRPROW: formData.rowRn,
NRRPCOLUMN: formData.colRn
}
});
setFormData(pv => ({
...pv,
reload: false,
code: data.SCODE,
name: data.SNAME
}));
}, [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.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 (
<Dialog open onClose={handleCancel}>
<DialogTitle>{formTitle()}</DialogTitle>
<IconButton aria-label="close" onClick={handleCancel} sx={STYLES.CLOSE_BUTTON}>
<Icon>close</Icon>
</IconButton>
<DialogContent>
{formData.status == STATUSES.DELETE || formData.status == STATUSES.RRPCONFSCTNMRK_DELETE ? (
formData.status == STATUSES.DELETE ? (
<Typography>Вы хотите удалить раздел {formData.name}?</Typography>
) : (
<Typography>Вы хотите удалить показатель раздела {formData.name}?</Typography>
)
) : (
<div>
{formData.status != STATUSES.RRPCONFSCTNMRK_EDIT ? (
<IUDFormTextField
elementCode="code"
elementValue={formData.code}
labelText="Мнемокод"
onChange={handleDialogItemChange}
/>
) : null}
<IUDFormTextField
elementCode="name"
elementValue={formData.name}
labelText="Наименование"
onChange={handleDialogItemChange}
/>
{formData.status == STATUSES.RRPCONFSCTNMRK_CREATE ? (
<div>
<IUDFormTextField
elementCode="row"
elementValue={formData.rowCode}
labelText="Строка"
onChange={handleDialogItemChange}
dictionary={callBack => selectRow(pOnlineShowDictionary, callBack)}
/>
<IUDFormTextField
elementCode="column"
elementValue={formData.colCode}
labelText="Графа"
onChange={handleDialogItemChange}
dictionary={callBack => selectColumn(pOnlineShowDictionary, callBack)}
/>
</div>
) : null}
</div>
)}
</DialogContent>
<DialogActions sx={STYLES.PADDING_DIALOG_BUTTONS_RIGHT}>
{renderSwitch()}
<Button onClick={handleCancel}>Отмена</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог
IUDFormDialog.propTypes = {
initial: PropTypes.object.isRequired,
onClose: PropTypes.func,
onReload: PropTypes.func
};
//--------------------
//Интерфейс компонента
//--------------------
export { IUDFormDialog };

View File

@ -1,87 +0,0 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Панель мониторинга: Компонент поля ввода
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, FormControl, InputLabel, Input, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
//---------
//Константы
//---------
//Стили
const STYLES = {
DIALOG_WINDOW_WIDTH: { width: 400 }
};
//---------------
//Тело компонента
//---------------
//Поле ввода
const IUDFormTextField = ({ elementCode, elementValue, labelText, onChange, dictionary, ...other }) => {
//Значение элемента
const [value, setValue] = useState(elementValue);
//При получении нового значения из вне
useEffect(() => {
setValue(elementValue);
}, [elementValue]);
//Выбор значения из словаря
const handleDictionaryClick = () =>
dictionary ? dictionary(res => (res ? handleChange({ target: { name: elementCode, value: res } }) : null)) : null;
//Изменение значения элемента
const handleChange = e => {
setValue(e.target.value);
if (onChange) onChange(e.target.name, e.target.value);
};
//Генерация содержимого
return (
<Box p={1}>
<FormControl variant="standard" sx={STYLES.DIALOG_WINDOW_WIDTH} {...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}
multiline
maxRows={4}
/>
</FormControl>
</Box>
);
};
//Контроль свойств - Поле ввода
IUDFormTextField.propTypes = {
elementCode: PropTypes.string.isRequired,
elementValue: PropTypes.string,
labelText: PropTypes.string.isRequired,
onChange: PropTypes.func,
dictionary: PropTypes.func
};
//--------------------
//Интерфейс компонента
//--------------------
export { IUDFormTextField };

View File

@ -0,0 +1,43 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Обще стили и константы
*/
//---------
//Константы
//---------
//Допустимые варианты сортировки строк/граф
const COL_ROW_ORDER = [
{ name: "Номер", value: 0 },
{ name: "Код", value: 1 },
{ name: "Мнемокод", value: 2 }
];
//Типовые цвета
const BG_GRAY = "#e3e3e3";
const BG_BLUE = "#1976d210";
//Типовые размеры шрифтов
const FONT_SIZE_LARGE = "0.85rem";
const FONT_SIZE_SMALL = "0.75rem";
//Стили
const STYLES = {
TOOLBAR: { position: "absolute", right: 0, backgroundColor: BG_BLUE },
LINK: { component: "button", cursor: "pointer", width: "-webkit-fill-available" },
FONT_DATA_GRID: { fontSize: FONT_SIZE_SMALL },
FONT_MARK_CARD_HEAD: { fontSize: FONT_SIZE_LARGE },
FONT_MARK_CARD_BODY: { fontSize: FONT_SIZE_SMALL },
BG_DATA_GRID_HEAD_CELL: { backgroundColor: BG_GRAY },
BG_DATA_GRID_DATA_CELL: { backgroundColor: BG_GRAY },
BG_MARK_CARD: { backgroundColor: BG_BLUE },
BORDER_DATA_GRID_HEAD_CELL: { border: "1px solid white" },
BORDER_DATA_GRID_DATA_CELL: { borderBottom: `1px solid ${BG_GRAY}` }
};
//----------------
//Интерфейс модуля
//----------------
export { COL_ROW_ORDER, STYLES };

View File

@ -0,0 +1,59 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Сообщение с действиями
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Icon, Stack, Typography } from "@mui/material"; //Интерфейсные элементы
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { height: "100%", width: "100%" }
};
//-----------
//Тело модуля
//-----------
//Сообщение с действиями
const ActionMessage = ({ icon, title, desc, children }) => {
return (
<Stack direction={"column"} justifyContent={"center"} sx={STYLES.CONTAINER}>
<Stack direction={"row"} justifyContent={"center"} alignItems={"center"}>
<Icon color={"disabled"}>{icon}</Icon>
<Typography align={"center"} color={"text.secondary"} variant={"button"}>
{title}
</Typography>
</Stack>
<Typography align={"center"} variant={"caption"}>
{desc}
</Typography>
<Stack direction={"row"} justifyContent={"center"} alignItems={"center"}>
{children}
</Stack>
</Stack>
);
};
//Контроль свойств - Сообщение с действиями
ActionMessage.propTypes = {
icon: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
desc: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)])
};
//----------------
//Интерфейс модуля
//----------------
export { ActionMessage };

View File

@ -0,0 +1,87 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Диалог дополнительной информации
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Typography, List, ListItem } from "@mui/material"; //Интерфейсные элементы
import { Form } from "./form"; //Типовая форма
//---------
//Константы
//---------
//Стили
const STYLES = {
HELP_LIST_ITEM: { padding: "0px 0px 0px 5px", whiteSpace: "pre" },
HELP_LIST_ITEM_NAME: { fontWeight: "bold", minWidth: "45px" }
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Элемент списка расшифровки состава
const HelpListItem = ({ name, desc }) => {
return (
<ListItem sx={STYLES.HELP_LIST_ITEM}>
<Typography sx={STYLES.HELP_LIST_ITEM_NAME}>{name}</Typography>
<Typography>{` - ${desc}`}</Typography>
</ListItem>
);
};
//Контроль свойств - Элемент списка расшифровки состава
HelpListItem.propTypes = {
name: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired
};
//-----------
//Тело модуля
//-----------
//Диалог дополнительной информации
const DialogHelp = ({ onClose }) => {
//При закрытии диалога
const handleClose = () => onClose && onClose();
//Генерация содержимого
return (
<Form title={"Информация"} onClose={handleClose}>
<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>
</Form>
);
};
//Контроль свойств - Диалог дополнительной информации
DialogHelp.propTypes = {
onClose: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { DialogHelp };

View File

@ -0,0 +1,103 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Диалог добавления/исправления показателя
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { useDictionary } from "../hooks"; //Кастомные хуки
import { Form } from "./form"; //Типовая форма
//-----------
//Тело модуля
//-----------
//Диалог добавления/исправления показателя
const DialogMarkIU = ({
code = "",
name = "",
rowCode = "",
rowVersion = "",
columnCode = "",
columnVersion = "",
insert = true,
onOk,
onCancel
}) => {
//Нажатие на кнопку "Ok"
const handleOk = values => onOk && onOk({ ...values });
//Нажатие на кнопку "Отмена"
const handleCancel = () => onCancel && onCancel();
//Хуки для работы со словарями
const { selectRRPRow, selectRRPColumn } = useDictionary();
//Выбор строки из словаря
const selectRow = (currentFormValues, setFormValues) => {
selectRRPRow(
currentFormValues.rowCode,
currentFormValues.rowVersion,
selectResult =>
selectResult &&
setFormValues([
{ name: "rowCode", value: selectResult.code },
{ name: "rowVersion", value: selectResult.version }
])
);
};
//Выбор графы из словаря
const selectColumn = (currentFormValues, setFormValues) => {
selectRRPColumn(
currentFormValues.columnCode,
currentFormValues.columnVersion,
selectResult =>
selectResult &&
setFormValues([
{ name: "columnCode", value: selectResult.code },
{ name: "columnVersion", value: selectResult.version }
])
);
};
//Генерация содержимого
return (
<Form
title={`${insert === true ? "Добавление" : "Исправление"} показателя`}
fields={[
{ elementCode: "code", elementValue: code, labelText: "Мнемокод" },
{ elementCode: "name", elementValue: name, labelText: "Наименование" },
{ elementCode: "rowCode", elementValue: rowCode, labelText: "Строка", dictionary: selectRow },
{ elementCode: "rowVersion", elementValue: rowVersion, labelText: "Редакция строки", disabled: true },
{ elementCode: "columnCode", elementValue: columnCode, labelText: "Графа", dictionary: selectColumn },
{ elementCode: "columnVersion", elementValue: columnVersion, labelText: "Редакция графы", disabled: true }
]}
onOk={handleOk}
onCancel={handleCancel}
/>
);
};
//Контроль свойств - Диалог добавления/исправления показателя
DialogMarkIU.propTypes = {
code: PropTypes.string,
name: PropTypes.string,
rowCode: PropTypes.string,
rowVersion: PropTypes.string,
columnCode: PropTypes.string,
columnVersion: PropTypes.string,
insert: PropTypes.bool,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { DialogMarkIU };

View File

@ -0,0 +1,53 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Диалог сортировки
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Form } from "./form"; //Типовая форма
import { COL_ROW_ORDER } from "../common"; //Обще стили и константы
//-----------
//Тело модуля
//-----------
//Диалог сортировки
const DialogOrder = ({ rowOrder = 0, columnOrder = 0, onOk, onCancel }) => {
//Нажатие на кнопку "Ok"
const handleOk = values => onOk && onOk({ ...values });
//Нажатие на кнопку "Отмена"
const handleCancel = () => onCancel && onCancel();
//Генерация содержимого
return (
<Form
title={"Сортировка"}
fields={[
{ elementCode: "rowOrder", elementValue: rowOrder, labelText: "Строки", list: COL_ROW_ORDER },
{ elementCode: "columnOrder", elementValue: columnOrder, labelText: "Графы", list: COL_ROW_ORDER }
]}
onOk={handleOk}
onCancel={handleCancel}
/>
);
};
//Контроль свойств - Диалог сортировки
DialogOrder.propTypes = {
rowOrder: PropTypes.number,
columnOrder: PropTypes.number,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { DialogOrder };

View File

@ -0,0 +1,53 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Диалог добавления/исправления раздела
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Form } from "./form"; //Типовая форма
//-----------
//Тело модуля
//-----------
//Диалог добавления/исправления раздела
const DialogSectionIU = ({ code = "", name = "", insert = true, onOk, onCancel }) => {
//Нажатие на кнопку "Ok"
const handleOk = values => onOk && onOk({ ...values });
//Нажатие на кнопку "Отмена"
const handleCancel = () => onCancel && onCancel();
//Генерация содержимого
return (
<Form
title={`${insert === true ? "Добавление" : "Исправление"} раздела`}
fields={[
{ elementCode: "code", elementValue: code, labelText: "Мнемокод" },
{ elementCode: "name", elementValue: name, labelText: "Наименование" }
]}
onOk={handleOk}
onCancel={handleCancel}
/>
);
};
//Контроль свойств - Диалог добавления/исправления раздела
DialogSectionIU.propTypes = {
code: PropTypes.string,
name: PropTypes.string,
insert: PropTypes.bool,
onOk: PropTypes.func,
onCancel: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { DialogSectionIU };

View File

@ -0,0 +1,76 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Форма
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from "@mui/material"; //Интерфейсные компоненты
import { BUTTONS } from "../../../../app.text"; //Общие текстовые ресурсы
import { FORM_FILED, FormField } from "./form_field"; //Элемент формы
//-----------
//Тело модуля
//-----------
//Форма
const Form = ({ title, fields = [], children, onOk, onCancel, onClose }) => {
//Состояние формы
const [state, setState] = useState({});
//При изменении элемента формы
const handleFieldChange = (name, value) => setState(pv => ({ ...pv, [name]: value }));
//При нажатии на "ОК" формы
const handleOk = () => onOk && onOk(state);
//При нажатии на "Отмена" формы
const handleCancel = () => onCancel && onCancel();
//При нажатии на "Закрыть" формы
const handleClose = () => (onClose ? onClose() : onCancel ? onCancel() : null);
//При подключении к старнице
useEffect(() => {
setState(fields.reduce((res, f) => ({ ...res, [f.elementCode]: f.elementValue == undefined ? null : f.elementValue }), {}));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Формирование представления
return (
<Dialog onClose={handleClose} open>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
{fields.map((f, i) => (
<FormField key={i} {...f} elementValue={state[f.elementCode]} formValues={state} onChange={handleFieldChange} />
))}
{children}
</DialogContent>
<DialogActions>
{onOk && <Button onClick={handleOk}>{BUTTONS.OK}</Button>}
{onCancel && <Button onClick={handleCancel}>{BUTTONS.CANCEL}</Button>}
{onClose && <Button onClick={handleClose}>{BUTTONS.CLOSE}</Button>}
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Форма
Form.propTypes = {
title: PropTypes.string.isRequired,
fields: PropTypes.arrayOf(PropTypes.shape(FORM_FILED)),
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
onOk: PropTypes.func,
onCancel: PropTypes.func,
onClose: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { Form };

View File

@ -0,0 +1,149 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Поле ввода формы
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Icon, Input, InputAdornment, FormControl, Select, InputLabel, MenuItem, IconButton, Autocomplete, TextField } from "@mui/material"; //Интерфейсные компоненты
//---------
//Константы
//---------
//Формат свойств поля формы
const FORM_FILED = {
elementCode: PropTypes.string.isRequired,
elementValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.instanceOf(Date)]),
labelText: PropTypes.string.isRequired,
onChange: PropTypes.func,
dictionary: PropTypes.func,
list: PropTypes.array,
type: PropTypes.string,
freeSolo: PropTypes.bool,
disabled: PropTypes.bool,
formValues: PropTypes.object
};
//-----------
//Тело модуля
//-----------
//Поле ввода формы
const FormField = ({
elementCode,
elementValue,
labelText,
onChange,
dictionary,
list,
type,
freeSolo = false,
disabled = false,
formValues,
...other
}) => {
//Значение элемента
const [value, setValue] = useState(elementValue);
//При получении нового значения из вне
useEffect(() => {
setValue(elementValue);
}, [elementValue]);
//Выбор значения из словаря
const handleDictionaryClick = () => dictionary && dictionary(formValues, res => (res ? res.map(i => handleChangeByName(i.name, i.value)) : null));
//Изменение значения элемента (по событию)
const handleChange = e => {
setValue(e.target.value);
if (onChange) onChange(e.target.name, e.target.value);
};
//Изменение значения элемента (по имени и значению)
const handleChangeByName = (name, value) => {
if (name === elementCode) setValue(value);
if (onChange) onChange(name, value);
};
//Генерация содержимого
return (
<Box p={1}>
<FormControl variant="standard" fullWidth {...other}>
{list ? (
freeSolo ? (
<Autocomplete
id={elementCode}
name={elementCode}
freeSolo
disabled={disabled}
inputValue={value ? value : ""}
onChange={(event, newValue) => handleChangeByName(elementCode, newValue)}
onInputChange={(event, newInputValue) => handleChangeByName(elementCode, newInputValue)}
options={list}
renderInput={params => <TextField {...params} label={labelText} name={elementCode} variant={"standard"} />}
/>
) : (
<>
<InputLabel id={`${elementCode}Lable`} shrink>
{labelText}
</InputLabel>
<Select
labelId={`${elementCode}Lable`}
id={elementCode}
name={elementCode}
label={labelText}
value={[undefined, null].includes(value) ? "" : value}
onChange={handleChange}
disabled={disabled}
displayEmpty
>
{list.map((item, i) => (
<MenuItem key={i} value={[undefined, null].includes(item.value) ? "" : item.value}>
{item.name}
</MenuItem>
))}
</Select>
</>
)
) : (
<>
<InputLabel {...(type == "date" ? { shrink: true } : {})} 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
}
{...(type ? { type } : {})}
onChange={handleChange}
disabled={disabled}
/>
</>
)}
</FormControl>
</Box>
);
};
//Контроль свойств - Поле ввода формы
FormField.propTypes = FORM_FILED;
//----------------
//Интерфейс модуля
//----------------
export { FORM_FILED, FormField };

View File

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

View File

@ -0,0 +1,122 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Карточка показателя
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Card, CardContent, Typography, Link, Divider } from "@mui/material"; //Интерфейсные компоненты
import { APP_STYLES } from "../../../../app.styles"; //Общие стили приложения
import { MarkCnList } from "./mark_cn_list"; //Состав показателя
import { MarkCardToolbar } from "./mark_card_toolbar"; //Панель инструментов карточки
import { STYLES as COMMON_STYLES } from "../common"; //Общие стили и константы
//---------
//Константы
//---------
//Стили
const STYLES = {
CARD: hovered => ({
padding: "0px 0px 0px 0px",
border: "1px solid lightgrey",
position: "relative",
...(hovered ? COMMON_STYLES.BG_MARK_CARD : null)
}),
CARD_CONTENT_MARK_TITLE: { padding: "0px 0px 0px 0px" },
CARD_CONTENT_MARK_CONSTITUTION: constitutionExists => ({
padding: "8px",
height: "105px",
"&:last-child": { paddingBottom: "10px" },
...COMMON_STYLES.FONT_MARK_CARD_BODY,
...(!constitutionExists ? { display: "flex", justifyContent: "center", alignItems: "center" } : null)
}),
BOX_MARK_CONSTITUTION: {
height: "100%",
width: "100%",
overflowY: "auto",
...APP_STYLES.SCROLL
}
};
//-----------
//Тело модуля
//-----------
//Карточка показателя
const MarkCard = ({ mark, code, name, constitution = [], onMarkAdd, onMarkUpdate, onMarkDelete, onMarkOpen, onMarkCnOpen, onMarkCnAdd }) => {
//Флаг нахождения указателя мыши в карточке
const [hovered, setHovered] = useState(false);
//При попадании мыши на закладку раздела
const handleCardMouseIn = () => setHovered(true);
//При выходе мыши из закладки раздела
const handleCardMouseOut = () => setHovered(false);
//Флаг наличия данных в составе показателя
const constitutionExists = constitution?.length > 0;
//Формирование представления
return mark ? (
<Card variant={"plain"} sx={STYLES.CARD(hovered)} onMouseEnter={handleCardMouseIn} onMouseLeave={handleCardMouseOut}>
<CardContent sx={STYLES.CARD_CONTENT_MARK_TITLE}>
<MarkCardToolbar
title={code}
desc={name}
onOpen={onMarkOpen}
onEdit={onMarkUpdate}
onDelete={onMarkDelete}
onCnAdd={onMarkCnAdd}
showButtons={hovered}
/>
</CardContent>
<Divider />
<CardContent sx={STYLES.CARD_CONTENT_MARK_CONSTITUTION(constitutionExists)}>
{constitutionExists ? (
<Box sx={STYLES.BOX_MARK_CONSTITUTION}>
<MarkCnList constitution={constitution} onMarkCnOpen={constitutionRn => onMarkCnOpen && onMarkCnOpen(constitutionRn)} />
</Box>
) : (
<Box>
<Typography sx={COMMON_STYLES.FONT_MARK_CARD_BODY}>Показатель не имеет состава</Typography>
<Link sx={COMMON_STYLES.LINK} onClick={onMarkCnAdd}>
Добавить
</Link>
</Box>
)}
</CardContent>
</Card>
) : (
<Box>
<Typography sx={COMMON_STYLES.FONT_MARK_CARD_BODY}>Показатель отсутствует</Typography>
<Link sx={COMMON_STYLES.LINK} onClick={onMarkAdd}>
Добавить
</Link>
</Box>
);
};
//Контроль свойств - Карточка показателя
MarkCard.propTypes = {
mark: PropTypes.number,
code: PropTypes.string,
name: PropTypes.string,
constitution: PropTypes.arrayOf(PropTypes.object),
onMarkAdd: PropTypes.func.isRequired,
onMarkUpdate: PropTypes.func.isRequired,
onMarkDelete: PropTypes.func.isRequired,
onMarkOpen: PropTypes.func.isRequired,
onMarkCnOpen: PropTypes.func.isRequired,
onMarkCnAdd: PropTypes.func.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { MarkCard };

View File

@ -0,0 +1,73 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Панель инструментов карточки показателя
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Stack, IconButton, Icon, Typography, Link } from "@mui/material"; //Интерфейсные компоненты
import { STYLES as COMMON_STYLES } from "../common"; //Общие стили и константы
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { ...COMMON_STYLES.TOOLBAR, width: "100%", position: "unset", right: "unset", height: "40px" }
};
//-----------
//Тело модуля
//-----------
//Панель инструментов карточки показателя
const MarkCardToolbar = ({ title, desc, onOpen, onEdit, onDelete, onCnAdd, showButtons = false }) => {
return (
<Stack direction={"row"} alignItems={"center"} justifyContent={"space-between"} sx={STYLES.CONTAINER} title={desc}>
<Typography noWrap p={1} textAlign={"left"} variant={"subtitle1"} component={"div"} sx={COMMON_STYLES.FONT_MARK_CARD_HEAD}>
{showButtons ? (
<Link sx={COMMON_STYLES.LINK} onClick={onOpen}>
{title}
</Link>
) : (
title
)}
</Typography>
{showButtons && (
<Stack direction={"row"}>
<IconButton title={"Исправить показатель"} onClick={onEdit}>
<Icon>edit</Icon>
</IconButton>
<IconButton title={"Добавить элемент состава"} onClick={onCnAdd}>
<Icon>library_add</Icon>
</IconButton>
<IconButton title={"Удалить показатель"} onClick={onDelete}>
<Icon>delete</Icon>
</IconButton>
</Stack>
)}
</Stack>
);
};
//Контроль свойств - Панель инструментов карточки показателя
MarkCardToolbar.propTypes = {
title: PropTypes.string,
desc: PropTypes.string,
onOpen: PropTypes.func.isRequired,
onEdit: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onCnAdd: PropTypes.func.isRequired,
showButtons: PropTypes.bool
};
//----------------
//Интерфейс модуля
//----------------
export { MarkCardToolbar };

View File

@ -0,0 +1,67 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Состав показателя
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Table, TableBody, TableRow, TableCell, Link } from "@mui/material"; //Интерфейсные компоненты
import { STYLES as COMMON_STYLES } from "../common"; //Общие стили и константы
//---------
//Константы
//---------
//Стили
const STYLES = {
DATA_CELL_CN: {
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "pre",
padding: "0px",
maxWidth: "100px",
border: "none",
...COMMON_STYLES.FONT_MARK_CARD_BODY
}
};
//-----------
//Тело модуля
//-----------
//Состав показателя
const MarkCnList = ({ constitution, onMarkCnOpen }) => {
return (
<Table>
<TableBody>
{constitution.map((el, index) => {
return (
<TableRow key={index}>
<TableCell sx={STYLES.DATA_CELL_CN} title={el.SDESC} align={"left"} size={"small"}>
<Link sx={COMMON_STYLES.LINK} onClick={() => onMarkCnOpen && onMarkCnOpen(el.NRN)}>
{el.SDESC}
</Link>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
};
//Контроль свойств - Состав показателя
MarkCnList.propTypes = {
constitution: PropTypes.array.isRequired,
onMarkCnOpen: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { MarkCnList };

View File

@ -0,0 +1,182 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Показатели раздела
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
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_STYLES } from "../../../../app.styles"; //Типовые стили
import { confSctnMrkCellRender, confSctnMrkHeadCellRender } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
import { ActionMessage } from "./action_message"; //Сообщение с действиями
import { DialogMarkIU } from "./dialog_mark_iu"; //Диалог добавления/исправления показателя
import { DialogHelp } from "./dialog_help"; //Диалог помощи
import { DialogOrder } from "./dialog_order"; //Диалог сортировки
import { useDictionary } from "../hooks"; //Кастомные хуки
//---------
//Константы
//---------
//Стили
const STYLES = {
MARKS_DG_CONTAINER: {
position: "absolute",
top: 0,
bottom: 0,
width: "100%",
height: "100%",
overflow: "auto",
border: "unset",
...APP_STYLES.SCROLL
},
MARKS_DG_TABLE: { tableLayout: "fixed", width: "auto" }
};
//-----------
//Тело модуля
//-----------
//Показатели раздела
const Marks = ({ marks, order, marksLoading, marksInit, onRefresh, onMarkInsert, onMarkUpdate, onMarkDelete, onOrderChange }) => {
//Состояние - диалог сортировки
const [dialogOrder, setDialogOrder] = useState(false);
//Состояние - диалог помощи
const [dialogHelp, setDialogHelp] = useState(false);
//Состояние - Редактируемый показатель
const [modMark, setModMark] = useState(null);
//Подключение к контексту сообщений
const { showMsgWarn } = useContext(MessagingСtx);
//Подключение к словарям
const { showMark, showMarkCn, showMarkCnAdd } = useDictionary();
//Изменение состояния диалога информации
const toggleHelpDialog = () => setDialogHelp(pv => !pv);
//Изменение состояния диалога сортировки
const toggleOrderDialog = () => setDialogOrder(pv => !pv);
//При необходимости обновления
const handleRefresh = () => onRefresh && onRefresh();
//При вызове сортировки
const handleOrder = () => toggleOrderDialog();
//При вызове помощи
const handleHelp = () => toggleHelpDialog();
//Изменение состояния сортировки строк и граф
const handleOrderChange = order => {
onOrderChange && onOrderChange(order);
toggleOrderDialog();
};
//При добавлении показателя
const handleMarkAdd = () => setModMark(true);
//При добавлении показателя по указанным строке/графе
const handleMarkAddByRowCol = (row, column) => onMarkInsert({ row, column });
//При исправлении показателя
const handleMarkUpdate = markDesc => setModMark({ ...markDesc });
//При удалении показателя
const handleMarkDelete = mark => showMsgWarn("Удалить показатель?", () => onMarkDelete && onMarkDelete(mark));
//При переходе к показателю
const handleMarkOpen = mark => showMark(mark, res => res.success && handleRefresh());
//При добавлении состава показателя
const handleMarkCnAdd = mark => showMarkCnAdd(mark, res => res.success && handleRefresh());
//При переходе к составу показателя
const handleMarkCnOpen = (mark, constitution) => showMarkCn(mark, constitution, res => res.success && handleRefresh());
//При закрытии формы добавления/исправления по "ОК"
const handleIUFormOk = values => {
if (modMark === true) onMarkInsert && onMarkInsert(values, res => res && setModMark(null));
else onMarkUpdate && onMarkUpdate({ ...modMark, ...values }, res => res && setModMark(null));
};
//При закрытии формы добавления/исправления по "Отмена"
const handleIUFormCancel = () => setModMark(null);
//Формирование представления
return (
<>
{dialogOrder && <DialogOrder {...order} onOk={handleOrderChange} onCancel={toggleOrderDialog} />}
{dialogHelp && <DialogHelp onClose={toggleHelpDialog} />}
{modMark && (
<DialogMarkIU {...(modMark === true ? {} : modMark)} insert={modMark === true} onOk={handleIUFormOk} onCancel={handleIUFormCancel} />
)}
{marksInit &&
(marks ? (
<P8PDataGrid
{...P8P_DATA_GRID_CONFIG_PROPS}
{...marks}
tableStyle={STYLES.MARKS_DG_TABLE}
containerComponentProps={{ elevation: 0, square: true, variant: "outlined", sx: STYLES.MARKS_DG_CONTAINER }}
size={P8P_DATA_GRID_SIZE.SMALL}
dataCellRender={prms =>
confSctnMrkCellRender({
...prms,
onMarkAdd: handleMarkAddByRowCol,
onMarkUpdate: handleMarkUpdate,
onMarkDelete: handleMarkDelete,
onMarkOpen: handleMarkOpen,
onMarkCnOpen: handleMarkCnOpen,
onMarkCnAdd: handleMarkCnAdd
})
}
headCellRender={prms =>
confSctnMrkHeadCellRender({
...prms,
onAdd: handleMarkAdd,
onRefresh: handleRefresh,
onOrder: handleOrder,
onHelp: handleHelp
})
}
/>
) : (
!marksLoading && (
<ActionMessage icon={"info"} title={"В разделе нет показателей"} desc={"Добавьте новый"}>
<Button startIcon={<Icon>add</Icon>} onClick={handleMarkAdd}>
Показатель
</Button>
</ActionMessage>
)
))}
</>
);
};
//Контроль свойств - Показатели раздела
Marks.propTypes = {
marks: PropTypes.object,
order: PropTypes.object.isRequired,
marksLoading: PropTypes.bool.isRequired,
marksInit: PropTypes.bool.isRequired,
onRefresh: PropTypes.func,
onMarkInsert: PropTypes.func,
onMarkUpdate: PropTypes.func,
onMarkDelete: PropTypes.func,
onOrderChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { Marks };

View File

@ -0,0 +1,60 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Панель инструментов показателей
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Stack, Icon, IconButton } from "@mui/material"; //Интерфейсные компоненты
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { width: "100%" }
};
//-----------
//Тело модуля
//-----------
//Панель инструментов показателей
const MarksToolbar = ({ onAdd, onRefresh, onOrder, onHelp }) => {
//Формирование представления
return (
<Stack direction={"row"} alignItems={"center"} justifyContent={"center"} sx={STYLES.CONTAINER}>
<IconButton title={"Добавить показатель"} onClick={onAdd}>
<Icon>add</Icon>
</IconButton>
<IconButton title={"Обновить показатели"} onClick={onRefresh}>
<Icon>refresh</Icon>
</IconButton>
<IconButton title={"Порядок сортировки показателей"} onClick={onOrder}>
<Icon>sort</Icon>
</IconButton>
<IconButton title={"Легенда"} onClick={onHelp}>
<Icon>help</Icon>
</IconButton>
</Stack>
);
};
//Контроль свойств - Панель инструментов показателей
MarksToolbar.propTypes = {
onAdd: PropTypes.func.isRequired,
onRefresh: PropTypes.func.isRequired,
onOrder: PropTypes.func.isRequired,
onHelp: PropTypes.func.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { MarksToolbar };

View File

@ -1,359 +0,0 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Раздел настройки
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
Box,
IconButton,
Icon,
Dialog,
DialogTitle,
DialogContent,
Button,
Typography,
List,
ListItem,
Select,
FormControl,
InputLabel,
MenuItem,
DialogActions
} from "@mui/material"; //Интерфейсные элементы
import { 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"
},
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" }
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Элемент списка расшифровки состава
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 SortDialog = ({ init, handleOpenSortChange, onOrderChange }) => {
//Собственное состояние сортировки
const [order, setOrder] = useState({ row: init.rowOrder ? init.rowOrder : 0, column: init.columnOrder ? init.columnOrder : 0 });
//Изменеие сортировки
const handleOrderChange = e => setOrder(pv => ({ ...pv, [e.target.name]: e.target.value }));
//Нажатие на кнопку Ok
const handleOk = () => {
onOrderChange({ rowOrder: order.row, columnOrder: order.column });
handleOpenSortChange();
};
//Кнопка "Очистить", значения по умолчанию
const handleClear = () => {
setOrder({ row: 0, column: 0 });
};
//Кнопка "Отмена"
const handleCancel = () => {
handleOpenSortChange();
};
//Генерация содержимого
return (
<Dialog open onClose={handleOpenSortChange}>
<DialogTitle>
<Box display="flex" alignItems="center">
<Box flexGrow={1} textAlign="center">
Сортировка
</Box>
<Box>
<IconButton aria-label="close" onClick={handleCancel}>
<Icon>close</Icon>
</IconButton>
</Box>
</Box>
</DialogTitle>
<DialogContent>
<Box component="section" p={1}>
<FormControl variant="standard" fullWidth>
<InputLabel id="row-label">Строки</InputLabel>
<Select labelId="row-label" id="row" name="row" value={order.row} label="Строки" onChange={handleOrderChange}>
<MenuItem value={0}>Номер</MenuItem>
<MenuItem value={1}>Код</MenuItem>
<MenuItem value={2}>Мнемокод</MenuItem>
</Select>
</FormControl>
</Box>
<Box component="section" p={1}>
<FormControl variant="standard" fullWidth>
<InputLabel id="column-label">Графы</InputLabel>
<Select labelId="column-label" id="column" name="column" value={order.column} label="Графы" onChange={handleOrderChange}>
<MenuItem value={0}>Номер</MenuItem>
<MenuItem value={1}>Код</MenuItem>
<MenuItem value={2}>Мнемокод</MenuItem>
</Select>
</FormControl>
</Box>
</DialogContent>
<DialogActions sx={STYLES.DIALOG_ACTIONS}>
<Button variant="text" onClick={handleOk}>
ОК
</Button>
<Button variant="text" onClick={handleClear}>
Очистить
</Button>
<Button variant="text" onClick={handleCancel}>
Отмена
</Button>
</DialogActions>
</Dialog>
);
};
//Контроль свойств - Диалог сортировки
SortDialog.propTypes = {
init: PropTypes.object.isRequired,
handleOpenSortChange: PropTypes.func.isRequired,
onOrderChange: PropTypes.func.isRequired
};
//-----------
//Тело модуля
//-----------
//Раздел настройки
const SectionTab = ({
section,
tabValue,
index,
order,
onOrderChange,
containerProps,
handleMarkAdd,
handleReload,
handleMarkOpen,
handleMarkCnOpen,
handleMarkCnInsert,
menuItems
}) => {
//Состояние - диалог информации
const [openHelp, setOpenHelp] = useState(false);
//Изменение состояния диалога информации
const handleOpenHelpChange = () => {
setOpenHelp(!openHelp);
};
//Состояние - диалог сортировки
const [openSort, setOpenSort] = useState(false);
//Изменение состояния диалога сортировки
const handleOpenSortChange = () => {
setOpenSort(!openSort);
};
//Генерация содержимого
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={() => handleOpenSortChange()}>
<Icon>sort</Icon>
</IconButton>
<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>
{openSort ? <SortDialog init={order} handleOpenSortChange={handleOpenSortChange} onOrderChange={onOrderChange} /> : null}
{openHelp ? <HelpDialog handleOpenHelpChange={handleOpenHelpChange} /> : null}
</>
);
};
//Контроль свойств - Раздел настройки
SectionTab.propTypes = {
section: PropTypes.object.isRequired,
tabValue: PropTypes.number,
index: PropTypes.number,
order: PropTypes.object.isRequired,
onOrderChange: PropTypes.func.isRequired,
containerProps: PropTypes.object,
handleMarkAdd: PropTypes.func,
handleReload: PropTypes.func,
handleMarkOpen: PropTypes.func,
handleMarkCnOpen: PropTypes.func,
handleMarkCnInsert: PropTypes.func,
menuItems: PropTypes.array
};
//----------------
//Интерфейс модуля
//----------------
export { SectionTab };

View File

@ -0,0 +1,152 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Раздел настройки
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box } from "@mui/material"; //Интерфейсные элементы
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { useConfSectionMarks } from "../hooks"; //Кастомные хуки
import { ActionMessage } from "./action_message"; //Сообщение с действиями
import { Marks } from "./marks"; //Показатели раздела
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { width: "100%", height: "100%" },
BOX_MARKS: { position: "relative", display: "flex", height: "100%", width: "100%" }
};
//-----------
//Тело модуля
//-----------
//Раздел настройки
const Section = ({ section = null }) => {
//Состояние сортировки строк и граф
const [order, setOrder] = useState({ rowOrder: 0, columnOrder: 0 });
//Состояние - флаг "сокрытия" (на самом деле - отмонтирования) компонента с показателями
const [hideMarksThenLoading, setHideMarksThenLoading] = useState(false);
//Данные раздела (показатели)
const [marks, refreshMarks, marksLoading, marksInit] = useConfSectionMarks(section, order.rowOrder, order.columnOrder);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Добавление показателя
const insertMark = async ({ code, name, row, rowCode, rowVersion, column, columnCode, columnVersion }, cb) => {
try {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_INSERT",
args: {
NPRN: section,
SCODE: code,
SNAME: name,
NRRPROW: row,
SRRPROW: rowCode,
SRRPVERSION_ROW: rowVersion,
NRRPCOLUMN: column,
SRRPCOLUMN: columnCode,
SRRPVERSION_COLUMN: columnVersion
},
loader: false
});
cb && cb(true);
refreshMarks();
} catch {
cb && cb(false);
}
};
//Исправление показателя
const updateMark = async ({ rn, code, name, rowCode, rowVersion, columnCode, columnVersion }, cb) => {
try {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_UPDATE",
args: {
NRN: rn,
SCODE: code,
SNAME: name,
SRRPROW: rowCode,
SRRPVERSION_ROW: rowVersion,
SRRPCOLUMN: columnCode,
SRRPVERSION_COLUMN: columnVersion
},
loader: false
});
cb && cb(true);
refreshMarks();
} catch {
cb && cb(false);
}
};
//Удаление показателя
const deleteMark = async mark => {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTNMRK_DELETE",
args: { NRN: mark },
loader: false
});
refreshMarks();
};
//Изменение сортировки
const changeOrder = order => setOrder(order);
//Сброс ранее выставленного флага "сокрытия" (отмонтирования) компонента с показателями (так он не "мигает", когда происходит не смена раздела, в работа внутри него с показателями)
useEffect(() => {
if (hideMarksThenLoading) setHideMarksThenLoading(false);
}, [hideMarksThenLoading]);
//При смене раздела - выставим флаг "спрятать" (отмонтировать) компонент с показателями (так он корректно полностью обновляется)
useEffect(() => {
if (section) setHideMarksThenLoading(true);
}, [section]);
//Формирование представления
return (
<Box sx={STYLES.CONTAINER}>
{section ? (
<Box sx={STYLES.BOX_MARKS}>
{((hideMarksThenLoading && !marksLoading) || !hideMarksThenLoading) && (
<Marks
marks={marks}
order={order}
marksLoading={marksLoading}
marksInit={marksInit}
onRefresh={refreshMarks}
onMarkInsert={insertMark}
onMarkUpdate={updateMark}
onMarkDelete={deleteMark}
onOrderChange={changeOrder}
/>
)}
</Box>
) : (
<ActionMessage icon={"info"} title={"Раздел настройки не выбран"} desc={"Укажите его, выбрав закладку сверху"} />
)}
</Box>
);
};
//Контроль свойств - Раздел настройки
Section.propTypes = {
section: PropTypes.number
};
//----------------
//Интерфейс модуля
//----------------
export { Section };

View File

@ -0,0 +1,89 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Закладка раздела
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Tab, IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты
import { STYLES as COMMON_STYLES } from "../common"; //Общие стили и константы
//---------
//Константы
//---------
//Стили
const STYLES = {
SECTION_TAB: { minWidth: "150px" },
SECTION_TAB_LABEL: { width: "100%", height: "100%" },
SECTION_TAB_TOOLBAR_STACK: { ...COMMON_STYLES.TOOLBAR, height: "100%", width: "100%" }
};
//-----------
//Тело модуля
//-----------
//Закладка раздела
const SectionTab = ({ value = false, section, sectionDesc, onSectionEdit, onSectionDelete, ...other }) => {
//Флаг нахождения указателя мыши в закладке
const [hoveredSection, setHoveredSection] = useState(false);
//При попадании мыши на закладку раздела
const handleSectionTabMouseIn = () => setHoveredSection(true);
//При выходе мыши из закладки раздела
const handleSectionTabMouseOut = () => setHoveredSection(false);
//При редактировании раздела настройки
const handleSectionEdit = () => onSectionEdit && onSectionEdit(sectionDesc.NRN);
//При удалении раздела настройки
const handleSectionDelete = () => onSectionDelete && onSectionDelete(sectionDesc.NRN);
//Формирование представления
return (
<Tab
component={"div"}
wrapped
value={value}
onMouseEnter={handleSectionTabMouseIn}
onMouseLeave={handleSectionTabMouseOut}
sx={STYLES.SECTION_TAB}
label={
<Stack direction={"row"} alignItems={"center"} textAlign={"left"} sx={STYLES.SECTION_TAB_LABEL} title={sectionDesc.SNAME}>
{`${sectionDesc.SCODE} - ${sectionDesc.SNAME_SHORT}`}
{section === sectionDesc.NRN && hoveredSection && (
<Stack direction={"row"} alignItems={"center"} justifyContent={"right"} sx={STYLES.SECTION_TAB_TOOLBAR_STACK} p={1}>
<IconButton onClick={handleSectionEdit} title={"Редактировать раздел"}>
<Icon>edit</Icon>
</IconButton>
<IconButton disabled={sectionDesc.NDELETE_ALLOW === 0} onClick={handleSectionDelete} title={"Удалить раздел"}>
<Icon>delete</Icon>
</IconButton>
</Stack>
)}
</Stack>
}
{...other}
/>
);
};
//Контроль свойств - Закладка раздела
SectionTab.propTypes = {
value: PropTypes.any,
section: PropTypes.number,
sectionDesc: PropTypes.object.isRequired,
onSectionEdit: PropTypes.func,
onSectionDelete: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { SectionTab };

View File

@ -1,50 +0,0 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Панель мониторинга: Компонент вкладки раздела
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box } from "@mui/material"; //Интерфейсные компоненты
//---------
//Константы
//---------
const STYLES = {
SECTION_INFO: {
padding: "24px 5px 0px 5px"
}
};
//---------------
//Тело компонента
//---------------
const SectionTabPanel = props => {
const { children, value, index, ...other } = props;
//Генерация содержимого
return (
<div role="tabpanel" hidden={value !== index} id={`tabpanel-${index}`} aria-labelledby={`tab-${index}`} {...other}>
{value === index && <Box sx={STYLES.SECTION_INFO}>{children}</Box>}
</div>
);
};
//Контроль свойств - Вкладка раздела
SectionTabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired
};
//--------------------
//Интерфейс компонента
//--------------------
export { SectionTabPanel };

View File

@ -0,0 +1,207 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Компонент панели: Разделы настройки
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useEffect, useContext, useCallback } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Tabs, IconButton, Icon, Stack, Button } from "@mui/material"; //Интерфейсные компоненты
import { tabsClasses } from "@mui/material/Tabs"; //Классы закладок
import { ApplicationСtx } from "../../../context/application"; //Контекст взаимодействия с приложением
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений
import { useConfSections } from "../hooks"; //Кастомные хуки
import { ActionMessage } from "./action_message"; //Сообщение с действиями
import { SectionTab } from "./section_tab"; //Закладка раздела
import { DialogSectionIU } from "./dialog_section_iu"; //Диалог добавления/исправления раздела
//---------
//Константы
//---------
//Стили
const STYLES = {
CONTAINER: { borderBottom: 1, borderColor: "divider", width: "100%", height: "100%" },
TABS_SECTIONS: { width: "100%", [`& .${tabsClasses.scrollButtons}`]: { "&.Mui-disabled": { opacity: 0.3 } } }
};
//-----------------------
//Вспомогательные функции
//-----------------------
//Поиск активного раздела после удаления текущего
const getNextSectionAfterDelete = (sections, deletedSection) => {
//Находим индекс удаляемого раздела
const delInd = sections.findIndex(s => s.NRN === deletedSection);
//Возвращаем рег. номер либо предыдущего раздела, либо следующего, либо ничего
return delInd === -1 ? null : sections[delInd - 1]?.NRN || sections[delInd + 1]?.NRN || null;
};
//-----------
//Тело модуля
//-----------
//Разделы настройки
const Sections = ({ conf, onSectionChange, onSectionCountChange }) => {
//Текущий раздел настройки
const [section, setSection] = useState(-1);
//Редактируемый раздел настройки
const [modSection, setModSection] = useState(null);
//Список разделов и просто описание настройки
const [confDesc, sections, refreshSections, sectionsLoading, sectionsInit] = useConfSections(conf);
//Подключение к контексту приложения
const { setAppBarTitle } = useContext(ApplicationСtx);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Подключение к контексту сообщений
const { showMsgWarn } = useContext(MessagingСtx);
//Выбор раздела
const selectSection = useCallback(
section => {
if (onSectionChange) onSectionChange(section);
setSection(section);
},
[onSectionChange]
);
//Добавление раздела
const insertSection = async ({ conf, code, name }) => {
const data = await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTN_INSERT",
args: { NPRN: conf, SCODE: code, SNAME: name },
loader: false
});
selectSection(data.NRN);
refreshSections();
};
//Исправление раздела
const updateSection = async ({ rn, code, name }) => {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTN_UPDATE",
args: { NRN: rn, SCODE: code, SNAME: name },
loader: false
});
refreshSections();
};
//Удаление раздела
const deleteSection = async section => {
await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTN_DELETE",
args: { NRN: section },
loader: false
});
selectSection(getNextSectionAfterDelete(sections, section));
refreshSections();
};
//При измении закладки текущего раздела
const handleSectionTabChange = (event, section) => selectSection(section);
//При добавлении раздела настройки
const handleSectionAdd = () => setModSection(true);
//При редактировании раздела настройки
const handleSectionEdit = section => setModSection(sections.find(s => s.NRN === section) || null);
//При удалении раздела настройки
const handleSectionDelete = section => showMsgWarn("Удалить раздел?", () => deleteSection(section));
//При закрытии формы добавления/исправления по "ОК"
const handleIUFormOk = async values => {
if (modSection === true) await insertSection({ conf, ...values });
else await updateSection({ rn: modSection.NRN, ...values });
setModSection(null);
};
//При закрытии формы добавления/исправления по "Отмена"
const handleIUFormCancel = () => setModSection(null);
//При изменении состава разделов
useEffect(() => {
//Если ещё не инициализировали выбранный раздел и есть чем
if (section === -1 && sections.length > 0) selectSection(sections[0].NRN);
}, [section, sections, selectSection]);
//При изменении количества разделов
useEffect(() => {
onSectionCountChange && onSectionCountChange(sections.length);
}, [sections.length, onSectionCountChange]);
//При изменении описания раздела
useEffect(() => {
if (confDesc?.SNAME) setAppBarTitle(confDesc.SNAME);
}, [confDesc, setAppBarTitle]);
//Вычисление подсвеченной закладки раздела
const hlSection = sections.find(s => s.NRN === section)?.NRN || false;
//Формирование представления
return (
<Stack direction={"row"} sx={STYLES.CONTAINER}>
{modSection && (
<DialogSectionIU
code={modSection?.SCODE}
name={modSection?.SNAME}
insert={modSection === true}
onOk={handleIUFormOk}
onCancel={handleIUFormCancel}
/>
)}
{sections.length > 0 ? (
<>
<Box display={"flex"} justifyContent={"center"} alignItems={"center"} sx={STYLES.PANELS_MAIN_COLOR} title={"Добавить раздел"}>
<IconButton onClick={handleSectionAdd}>
<Icon>add</Icon>
</IconButton>
</Box>
<Tabs value={hlSection} onChange={handleSectionTabChange} variant={"scrollable"} scrollButtons sx={STYLES.TABS_SECTIONS}>
{sections.map((s, i) => (
<SectionTab
key={i}
value={s.NRN}
section={section}
sectionDesc={s}
onSectionEdit={handleSectionEdit}
onSectionDelete={handleSectionDelete}
/>
))}
</Tabs>
</>
) : (
sectionsInit &&
!sectionsLoading && (
<ActionMessage icon={"info"} title={"В настройке нет разделов"} desc={"Добавьте первый..."}>
<Button startIcon={<Icon>add</Icon>} onClick={handleSectionAdd}>
Раздел
</Button>
</ActionMessage>
)
)}
</Stack>
);
};
//Контроль свойств - Разделы настройки
Sections.propTypes = {
conf: PropTypes.number,
onSectionChange: PropTypes.func,
onSectionCountChange: PropTypes.func
};
//----------------
//Интерфейс модуля
//----------------
export { Sections };

View File

@ -7,403 +7,209 @@
//Подключение библиотек
//---------------------
import { useState, useContext, useEffect, useCallback, useLayoutEffect } from "react"; //Классы React
import { useState, useContext, useEffect } from "react"; //Классы React
import { xml2JSON } from "../../core/utils"; //Вспомогательные функции
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"; //Тексты для ошибок
//-----------
//Тело модуля
//-----------
//Хук для отработки изменений ширины и высоты рабочей области окна
const useWindowResize = () => {
//Состояние размера рабочей области
const [size, setSize] = useState([0, 0]);
//Получение данных о разделах настройки РО
const useConfSections = conf => {
//Собственное состояние - флаг инициализированности
const [isInit, setInit] = useState(false);
//При изменении размера
useLayoutEffect(() => {
const updateSize = () => {
setSize([document.documentElement.clientWidth, document.documentElement.clientHeight]);
};
window.addEventListener("resize", updateSize);
updateSize();
return () => window.removeEventListener("resize", updateSize);
}, []);
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Вернём размеры
return size;
};
//Собственное состояние - флаг необходимости обновления
const [refresh, setRefresh] = useState(true);
//Хук для настройки регламентированного отчета
const useConf = (currentTab, handleSectionChange, order) => {
//Собственное состояние - таблица данных
const dataGrid = {
rn: 0,
code: "",
name: "",
dataLoaded: false,
columnsDef: [],
groups: [],
rows: [],
fixedHeader: false,
fixedColumns: 0,
reload: false
};
//Собственное состояние - данные настройки
const [dataConf, setDataConf] = useState(null);
//Собственное состояние
const [rrpConf, setRrpConf] = useState({
docLoaded: false,
sections: [],
orderChanged: false,
reload: true
});
//Состояние массива данных разделов
const [dataGrids] = useState([]);
//Собственное состояние - данные разделов
const [dataSections, setDataSections] = useState([]);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Подключение к контексту навигации
const { getNavigationSearch } = useContext(NavigationCtx);
//Обновление данных
const doRefresh = () => setRefresh(true);
//При необходимости обновить
const handleReload = useCallback(async () => {
setRrpConf(pv => ({ ...pv, reload: true }));
}, []);
//Загрузка данных разделов регламентированного отчёта
const loadData = useCallback(
async () => {
if (rrpConf.reload) {
//Переменная номера раздела с фокусом
let tabFocus = currentTab ? currentTab : 0;
//При необходимости получить/обновить данные
useEffect(() => {
//Загрузка данных с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONF_GET_SECTIONS",
args: {
NRN_RRPCONF: Number(getNavigationSearch().NRN),
NROW_ORDER: Number(order.rowOrder),
NCOL_ORDER: Number(order.columnOrder)
},
stored: "PKG_P8PANELS_RRPCONFED.RRPCONF_GET",
args: { NRRPCONF: conf },
respArg: "COUT",
isArray: name => name === "XSECTIONS",
attributeValueProcessor: (name, val) => (name.startsWith("S") ? undefined : val),
loader: false
});
setDataConf(data?.XCONF || null);
setDataSections(data?.XSECTIONS || []);
setInit(true);
} finally {
setRefresh(false);
setLoading(false);
}
};
//Если надо обновить
if (refresh)
if (conf)
//Если есть для чего получать данные
loadData();
//Нет идентификатора настройки - нет данных
else {
setDataConf(null);
setDataSections([]);
}
}, [refresh, conf, executeStored]);
//При изменении входных свойств - поднимаем флаг обновления
useEffect(() => setRefresh(true), [conf]);
//Возвращаем интерфейс хука
return [dataConf, dataSections, doRefresh, isLoading, isInit];
};
//Получение данных о показателях раздела настройки РО
const useConfSectionMarks = (section, rowOrder = 0, columnOrder = 0) => {
//Собственное состояние - флаг инициализированности
const [isInit, setInit] = useState(false);
//Собственное состояние - флаг загрузки
const [isLoading, setLoading] = useState(false);
//Собственное состояние - флаг необходимости обновления
const [refresh, setRefresh] = useState(true);
//Собственное состояние - данные
const [data, setData] = useState(null);
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Обновление данных
const doRefresh = () => setRefresh(true);
//При необходимости получить/обновить данные
useEffect(() => {
//Загрузка данных с сервера
const loadData = async () => {
try {
setLoading(true);
const data = await executeStored({
stored: "PKG_P8PANELS_RRPCONFED.RRPCONFSCTN_GET",
args: { NRRPCONFSCTN: section, NROW_ORDER: rowOrder, NCOL_ORDER: columnOrder },
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
.sort((a, b) => a.SCODE - b.SCODE)
.map(s => {
let dg = {};
Object.assign(dg, dataGrid, {
...s.XDATA.XDATA_GRID,
rn: s.NRN,
code: s.SCODE,
name: s.SNAME,
delete_allow: s.NDELETE_ALLOW,
dataLoaded: true,
columnsDef: [...(s.XDATA.XDATA_GRID.columnsDef || [])],
groups: [...(s.XDATA.XDATA_GRID.groups || [])],
rows: [...(s.XDATA.XDATA_GRID.rows || [])],
reload: false
});
//Если раздел имеет составы показателей
if (s.MARK_CNS.MARK_CN) {
//Обходим строки раздела
dg.rows.map(row => {
//Цикл по ключам строки
for (let key in row) {
//Если это ключ для группы составов показателей
if (key.match(/MARK_CNS_.*/)) {
//Считываем рег. номер показателя
let markRn = key.substring(9);
//Переносим из раздела
row[key] = Array.isArray(s.MARK_CNS.MARK_CN)
? [...s.MARK_CNS.MARK_CN].filter(el => el.NPRN === row[`NMARK_RN_${markRn}`])
: s.MARK_CNS.MARK_CN.NPRN === row[`NMARK_RN_${markRn}`]
? [s.MARK_CNS.MARK_CN]
: null;
}
}
});
}
//Ищем загружен ли уже раздел с таким же ид.
const dgItem = dataGrids.find(x => x.rn === dg.rn);
//Его индекс, если нет соответствия, то -1
let index = dataGrids.indexOf(dgItem);
//Если было соответствие
if (dgItem) {
//Если в нём не найдено изменений
if (JSON.stringify(dgItem, null, 4) === JSON.stringify(dg, null, 4)) {
//То из копированного массива его удаляем
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dgItem.rn)), 1);
} else {
//Иначе обновляем раздел в массиве
dataGrids[index] = dg;
//Удаляем из копированного массива
cloneDGs.splice(cloneDGs.indexOf(cloneDGs.find(x => x.rn === dg.rn)), 1);
//Устанавливаем фокус на обновлённый раздел, если был добавлен
tabFocus = rrpConf.orderChanged ? 0 : index;
}
} else {
//Если раздел новый, то добавляем его в массив данных
dataGrids.push(dg);
//И устанавливаем на него фокус, если флаг первой загрузки = false
tabFocus = !firstLoad ? dataGrids.length - 1 : 0;
}
});
//Обходим разделы, что остались в копированном массиве (на удаление)
cloneDGs.map(s => {
let curIndex = dataGrids.indexOf(dataGrids.find(x => x.rn === s.rn));
//Устаревший раздел удаляем из массива данных
dataGrids.splice(curIndex, 1);
//Фокус на предшествующий раздел
if (curIndex > 0) tabFocus = curIndex - 1;
//Иначе фокус на следующий, если был удалён первый раздел
else tabFocus = curIndex;
});
setRrpConf(pv => ({
...pv,
docLoaded: true,
orderChanged: false,
reload: false,
sections: dataGrids
}));
handleSectionChange(tabFocus);
if (data) {
for (let i = 0; i < data?.XDATA_GRID?.rows?.length; i++)
for (const key of Object.keys(data.XDATA_GRID.rows[i]))
if (key.startsWith("SCOL_"))
data.XDATA_GRID.rows[i][key] = (
await xml2JSON({
xmlDoc: data.XDATA_GRID.rows[i][key],
isArray: name => name === "CONSTITUTION",
attributeValueProcessor: (name, val) => (name.startsWith("S") ? undefined : val)
})
).XMARK;
setData(data?.XDATA_GRID || null);
} else setData(null);
setInit(true);
} finally {
setRefresh(false);
setLoading(false);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[rrpConf.reload, rrpConf.docLoaded, dataGrid.reload, dataGrid.docLoaded, executeStored]
);
};
//Если надо обновить
if (refresh)
if (section)
//Если есть для чего получать данные
loadData();
//Нет идентификатора раздела настройки - нет данных
else setData(null);
}, [refresh, section, rowOrder, columnOrder, executeStored]);
//При изменении сортировок
useEffect(() => {
setRrpConf(pv => ({ ...pv, orderChanged: true, reload: true }));
}, [order]);
//При изменении входных свойств - поднимаем флаг обновления
useEffect(() => setRefresh(true), [section, rowOrder, columnOrder]);
//При необходимости обновить данные таблицы
useEffect(() => {
loadData();
}, [rrpConf.reload, dataGrid.reload, loadData]);
//При изменении раздела - сбрасываем флаг инициализированности (так карточки показателей грузятся красивее - таблица исчезает и появляется уже загруженная)
useEffect(() => setInit(false), [section]);
return [rrpConf, handleReload];
//Возвращаем интерфейс хука
return [data, doRefresh, isLoading, isInit];
};
//Хук для вкладки
const useTab = () => {
//Состояние раздела
const [tabValue, setTabValue] = useState("");
//Переключение раздела
const handleSectionChange = useCallback(newValue => {
setTabValue(newValue);
}, []);
return [tabValue, handleSectionChange];
};
//Хук для функций открытия записей
const useRecOpen = handleReload => {
//Подключение к контексту взаимодействия с сервером
const { executeStored } = useContext(BackEndСtx);
//Работа со словарями
const useDictionary = () => {
//Подключение к контексту приложения
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 selectRRPRow = (code, version, callBack) => {
pOnlineShowDictionary({
unitCode: "RRPRow",
inputParameters: [
{ name: "in_CODE", value: code },
{ name: "in_RRPVERSION_CODE", value: version }
],
callBack: res =>
callBack(res.success === true ? { code: res.outParameters.out_CODE, version: res.outParameters.out_RRPVERSION_CODE } : 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
//Выбор графы
const selectRRPColumn = (code, version, callBack) => {
pOnlineShowDictionary({
unitCode: "RRPColumn",
inputParameters: [
{ name: "in_CODE", value: code },
{ name: "in_RRPVERSION_CODE", value: version }
],
callBack: res =>
callBack(res.success === true ? { code: res.outParameters.out_CODE, version: res.outParameters.out_RRPVERSION_CODE } : null)
});
openForm();
};
//Отработка нажатия на кнопку исправления показателя раздела
const handleMarkEdit = rn => {
setFormData({ reload: true, rn: rn, status: STATUSES.RRPCONFSCTNMRK_EDIT });
openForm();
};
//Отображение показателя раздела
const showMark = (mark, callBack) => showMarkCn(mark, null, callBack);
//Отработка нажатия на кнопку удаления показателя раздела
const handleMarkDelete = rn => {
setFormData({ rn: rn, status: STATUSES.RRPCONFSCTNMRK_DELETE });
openForm();
};
//Отображение состава показателя раздела
const showMarkCn = (mark, constitution, callBack) =>
pOnlineShowDictionary({
unitCode: "RRPConfigSectionMark",
showMethod: "link_cn",
inputParameters: [
{ name: "in_RN", value: mark },
{ name: "in_RRPCONFSCTNMRKCN", value: constitution }
],
callBack
});
//При закрытии диалога
const handleDialogClose = () => {
setForm(false);
clearFormData();
};
//Отображение состава показателя раздела с добавлением
const showMarkCnAdd = (mark, callBack) =>
pOnlineShowDictionary({
unitCode: "RRPConfigSectionMarkConstitution",
showMethod: "link_add",
inputParameters: [{ name: "in_PRN", value: mark }],
callBack
});
return [
formOpen,
formData,
handleSectionAdd,
handleSectionEdit,
handleSectionDelete,
handleMarkAdd,
handleMarkEdit,
handleMarkDelete,
handleDialogClose
];
};
//Формирование разделов
const a11yProps = index => {
return {
id: `simple-tab-${index}`,
"aria-controls": `simple-tabpanel-${index}`
};
//Возвращаем интерфейс хука
return { selectRRPRow, selectRRPColumn, showMark, showMarkCn, showMarkCnAdd };
};
//----------------
//Интерфейс модуля
//----------------
export { useWindowResize, useConf, useTab, useRecOpen, useFormDialog, a11yProps };
export { useConfSections, useConfSectionMarks, useDictionary };

View File

@ -0,0 +1,91 @@
/*
Парус 8 - Панели мониторинга - РО - Редактор настройки регламентированного отчёта
Дополнительная разметка и вёрстка клиентских элементов
*/
//---------------------
//Подключение библиотек
//---------------------
import React from "react"; //Классы React
import { MarksToolbar } from "./components/marks_toolbar"; //Панель инструментов показателей
import { MarkCard } from "./components/mark_card"; //Карточка показателя
import { STYLES as COMMON_STYLES } from "./common"; //Общие стили и константы
//---------
//Константы
//---------
//Стили
export const STYLES = {
HEAD_CELL: {
textAlign: "center",
fontWeight: "bold",
lineHeight: "unset",
...COMMON_STYLES.FONT_DATA_GRID,
...COMMON_STYLES.BG_DATA_GRID_HEAD_CELL,
...COMMON_STYLES.BORDER_DATA_GRID_HEAD_CELL
},
HEAD_CELL_STACK: { justifyContent: "center" },
DATA_CELL: isMarkRowHead => ({
padding: "5px 5px",
textAlign: "center",
...COMMON_STYLES.FONT_DATA_GRID,
...(isMarkRowHead
? { ...COMMON_STYLES.BG_DATA_GRID_DATA_CELL, ...COMMON_STYLES.BORDER_DATA_GRID_HEAD_CELL, fontWeight: "bold" }
: COMMON_STYLES.BORDER_DATA_GRID_DATA_CELL)
})
};
//-----------
//Тело модуля
//-----------
//Генерация представления ячейки c данными показателя раздела регламентированного отчета
export const confSctnMrkCellRender = ({ row, columnDef, onMarkAdd, onMarkUpdate, onMarkDelete, onMarkOpen, onMarkCnOpen, onMarkCnAdd }) => {
//Считываем информацию о показателе
const mark = {
rn: row[columnDef.name]?.NRN,
code: row[columnDef.name]?.SCODE,
name: row[columnDef.name]?.SNAME,
row: row[columnDef.name]?.NRRPROW,
rowCode: row[columnDef.name]?.SRRPROW,
rowVersion: row[columnDef.name]?.SRRPVERSION_ROW,
column: row[columnDef.name]?.NRRPCOLUMN,
columnCode: row[columnDef.name]?.SRRPCOLUMN,
columnVersion: row[columnDef.name]?.SRRPVERSION_COLUMN,
constitution: row[columnDef.name]?.CONSTITUTION
};
//Вернём представление ячейки
return {
cellStyle: STYLES.DATA_CELL(columnDef.name === "SROW_NAME"),
data: columnDef.name != "SROW_NAME" && (
<MarkCard
mark={mark.rn}
code={mark.code}
name={mark.name}
constitution={mark.constitution}
onMarkAdd={() => onMarkAdd && onMarkAdd(mark.row, mark.column)}
onMarkUpdate={() => onMarkUpdate && onMarkUpdate({ ...mark })}
onMarkDelete={() => onMarkDelete && onMarkDelete(mark.rn)}
onMarkOpen={() => onMarkOpen && onMarkOpen(mark.rn)}
onMarkCnOpen={constitutionRn => onMarkCnOpen && onMarkCnOpen(mark.rn, constitutionRn)}
onMarkCnAdd={() => onMarkCnAdd && onMarkCnAdd(mark.rn)}
/>
)
};
};
//Генерация представления ячейки заголовка таблицы показателей
export const confSctnMrkHeadCellRender = ({ columnDef, onAdd, onRefresh, onOrder, onHelp }) => {
return {
cellStyle: STYLES.HEAD_CELL,
stackStyle: STYLES.HEAD_CELL_STACK,
data:
columnDef.name === "SROW_NAME" ? (
<MarksToolbar onAdd={onAdd} onRefresh={onRefresh} onOrder={onOrder} onHelp={onHelp} />
) : (
columnDef.caption
)
};
};

View File

@ -7,32 +7,23 @@
//Подключение библиотек
//---------------------
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"; //Компонент раздела настройки
import React, { useState, useEffect, useContext } from "react"; //Классы React
import { Grid } from "@mui/material"; //Интерфейсные компоненты
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
import { Sections } from "./components/sections"; //Список разделов настройки
import { Section } from "./components/section"; //Раздел настройки
//---------
//Константы
//---------
//Высота меню Парус (пиксели)
const pxOuterMenuH = 53;
//Высота заголовка панели (пиксели)
const pxPanelHeaderH = 64;
//Ширина кнопки добавления раздела (пиксели)
const pxSectionAddButtonW = 40;
//Стили
const STYLES = {
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 }),
SECTION_ACTIONS: { display: "flex", justifyContent: "center", alignItems: "center" }
GRID_MAIN_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})` },
GRID_MAIN_SECTIONS_ITEM: fullHeight => ({ ...(fullHeight ? { height: "100%" } : {}) }),
GRID_SECTIONS_CONTAINER: { width: "100vw", height: "100%" },
GRID_SECTION_CONTAINER: { height: "100%" }
};
//-----------
@ -41,122 +32,46 @@ const STYLES = {
//Редактор настройки регламентированного отчёта
const RrpConfEditor = () => {
//Состояние вкладки
const [tabValue, handleSectionChange] = useTab("");
//Редактируемая настройка
const [conf, setConf] = useState(null);
//Состояние сортировки строк и граф
const [order, setOrder] = useState({ rowOrder: 0, columnOrder: 0 });
//Текущий раздел настройки
const [section, setSection] = useState(null);
//Изменение состояния сортировки строк и граф
const handleOrder = newOrder => setOrder(newOrder);
//Текущее количество разделов настройки
const [sectionsCount, setSectionsCount] = useState(0);
//Состояние настройки
const [rrpConf, handleReload] = useConf(tabValue, handleSectionChange, order);
//Подключение к контексту навигации
const { getNavigationSearch } = useContext(NavigationCtx);
//Функции открытия разделов
const [handleMarkOpen, handleMarkCnOpen, handleMarkCnInsert] = useRecOpen(handleReload);
//Изменение текущего раздела настройки
const handleSectionChange = section => setSection(section);
//Состояние форм диалога
const [
formOpen,
formData,
handleSectionAdd,
handleSectionEdit,
handleSectionDelete,
handleMarkAdd,
handleMarkEdit,
handleMarkDelete,
handleDialogClose
] = useFormDialog();
//Изменение количества разделов настройки
const handleSectionsCountChange = sectionsCount => setSectionsCount(sectionsCount);
//Состояние ширины и высоты рабочей области окна
const [width, height] = useWindowResize();
//Состояние высоты вкладок с разделами
const [pxTabsH, setPxTabsH] = useState(0);
//При рендере данных
//При подключении к странице
useEffect(() => {
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 }
];
setConf(Number(getNavigationSearch().NRN));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Генерация содержимого
return (
<Box sx={STYLES.CONTAINER}>
{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={(event, newValue) => handleSectionChange(newValue)}
variant="scrollable"
scrollButtons={false}
visibleScrollbar
aria-label="section tab"
sx={STYLES.TABS_SIZES(width, pxSectionAddButtonW)}
>
{rrpConf.sections.map((s, i) => {
return (
<Tab
key={s.rn}
{...a11yProps(i)}
sx={STYLES.TABS_PADDING}
label={
<Box sx={STYLES.SECTION_ACTIONS}>
{s.name}
<IconButton component="span" onClick={() => handleSectionEdit(s.rn, s.code, s.name)}>
<Icon>edit</Icon>
</IconButton>
<IconButton
disabled={s.delete_allow === 0}
component="span"
onClick={() => handleSectionDelete(s.rn, s.code, s.name)}
>
<Icon>delete</Icon>
</IconButton>
</Box>
}
wrapped
/>
);
})}
</Tabs>
<Box display="flex" justifyContent="center" alignItems="center" sx={STYLES.PANELS_MAIN_COLOR}>
<IconButton onClick={handleSectionAdd}>
<Icon sx={STYLES.ICON_WHITE}>add</Icon>
</IconButton>
</Box>
</Stack>
{rrpConf.sections.map((s, i) => {
return (
<SectionTab
key={s.rn}
section={s}
tabValue={tabValue}
index={i}
order={order}
onOrderChange={handleOrder}
containerProps={{ height, pxOuterMenuH, pxPanelHeaderH, pxTabsH }}
handleReload={handleReload}
handleMarkOpen={handleMarkOpen}
handleMarkAdd={handleMarkAdd}
handleMarkCnOpen={handleMarkCnOpen}
handleMarkCnInsert={handleMarkCnInsert}
menuItems={markMenuItems}
/>
);
})}
</Box>
) : null}
</Box>
<Grid container direction={"column"} sx={STYLES.GRID_MAIN_CONTAINER}>
<Grid item sx={STYLES.GRID_MAIN_SECTIONS_ITEM(sectionsCount === 0)}>
<Grid container sx={STYLES.GRID_SECTIONS_CONTAINER}>
<Sections conf={conf} onSectionChange={handleSectionChange} onSectionCountChange={handleSectionsCountChange} />
</Grid>
</Grid>
{sectionsCount > 0 && (
<Grid item xs>
<Grid container direction={"row"} sx={STYLES.GRID_SECTION_CONTAINER}>
<Section section={section} />
</Grid>
</Grid>
)}
</Grid>
);
};

File diff suppressed because it is too large Load Diff