175 lines
6.7 KiB
JavaScript
175 lines
6.7 KiB
JavaScript
/*
|
||
Парус 8 - Панели мониторинга - УДП - Доски задач
|
||
Компонент: Кастомное поле ввода
|
||
*/
|
||
|
||
//---------------------
|
||
//Подключение библиотек
|
||
//---------------------
|
||
|
||
import React, { useEffect, useState } from "react"; //Классы React
|
||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||
import { FormControl, InputLabel, Input, InputAdornment, IconButton, Icon, FormHelperText, Select, MenuItem, Typography } from "@mui/material"; //Интерфейсные компоненты
|
||
import { COMMON_STYLES } from "../styles"; //Общие стили
|
||
|
||
//---------
|
||
//Константы
|
||
//---------
|
||
|
||
//Стили
|
||
const STYLES = {
|
||
HELPER_TEXT: { color: "red" },
|
||
SELECT_MENU: width => {
|
||
return { ...COMMON_STYLES.SCROLL, width: width ? width + 24 : null };
|
||
}
|
||
};
|
||
|
||
//---------------
|
||
//Тело компонента
|
||
//---------------
|
||
|
||
//Кастомное поле ввода
|
||
const CustomInputField = ({
|
||
elementCode,
|
||
elementValue,
|
||
labelText,
|
||
onChange,
|
||
required = false,
|
||
items = null,
|
||
emptyItem = null,
|
||
dictionary,
|
||
menuItemRender,
|
||
...other
|
||
}) => {
|
||
//Значение элемента
|
||
const [value, setValue] = useState(elementValue);
|
||
|
||
//Состояние элемента HTML (для оптимизации ширины MenuItems)
|
||
const [anchorEl, setAnchorEl] = useState();
|
||
|
||
//При открытии меню заливки событий
|
||
const handleMenuOpen = e => {
|
||
//Устанавливаем элемент меню
|
||
setAnchorEl(e.target);
|
||
};
|
||
|
||
//При получении нового значения из вне
|
||
useEffect(() => {
|
||
setValue(elementValue);
|
||
}, [elementValue]);
|
||
|
||
//Изменение значения элемента
|
||
const handleChange = e => {
|
||
setValue(e.target.value);
|
||
if (onChange) onChange(e.target.name, e.target.value);
|
||
};
|
||
|
||
//Выбор значения из словаря
|
||
const handleDictionaryClick = () => {
|
||
dictionary ? dictionary(res => (res ? handleChange({ target: { name: elementCode, value: res } }) : null)) : null;
|
||
};
|
||
|
||
//Генерация поля с выбором из словаря Парус
|
||
const renderInput = validationError => {
|
||
//Генерация содержимого
|
||
return (
|
||
<Input
|
||
error={validationError}
|
||
id={elementCode}
|
||
name={elementCode}
|
||
value={value}
|
||
endAdornment={
|
||
dictionary ? (
|
||
<InputAdornment position="end">
|
||
<IconButton aria-label={`${elementCode} select`} onClick={handleDictionaryClick} edge="end">
|
||
<Icon>list</Icon>
|
||
</IconButton>
|
||
</InputAdornment>
|
||
) : null
|
||
}
|
||
aria-describedby={`${elementCode}-helper-text`}
|
||
label={labelText}
|
||
onChange={handleChange}
|
||
{...other}
|
||
/>
|
||
);
|
||
};
|
||
|
||
//Генерация поля с выпадающим списком
|
||
const renderSelect = (items, anchorEl, handleMenuOpen, validationError) => {
|
||
//Формируем общий список элементов меню
|
||
const menuItems = emptyItem ? [emptyItem, ...items] : [...items];
|
||
//Генерация содержимого
|
||
return (
|
||
<Select
|
||
error={validationError}
|
||
id={elementCode}
|
||
name={elementCode}
|
||
//!!!Пересмотреть момент. При изменении типа происходит ререндер со старым значением учетного документа:
|
||
//1. Изменяется тип
|
||
//2. Очищается items (список учетных документов)
|
||
//3. Рисуется компонент со старым value и пустым items, из-за чего ошибка "You have provided an out-of-range value"
|
||
//4. Вызывается useEffect, меняется значение value на новое (пустое значение)
|
||
value={value}
|
||
aria-describedby={`${elementCode}-helper-text`}
|
||
label={labelText}
|
||
MenuProps={{ slotProps: { paper: { sx: STYLES.SELECT_MENU(anchorEl?.offsetWidth) } } }}
|
||
onChange={handleChange}
|
||
onOpen={handleMenuOpen}
|
||
{...other}
|
||
>
|
||
{menuItems
|
||
? menuItems.map((item, index) => {
|
||
let customRender = null;
|
||
if (menuItemRender) customRender = menuItemRender({ item: item, key: item?.key ?? index }) || null;
|
||
return customRender ? (
|
||
customRender
|
||
) : (
|
||
<MenuItem key={item?.key ?? index} value={item.id}>
|
||
<Typography variant="inherit" noWrap title={item.caption} component="div">
|
||
{item.caption}
|
||
</Typography>
|
||
</MenuItem>
|
||
);
|
||
})
|
||
: null}
|
||
</Select>
|
||
);
|
||
};
|
||
|
||
//Признак ошибки валидации
|
||
const validationError = !value && required ? true : false;
|
||
|
||
//Генерация содержимого
|
||
return (
|
||
<FormControl fullWidth variant="standard">
|
||
<InputLabel htmlFor={elementCode}>{labelText}</InputLabel>
|
||
{items ? renderSelect(items, anchorEl, handleMenuOpen, validationError) : renderInput(validationError)}
|
||
{validationError ? (
|
||
<FormHelperText id={`${elementCode}-helper-text`} sx={STYLES.HELPER_TEXT}>
|
||
*Обязательное поле
|
||
</FormHelperText>
|
||
) : null}
|
||
</FormControl>
|
||
);
|
||
};
|
||
|
||
//Контроль свойств - Кастомное поле ввода
|
||
CustomInputField.propTypes = {
|
||
elementCode: PropTypes.string.isRequired,
|
||
elementValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||
labelText: PropTypes.string.isRequired,
|
||
required: PropTypes.bool,
|
||
items: PropTypes.arrayOf(PropTypes.object),
|
||
emptyItem: PropTypes.object,
|
||
dictionary: PropTypes.func,
|
||
onChange: PropTypes.func,
|
||
menuItemRender: PropTypes.func
|
||
};
|
||
|
||
//--------------------
|
||
//Интерфейс компонента
|
||
//--------------------
|
||
|
||
export { CustomInputField };
|