DFiP_Budget_planing/web/src/pages/ProjectsPage/ModalCreateProject.jsx
2026-08-20 17:25:18 +03:00

283 lines
7.4 KiB
JavaScript

import { Box, Button, Divider, TextField, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
import Modal from '../../components/common/Modal/Modal';
import SelectWithOptions from '../AdminPanel/components/SelectWithOptions';
export const ModalCreateProject = ({
open,
onClose,
onCreate,
config = {},
initialData = null,
title = 'Создание проекта',
buttonText = 'Создать',
}) => {
const [projectData, setProjectData] = useState(() => {
if (initialData) {
return { ...initialData };
}
// Иначе создаем из конфига
const data = {};
Object.keys(config).forEach((key) => {
data[key] = config[key].defaultValue;
});
return data;
});
const [errors, setErrors] = useState({});
useEffect(() => {
if (initialData) {
setProjectData({ ...initialData });
} else {
const data = {};
Object.keys(config).forEach((key) => {
data[key] = config[key].defaultValue;
});
setProjectData(data);
}
setErrors({});
}, [initialData, config, open]);
const handleChange = (field, value) => {
setProjectData((prev) => ({
...prev,
[field]: value,
}));
if (errors[field]) {
setErrors((prev) => ({
...prev,
[field]: undefined,
}));
}
};
const validateField = (fieldName, config) => {
const value = projectData[fieldName];
if (config.required_field) {
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.type === 'number' && value !== null && value !== undefined && value !== '') {
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);
onClose();
}
};
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} sx={{ mb: hasError ? '0.5rem' : 0 }}>
<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} sx={{ mb: hasError ? '0.5rem' : 0 }}>
<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} sx={{ mb: hasError ? '0.5rem' : 0 }}>
<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={title}
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' }}>
{buttonText}
</PrimaryButton>
</>
}>
<Divider />
<Box
sx={{
m: '1.25rem 0',
display: 'flex',
flexDirection: 'column',
gap: '1.25rem',
'& .sameSize': {
minWidth: '39.75rem',
},
'& .sameSize.MuiInputBase-root, & .sameSize .MuiInputBase-root': {
height: '2.69rem',
},
'& .gray-color': { color: '#4A5565' },
}}>
{Object.entries(config).map(([fieldName, config]) => renderField(fieldName, config))}
</Box>
<Divider />
</Modal>
);
};