2026-08-18 14:56:38 +03:00

223 lines
8.0 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.

import CloseIcon from '@mui/icons-material/Close';
import { Box, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, MenuItem, TextField, Typography } from '@mui/material';
import React from 'react';
import { useAuth } from '../../../../app/context/AuthProvider';
import { DangerOutlinedButton, PrimaryButton } from '../../../../components/common/Buttons/Buttons';
import { DEVELOMENT_BLOCK, FUNDING_BY_DECISION } from '../../constants';
import { canEditFirstTableField } from '../../constants/fieldAccess';
const EditModal = ({ open, onClose, rowData, onSave, isSaving = false, orgUnitNames = {} }) => {
const { user } = useAuth();
const [formData, setFormData] = React.useState(rowData || {});
React.useEffect(() => {
if (rowData) {
setFormData(rowData);
}
}, [rowData]);
const handleChange = (field) => (event) => {
let value = event.target.value;
if (field.valueType === 'boolean') value = value === '' ? null : value === 'true';
if (field.valueType === 'number') value = value === '' ? '' : Number(value);
setFormData({
...formData,
[field.key]: value,
});
};
const handleSave = async () => {
await onSave(formData);
};
if (!rowData) return null;
const projectFields = [
{ key: 'name', label: 'Проект' },
{ key: 'status', label: 'Статус', options: [['created', 'Создан'], ['agreed', 'Согласован'], ['archived', 'В архиве']] },
{ key: 'technical_number', label: 'Технический номер проекта' },
{ key: 'org_unit_id', label: 'ССП/РФ', valueType: 'number', options: Object.entries(orgUnitNames).map(([id, title]) => [Number(id), title]) },
{ key: 'project_type', label: 'Тип проекта', options: ['Открытие ВСП', 'Закрытие ВСП', 'Переезд ВСП', 'Реновация РФ', 'Открытие УРМ'] },
{ key: 'vsp_format', label: 'Формат ВСП', options: ['Флагманский', 'Типовой', 'Розничный', 'МСБ', 'Лёгкий', 'Мини', 'Розничный-киоск', 'МБО', 'Офис самообслуживания', 'Другое'] },
{ key: 'placement_type', label: 'Размещение', options: ['Собственность', 'Аренда', 'Субаренда'] },
{ key: 'staff_count', label: 'Количество сотрудников', type: 'number', valueType: 'number' },
{ key: 'total_area', label: 'Общая площадь', type: 'number', valueType: 'number' },
{ key: 'funding_by_ko_decision', label: 'Финансирование по решению КО', options: FUNDING_BY_DECISION },
{ key: 'krf_decision_date', label: 'Дата решения КРФ', type: 'date' },
{ key: 'fk_decision_date', label: 'Дата решения ФК', type: 'date' },
{ key: 'board_decision_date', label: 'Дата решения Правления', type: 'date' },
{ key: 'open_relocate_close_date', label: 'Дата открытия / переезда / закрытия', type: 'date' },
{ key: 'object_address', label: 'Адрес объекта', multiline: true, rows: 2 },
];
const smetaFields = [
{ key: 'year', label: 'Год сметы', type: 'number', disabled: true },
{ key: 'is_in_plan', label: 'В базовой смете', valueType: 'boolean', options: [['true', 'Да'], ['false', 'Нет']] },
{ key: 'is_in_plan_q2', label: 'В скорректированной смете 2 кв', valueType: 'boolean', options: [['true', 'Да'], ['false', 'Нет']] },
{ key: 'is_in_plan_q3', label: 'В скорректированной смете 3 кв', valueType: 'boolean', options: [['true', 'Да'], ['false', 'Нет']] },
{ key: 'is_in_plan_q4', label: 'В скорректированной смете 4 кв', valueType: 'boolean', options: [['true', 'Да'], ['false', 'Нет']] },
{ key: 'development_block', label: 'Блок развития', options: DEVELOMENT_BLOCK },
{ key: 'reserve_to_prrs_ahr', label: 'Из резерва Банка в смету ПРРС АХР', type: 'number', valueType: 'number' },
{ key: 'reserve_to_prrs_kv', label: 'Из резерва Банка в смету ПРРС КВ', type: 'number', valueType: 'number' },
];
const fields = rowData._rowType === 'smeta' ? smetaFields : projectFields;
const middle = Math.ceil(fields.length / 2);
const leftColumnFields = fields.slice(0, middle);
const rightColumnFields = fields.slice(middle);
// Компонент для поля ввода
const renderField = (field) => {
const rawValue = formData[field.key];
const isFieldDisabled = field.disabled || !canEditFirstTableField(field.key, user?.role_id);
const fieldValue = field.valueType === 'boolean' && rawValue !== null && rawValue !== undefined
? String(rawValue)
: (rawValue ?? '');
return (
<Box key={field.key} sx={{ width: '390px', flexShrink: 0 }}>
<Typography
variant='caption'
sx={{
fontWeight: 600,
color: '#666',
display: 'block',
mb: 0.5,
fontSize: '0.75rem',
}}>
{field.label}
</Typography>
<TextField
fullWidth
value={fieldValue}
onChange={handleChange(field)}
variant='outlined'
size='small'
type={field.type || 'text'}
placeholder={field.placeholder || ''}
multiline={field.multiline || false}
rows={field.rows || 1}
select={Boolean(field.options)}
disabled={isFieldDisabled || isSaving}
InputLabelProps={field.type === 'date' ? { shrink: true } : undefined}
sx={{
'& .MuiOutlinedInput-root': {
bgcolor: 'transparent',
'& fieldset': {
borderColor: '#d0d0d0',
},
'&:hover fieldset': {
borderColor: '#b0b0b0',
},
'&.Mui-focused fieldset': {
borderColor: '#1976d2',
},
...(field.multiline && {
minHeight: '56px',
}),
},
'& .MuiOutlinedInput-input': {
bgcolor: 'transparent',
},
'& .MuiInputBase-input': {
bgcolor: 'transparent',
},
width: '100%',
}}
>
{field.options && [<MenuItem key='empty' value=''>Не выбрано</MenuItem>, ...field.options.map((option) => {
const [value, label] = Array.isArray(option) ? option : [option, option];
return <MenuItem key={value} value={value}>{label}</MenuItem>;
})]}
</TextField>
</Box>
);
};
return (
<Dialog
open={open}
onClose={onClose}
maxWidth={false}
PaperProps={{
sx: {
borderRadius: '12px',
maxHeight: '90vh',
height: 'auto',
maxWidth: '820px',
width: '100%',
},
}}>
<DialogTitle
sx={{
m: 0,
p: '16px 24px',
borderBottom: '1px solid #e0e0e0',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
bgcolor: '#f8f9fa',
}}>
<Typography variant='h6' sx={{ fontWeight: 600, fontSize: '18px' }}>
Редактирование строки
</Typography>
<IconButton onClick={onClose} size='small'>
<CloseIcon fontSize='small' />
</IconButton>
</DialogTitle>
<DialogContent
sx={{
pt: 3,
pb: 2,
px: 3,
overflow: 'auto',
}}>
<Box
sx={{
display: 'flex',
gap: '20px',
justifyContent: 'space-between',
}}>
{/* Левая колонка */}
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
flex: '0 0 auto',
}}>
{leftColumnFields.map(renderField)}
</Box>
{/* Правая колонка */}
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
flex: '0 0 auto',
}}>
{rightColumnFields.map(renderField)}
</Box>
</Box>
</DialogContent>
<DialogActions
sx={{
p: 2,
px: 3,
borderTop: '1px solid #e0e0e0',
bgcolor: '#f8f9fa',
justifyContent: 'flex-end',
gap: 1,
}}>
<DangerOutlinedButton onClick={onClose} disabled={isSaving}>Отмена</DangerOutlinedButton>
<PrimaryButton onClick={handleSave} disabled={isSaving}>{isSaving ? 'Сохранение...' : 'Сохранить'}</PrimaryButton>
</DialogActions>
</Dialog>
);
};
export default EditModal;