/*
Парус 8 - Панели мониторинга - УДП - Доски задач
Компонент панели: Диалог формы события
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useState, useContext, useEffect } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Typography, TextField, Dialog, DialogContent, DialogActions, Button, Tabs, Tab, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные компоненты
import { useClientEvent, useDocsProps } from "../hooks"; //Вспомогательные хуки
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
import dayjs from "dayjs"; //Работа с датами
import customParseFormat from "dayjs/plugin/customParseFormat"; //Настройка пользовательского формата даты
//---------
//Константы
//---------
//Перечисление "Значение по умолчанию"
const DP_DEFAULT_VALUE = Object.freeze({ 0: "defaultStr", 1: "defaultNum", 2: "defaultDate", 3: "defaultNum" });
//Перечисление "Префикс формата данных"
const DP_TYPE_PREFIX = Object.freeze({ 0: "S", 1: "N", 2: "D", 3: "N" });
//Перечисление "Входящее значение дополнительного словаря"
const DP_IN_VALUE = Object.freeze({ 0: "pos_str_value", 1: "pos_num_value", 2: "pos_date_value", 3: "pos_num_value" });
//Перечисление "Исходящее значение дополнительного словаря"
const DP_RETURN_VALUE = Object.freeze({ 0: "str_value", 1: "num_value", 2: "date_value", 3: "num_value" });
//Стили
const STYLES = {
CONTAINER: { margin: "5px 0px", textAlign: "center" },
DIALOG_CONTENT: {
paddingBottom: "0px",
maxHeight: "740px",
minHeight: "740px",
...APP_STYLES.SCROLL
},
DIALOG_ACTIONS: { justifyContent: "end", paddingRight: "24px", paddingLeft: "24px" },
BOX_WITH_LEGEND: { border: "1px solid #939393" },
BOX_SINGLE_COLUMN: { display: "flex", flexDirection: "column", gap: "10px" },
BOX_FEW_COLUMNS: { display: "flex", flexWrap: "wrap", justifyContent: "space-between" },
BOX_LEFT_ALIGN: { display: "flex", justifyContent: "flex-start" },
LEGEND: { textAlign: "left" },
TEXT_FIELD: (widthVal, greyDisabled = false) => ({
margin: "4px",
...(widthVal ? { width: widthVal } : {}),
...(greyDisabled
? {
"& .MuiInputBase-input.Mui-disabled": {
WebkitTextFillColor: "rgba(0, 0, 0, 0.87)"
},
"& .MuiInputLabel-root.Mui-disabled": {
WebkitTextFillColor: "rgba(0, 0, 0, 0.6)"
}
}
: {})
})
};
//------------------------------------
//Вспомогательные функции и компоненты
//------------------------------------
//Подключение настройки пользовательского формата даты
dayjs.extend(customParseFormat);
//Свойства вкладки
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
"aria-controls": `simple-tabpanel-${index}`
};
}
//Формирование кнопки для открытия раздела
const getInputProps = (onClick, disabled = false, icon = "list") => {
return {
endAdornment: (
{icon}
)
};
};
//Вкладка информации
function CustomTabPanel(props) {
const { children, value, index, ...other } = props;
return (
{value === index && {children}}
);
}
//Контроль свойств - Вкладка информации
CustomTabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired
};
//Вкладка основной информации
const MainEventInfoTab = ({
task,
editable,
handleFieldEdit,
handleClientClientsOpen,
handleClientPersonOpen,
handleCrnOpen,
handleEventNextNumbGet
}) => {
return (
handleClientPersonOpen(0), !task.stype)}
>
);
};
//Контроль свойств - Вкладка основной информации
MainEventInfoTab.propTypes = {
task: PropTypes.object.isRequired,
editable: PropTypes.bool.isRequired,
handleFieldEdit: PropTypes.func.isRequired,
handleClientClientsOpen: PropTypes.func.isRequired,
handleClientPersonOpen: PropTypes.func.isRequired,
handleCrnOpen: PropTypes.func.isRequired,
handleEventNextNumbGet: PropTypes.func.isRequired
};
//Вкладка информации об исполнителе
const ExecutorEventInfoTab = ({ task, handleFieldEdit, handleClientPersonOpen }) => {
return (
handleClientPersonOpen(1), task.isUpdate)}
>
);
};
//Контроль свойств - Вкладка информации об исполнителе
ExecutorEventInfoTab.propTypes = {
task: PropTypes.object.isRequired,
handleFieldEdit: PropTypes.func.isRequired,
handleClientPersonOpen: PropTypes.func.isRequired
};
//Вкладка информации со свойствами
const PropsEventInfoTab = ({ task, docProps, handlePropEdit }) => {
//Подключение к контексту приложения
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
//Формат дополнительного свойства типа число (длина, точность)
const DPNumFormat = (l, p) => new RegExp("^(\\d{1," + (l - p) + "}" + (p > 0 ? "((\\.|,)\\d{1," + p + "})?" : "") + ")?$");
//Формат дополнительного свойства типа строка (длина)
const DPStrFormat = l => new RegExp("^.{0," + l + "}$");
//Проверка валидности числа
const isValidDPNum = (length, prec, value) => {
return DPNumFormat(length, prec).test(value);
};
//Проверка валидности строки
const isValidDPStr = (length, value) => {
return DPStrFormat(length).test(value);
};
//Признак ошибки валидации
const validationError = (value = "", format, numW, numPrec, strW) => {
if (format === 0) return isValidDPStr(strW, value);
else if (format === 1) {
return isValidDPNum(numW, numPrec, value);
} else return true;
};
//Конвертация времени в привычный формат
const timeFromSqlFormat = ts => {
if (ts.indexOf(".") !== -1) {
let s = 24 * 60 * 60 * ts;
const h = Math.trunc(s / (60 * 60));
s = s % (60 * 60);
const m = Math.trunc(s / 60);
s = Math.round(s % 60);
const formattedTime = ("0" + h).slice(-2) + ":" + ("0" + m).slice(-2) + ":" + ("0" + s).slice(-2);
return formattedTime;
}
return ts;
};
//Выбор из словаря или дополнительного словаря
const handleDict = async (dp, curValue = null) => {
dp.entryType === 1
? pOnlineShowDictionary({
unitCode: dp.unitcode,
showMethod: dp.showMethodCode,
inputParameters: dp.paramRn ? [{ name: dp.paramIn, value: curValue }] : null,
callBack: res => {
res.success
? handlePropEdit({
target: {
id: `${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`,
value: res.outParameters[dp.paramOut]
}
})
: null;
}
})
: pOnlineShowDictionary({
unitCode: "ExtraDictionaries",
showMethod: "values",
inputParameters: [
{ name: "pos_rn", value: dp.extraDictRn },
{ name: DP_IN_VALUE[dp.format], value: curValue }
],
callBack: res => {
res.success
? handlePropEdit({
target: {
id: `${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`,
value: res.outParameters[DP_RETURN_VALUE[dp.format]]
}
})
: null;
}
});
};
//Инициализация дополнительного свойства
const initProp = prop => {
//Значение свойства
const value = task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`];
if (
(task.nrn || task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`]) &&
task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`] !== undefined
) {
//Строка или число
if (prop.format < 2) return prop.numPrecision ? String(value).replace(".", ",") : value;
//Дата
else if (prop.format === 2) {
//Дата без времени
if (prop.dataSubtype === 0) return dayjs(value).format("YYYY-MM-DD");
//Дата + время без секунд
else if (prop.dataSubtype === 1) return dayjs(value).format("YYYY-MM-DD HH:mm");
//Дата + время с секундами
else return dayjs(value).format("YYYY-MM-DD HH:mm:ss");
}
//Время
else {
return timeFromSqlFormat(value);
}
} else if (task.nrn) {
return "";
} else return prop[DP_DEFAULT_VALUE[prop.format]];
};
//Генерация содержимого
return (
{docProps.props.map(dp => {
return dp.showInGrid ? (
0
? getInputProps(() => handleDict(dp, task.docProps[`${DP_TYPE_PREFIX[dp.format]}DP_${dp.rn}`]))
: null
}
InputLabelProps={
dp.format < 2
? {}
: {
shrink: true
}
}
required={dp.require}
disabled={dp.readonly}
/>
) : null;
})}
);
};
//Контроль свойств - Вкладка информации со свойствами
PropsEventInfoTab.propTypes = {
task: PropTypes.object.isRequired,
docProps: PropTypes.object.isRequired,
handlePropEdit: PropTypes.func.isRequired
};
//-----------
//Тело модуля
//-----------
//Форма события
const TaskForm = ({
task,
taskType,
setTask,
editable,
handleClientClientsOpen,
handleClientPersonOpen,
handleCrnOpen,
handleEventNextNumbGet,
handleDPReady
}) => {
//Состояние вкладки
const [value, setValue] = useState(0);
//Состояние допустимых дополнительных свойств
const [docProps] = useDocsProps(taskType);
//При изменении вкладки
const handleChange = (event, newValue) => {
setValue(newValue);
};
//При изменении поля
const handleFieldEdit = e => {
setTask(pv => ({
...pv,
[e.target.id]: e.target.value,
//Связанные значения, если меняется одно, то необходимо обнулить другое
...(e.target.id === "sclnt_clnperson" ? { sclnt_clnclients: "" } : {}),
...(e.target.id === "sclnt_clnclients" ? { sclnt_clnperson: "" } : {})
}));
};
//При изменении свойства
const handlePropEdit = e => {
setTask(pv => ({
...pv,
docProps: { ...pv.docProps, [e.target.id]: e.target.value }
}));
};
//При заполнении всех обязательных свойств
useEffect(() => {
let i = 0;
docProps.props.filter(dp => dp.require === true).map(prop => (!task.docProps[`${DP_TYPE_PREFIX[prop.format]}DP_${prop.rn}`] ? i++ : null));
docProps.loaded && i === 0 ? handleDPReady(true) : handleDPReady(false);
}, [docProps, handleDPReady, task.docProps]);
//Генерация содержимого
return (
{task.nrn ? "Исправление события" : "Добавление события"}
{docProps.props.length > 0 ? : null}
{docProps.props.length > 0 ? (
) : null}
);
};
//Контроль свойств - Форма события
TaskForm.propTypes = {
task: PropTypes.object.isRequired,
taskType: PropTypes.string.isRequired,
setTask: PropTypes.func.isRequired,
editable: PropTypes.bool.isRequired,
handleClientClientsOpen: PropTypes.func.isRequired,
handleClientPersonOpen: PropTypes.func.isRequired,
handleCrnOpen: PropTypes.func.isRequired,
handleEventNextNumbGet: PropTypes.func.isRequired,
handleDPReady: PropTypes.func.isRequired
};
//Диалог с формой события
const TaskFormDialog = ({ taskRn, taskType, taskStatus, editable, handleReload, onClose }) => {
//Собственное состояние
const [task, setTask, insertEvent, updateEvent, handleClientClientsOpen, handleClientPersonOpen, handleCrnOpen, handleEventNextNumbGet] =
useClientEvent(taskRn, taskType, taskStatus);
//Состояние заполненности всех обязательных свойств
const [dpReady, setDPReady] = useState(false);
//Изменение состояния заполненности всех обязательных свойств
const handleDPReady = v => setDPReady(v);
//Генерация содержимого
return (
);
};
//Контроль свойств - Диалог с формой события
TaskFormDialog.propTypes = {
taskRn: PropTypes.number,
taskType: PropTypes.string.isRequired,
taskStatus: PropTypes.string,
editable: PropTypes.bool,
handleReload: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired
};
//----------------
//Интерфейс модуля
//----------------
export { TaskFormDialog };