DFiP_Budget_planing/web/src/pages/ProjectsPage/ModalCreateProject.jsx

307 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/components/common/ProjectCardComponent/ModalCreateProject.jsx
import { useState, useEffect } from 'react';
import { Box, Button, Divider, TextField, Typography } from '@mui/material';
import SelectWithOptions from '../AdminPanel/components/SelectWithOptions';
import Modal from '../../components/common/Modal/Modal';
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
export const ModalCreateProject = ({
open,
onClose,
onCreate,
config = {},
initialData = null,
isEdit = false,
projectId = null,
}) => {
const [projectData, setProjectData] = useState(() => {
// Если есть initialData, используем её, иначе создаем из конфига
if (initialData) {
return { ...initialData };
}
const initial = {};
Object.keys(config).forEach((key) => {
initial[key] = config[key].defaultValue;
});
return initial;
});
const [errors, setErrors] = useState({});
// Обновляем данные при изменении initialData
useEffect(() => {
if (initialData) {
setProjectData({ ...initialData });
} else {
const initial = {};
Object.keys(config).forEach((key) => {
initial[key] = config[key].defaultValue;
});
setProjectData(initial);
}
setErrors({});
}, [initialData, config]);
const handleChange = (field, value) => {
setProjectData((prev) => ({
...prev,
[field]: value,
}));
// Очищаем ошибку для поля при изменении
if (errors[field]) {
setErrors((prev) => ({
...prev,
[field]: undefined,
}));
}
};
const validateField = (fieldName, config) => {
if (config.required_field) {
const value = projectData[fieldName];
// Проверка для разных типов полей
if (config.type === 'multiselect') {
if (!value || (Array.isArray(value) && value.length === 0) || value === '') {
return `Поле "${fieldName}" обязательно для заполнения`;
}
} else if (config.type === 'text') {
if (!value || value.trim() === '') {
return `Поле "${fieldName}" обязательно для заполнения`;
}
} else if (config.type === 'number') {
if (value === null || value === undefined || value === '') {
return `Поле "${fieldName}" обязательно для заполнения`;
}
// Проверка на минимальное значение
if (config.min !== undefined && Number(value) < config.min) {
return `Значение должно быть не меньше ${config.min}`;
}
// Проверка на максимальное значение
if (config.max !== undefined && Number(value) > config.max) {
return `Значение должно быть не больше ${config.max}`;
}
}
}
return null;
};
const validateAllFields = () => {
const newErrors = {};
let hasErrors = false;
Object.entries(config).forEach(([fieldName, fieldConfig]) => {
const error = validateField(fieldName, fieldConfig);
if (error) {
newErrors[fieldName] = error;
hasErrors = true;
}
});
setErrors(newErrors);
return !hasErrors;
};
const handleClickCreate = () => {
if (validateAllFields()) {
onCreate(projectData);
// Не закрываем модалку здесь, пусть это делает родительский компонент
}
};
const handleClose = () => {
const resetData = {};
Object.keys(config).forEach((key) => {
resetData[key] = config[key].defaultValue;
});
setProjectData(resetData);
setErrors({});
onClose();
};
const renderField = (fieldName, config) => {
const hasError = !!errors[fieldName];
const errorText = errors[fieldName];
switch (config.type) {
case 'multiselect':
return (
<Box key={fieldName}>
<Typography
className="gray-color"
sx={{
fontWeight: '400',
fontSize: '0.75rem',
paddingBottom: '.25rem',
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
}}
>
{fieldName}
{config.required_field && (
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
)}
</Typography>
<SelectWithOptions
className="sameSize"
value={projectData[fieldName] || ''}
options={config.options}
placeholder={config.placeholder}
onChange={(value) => handleChange(fieldName, value)}
width="28.25rem"
style={{
borderColor: hasError ? 'var(--error-color, #d32f2f)' : undefined,
borderWidth: hasError ? '2px' : undefined,
}}
/>
{hasError && (
<Typography
sx={{
color: 'var(--error-color, #d32f2f)',
fontSize: '0.75rem',
marginTop: '4px',
}}
>
{errorText}
</Typography>
)}
</Box>
);
case 'text':
return (
<Box key={fieldName}>
<Typography
className="gray-color"
sx={{
fontWeight: '400',
fontSize: '0.75rem',
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
paddingBottom: '.25rem',
}}
>
{fieldName}
{config.required_field && (
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
)}
</Typography>
<TextField
className="sameSize"
value={projectData[fieldName] || ''}
onChange={(e) => handleChange(fieldName, e.target.value)}
placeholder={config.placeholder}
size="small"
error={hasError}
helperText={hasError ? errorText : ''}
FormHelperTextProps={{
sx: {
marginLeft: 0,
fontSize: '0.75rem',
},
}}
/>
</Box>
);
case 'number':
return (
<Box key={fieldName}>
<Typography
className="gray-color"
sx={{
fontWeight: '400',
fontSize: '0.75rem',
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
paddingBottom: '.25rem',
}}
>
{fieldName}
{config.required_field && (
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
)}
</Typography>
<TextField
className="sameSize"
type="text"
value={projectData[fieldName] || ''}
onChange={(e) => {
const inputValue = e.target.value;
//только числа
const isValidInput =
/^\d*\.?\d*$/.test(inputValue) || inputValue === '';
if (isValidInput) {
handleChange(fieldName, inputValue);
}
}}
placeholder={config.placeholder}
size="small"
error={hasError}
helperText={hasError ? errorText : ''}
slotProps={{
htmlInput: {
min: config.min,
max: config.max || null,
step: config.step || 1,
},
}}
FormHelperTextProps={{
sx: {
marginLeft: 0,
fontSize: '0.75rem',
},
}}
/>
</Box>
);
default:
return null;
}
};
return (
<Modal
title={isEdit ? 'Редактирование проекта' : 'Создание проекта'}
open={open}
onClose={handleClose}
customSize={'43.75'}
actions={
<>
<Button
variant="grey"
onClick={handleClose}
style={{ height: '2.5rem' }}
>
Отменить
</Button>
<PrimaryButton
variant="contained"
onClick={handleClickCreate}
style={{ height: '2.5rem' }}
>
{isEdit ? 'Сохранить' : 'Создать'}
</PrimaryButton>
</>
}
>
<Divider />
<Box
sx={{
m: '1.25rem 0',
display: 'flex',
flexDirection: 'column',
gap: '1.25rem',
'& .sameSize': {
height: '2.69rem',
minWidth: '39.75rem',
},
'& .gray-color': { color: '#4A5565' },
}}
>
{Object.entries(config).map(([fieldName, config]) =>
renderField(fieldName, config),
)}
</Box>
<Divider />
</Modal>
);
};