Compare commits

..

12 Commits

10 changed files with 75 additions and 64 deletions

View File

@ -2,8 +2,11 @@
import { useEffect, useRef, useLayoutEffect, useState } from 'react'; import { useEffect, useRef, useLayoutEffect, useState } from 'react';
import { isFormula, extractFormula, calculateFormula, formatNumber } from '../../utils/formulaUtils'; import { isFormula, extractFormula, calculateFormula, formatNumber } from '../../utils/formulaUtils';
import { saveToStorage, loadFromStorage } from '../../utils/localStorageUtils'; import { saveToStorage, loadFromStorage } from '../../utils/localStorageUtils';
import { useRealtime } from '../../contexts/RealtimeContext';
const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId, table }) => {
const { endEditing: contextEndEditing } = useRealtime();
const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId }) => {
const [curValue, setCurValue] = useState(value); const [curValue, setCurValue] = useState(value);
const [displayValue, setDisplayValue] = useState(value); const [displayValue, setDisplayValue] = useState(value);
const [isFormulaMode, setIsFormulaMode] = useState(false); const [isFormulaMode, setIsFormulaMode] = useState(false);
@ -69,7 +72,6 @@ const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId }) => {
if (isFormula(curValue)) { if (isFormula(curValue)) {
// Сохраняем формулу в localStorage // Сохраняем формулу в localStorage
saveToStorage(storageKey, curValue); saveToStorage(storageKey, curValue);
// Вычисляем и сохраняем результат // Вычисляем и сохраняем результат
try { try {
const formula = extractFormula(curValue); const formula = extractFormula(curValue);
@ -108,6 +110,32 @@ const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId }) => {
return displayValue; return displayValue;
}; };
const finishEditing = () => {
if (!disabled) {
handleSave();
}
const editingCell = table.getState().editingCell || null;
if (editingCell) {
contextEndEditing?.(editingCell.row, editingCell.column);
}
};
useEffect(() => {
const handleClickOutside = (event) => {
// Проверяем, был ли клик внутри элемента редактирования
const editElement = ref.current;
if (editElement && !editElement.contains(event.target)) {
finishEditing();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [ref, finishEditing]);
return ( return (
<tr> <tr>
<td> <td>
@ -150,16 +178,10 @@ const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId }) => {
}} }}
onChange={handleChangeValue} onChange={handleChangeValue}
value={curValue} value={curValue}
onBlur={handleSave}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
handleSave(); finishEditing();
// Закрываем редактирование
if (onChange) {
// Симулируем завершение редактирования
document.activeElement?.blur();
}
} }
}} }}
placeholder={isFormulaMode ? 'Введите формулу (начинается с =)' : ''} placeholder={isFormulaMode ? 'Введите формулу (начинается с =)' : ''}

View File

@ -63,7 +63,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
addRow: contextAddRow, addRow: contextAddRow,
deleteRow: contextDeleteRow, deleteRow: contextDeleteRow,
startEditing: contextStartEditing, startEditing: contextStartEditing,
endEditing: contextEndEditing,
} = useRealtime(); } = useRealtime();
const { const {
@ -180,8 +179,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
if (cell) { if (cell) {
if (editingCell) return; if (editingCell) return;
contextStartEditing?.(cell.row, cell.column); contextStartEditing?.(cell.row, cell.column);
} else {
contextEndEditing?.(editingCell.row, editingCell.column);
} }
}, },
columnVirtualizerOptions: ({ table }) => ({ columnVirtualizerOptions: ({ table }) => ({

View File

@ -126,8 +126,7 @@ const SettingPanel = ({
</Tooltip> </Tooltip>
</GroupByObject> </GroupByObject>
{!isProject &&
{!isProject ??
<> <>
<Divider orientation="vertical" flexItem /> <Divider orientation="vertical" flexItem />
<GroupByObject title="Строки"> <GroupByObject title="Строки">

View File

@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
import { columns as mockColumns } from '../../mocks/mockTable'; import { columns as mockColumns } from '../../mocks/mockTable';
import { config } from './constants/FORM_2/AHR'; import { config } from './constants/FORM_2/AHR';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { useRealtime } from './contexts/RealtimeContext';
function EditCellPortal({ function EditCellPortal({
cell, cell,
@ -20,6 +21,8 @@ function EditCellPortal({
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const isDisabled = false; const isDisabled = false;
const { endEditing: contextEndEditing } = useRealtime();
useEffect(() => { useEffect(() => {
if (ref.current) { if (ref.current) {
const tbodyRef = ref.current.offsetParent?.offsetParent?.offsetParent; const tbodyRef = ref.current.offsetParent?.offsetParent?.offsetParent;
@ -68,6 +71,7 @@ function EditCellPortal({
disabled={!isEditable} disabled={!isEditable}
onChange={handleChange} onChange={handleChange}
tableId={tableKey} tableId={tableKey}
table={table}
cellId={cell.row.id + '_' + cell.column.id} cellId={cell.row.id + '_' + cell.column.id}
/>, />,
refTbody, refTbody,

View File

@ -12,7 +12,7 @@ export const DEFAULT_PROJECT_CONFIG = {
defaultValue: null, defaultValue: null,
min: 0, min: 0,
step: 1, step: 1,
required_field: true, required_field: false,
}, },
'Тип проекта развития': { 'Тип проекта развития': {
type: 'multiselect', type: 'multiselect',

View File

@ -12,8 +12,8 @@ export const EVENT_CONFIGS = {
format: 'Утвержденный формат', format: 'Утвержденный формат',
opened_at: 'Дата открытия ВСП', opened_at: 'Дата открытия ВСП',
notes: 'Примечания', notes: 'Примечания',
location_form: 'Форма расположения ВСП', placement_type: 'Форма расположения ВСП',
numbers: 'Штатная численность ВСП', staff_count: 'Штатная численность ВСП',
total_area: 'Площадь помещений ВСП', total_area: 'Площадь помещений ВСП',
rent_contract_num: 'Номер договора аренды', rent_contract_num: 'Номер договора аренды',
rent_end_date: 'Срок окончания договора аренды', rent_end_date: 'Срок окончания договора аренды',
@ -21,8 +21,6 @@ export const EVENT_CONFIGS = {
branch_name: 'Региональный филиал ', branch_name: 'Региональный филиал ',
closed_at: 'Дата закрытия ВСП', closed_at: 'Дата закрытия ВСП',
user_email: 'Почта пользователя', user_email: 'Почта пользователя',
staff_count: 'Кол-во работников',
placement_type: 'Тип размещение',
is_active: 'Активный', is_active: 'Активный',
}, },
VSP_UPDATE: { VSP_UPDATE: {
@ -32,8 +30,8 @@ export const EVENT_CONFIGS = {
format: 'Утвержденный формат', format: 'Утвержденный формат',
opened_at: 'Дата открытия ВСП', opened_at: 'Дата открытия ВСП',
notes: 'Примечания', notes: 'Примечания',
location_form: 'Форма расположения ВСП', placement_type: 'Форма расположения ВСП',
numbers: 'Штатная численность ВСП', staff_count: 'Штатная численность ВСП',
total_area: 'Площадь помещений ВСП', total_area: 'Площадь помещений ВСП',
rent_contract_num: 'Номер договора аренды', rent_contract_num: 'Номер договора аренды',
rent_end_date: 'Срок окончания договора аренды', rent_end_date: 'Срок окончания договора аренды',
@ -41,8 +39,6 @@ export const EVENT_CONFIGS = {
'changes.branch_name': 'Региональный филиал ', 'changes.branch_name': 'Региональный филиал ',
closed_at: 'Дата закрытия ВСП', closed_at: 'Дата закрытия ВСП',
user_email: 'Почта пользователя', user_email: 'Почта пользователя',
staff_count: 'Кол-во работников',
placement_type: 'Тип размещение',
is_active: 'Активный', is_active: 'Активный',
}, },
USER_ACCESS_GRANTED: { USER_ACCESS_GRANTED: {

View File

@ -43,8 +43,8 @@ const ModalEditAddVsp = ({
open_date: null, open_date: null,
close_date: null, close_date: null,
notes: '', notes: '',
location_form: '', placement_type: '',
numbers: '', staff_count: '',
area: null, area: null,
rent_contract_num: '', rent_contract_num: '',
rent_end_date: null, rent_end_date: null,
@ -94,8 +94,8 @@ const ModalEditAddVsp = ({
? new Date(initialData.close_date) ? new Date(initialData.close_date)
: null, : null,
notes: initialData.notes || '', notes: initialData.notes || '',
location_form: initialData.location_form || '', placement_type: initialData.placement_type || '',
numbers: initialData.staff_count?.toString() || '', staff_count: initialData.staff_count?.toString() || '',
area: initialData.area || null, area: initialData.area || null,
rent_contract_num: initialData.rent_contract_num || '', rent_contract_num: initialData.rent_contract_num || '',
rent_end_date: initialData.rent_end_date rent_end_date: initialData.rent_end_date
@ -114,8 +114,8 @@ const ModalEditAddVsp = ({
open_date: null, open_date: null,
close_date: null, close_date: null,
notes: '', notes: '',
location_form: '', placement_type: '',
numbers: '', staff_count: '',
area: null, area: null,
rent_contract_num: '', rent_contract_num: '',
rent_end_date: null, rent_end_date: null,
@ -215,7 +215,7 @@ const ModalEditAddVsp = ({
const submitData = { const submitData = {
...formData, ...formData,
numbers: formData.staff_count ? parseInt(formData.staff_count, 10) : null, staff_count: formData.staff_count ? parseInt(formData.staff_count, 10) : null,
area: formData.area ? parseFloat(formData.area) : null, area: formData.area ? parseFloat(formData.area) : null,
open_date: formData.open_date open_date: formData.open_date
? formData.open_date.toLocaleDateString('en-CA') ? formData.open_date.toLocaleDateString('en-CA')
@ -255,8 +255,8 @@ const ModalEditAddVsp = ({
open_date: null, open_date: null,
close_date: null, close_date: null,
notes: '', notes: '',
location_form: '', placement_type: '',
numbers: '', staff_count: '',
area: '', area: '',
rent_contract_num: '', rent_contract_num: '',
rent_end_date: null, rent_end_date: null,
@ -461,11 +461,11 @@ const ModalEditAddVsp = ({
<FieldLabel>Форма расположения ВСП</FieldLabel> <FieldLabel>Форма расположения ВСП</FieldLabel>
<StyledTextField <StyledTextField
select select
value={formData.location_form} value={formData.placement_type}
onChange={handleChange('location_form')} onChange={handleChange('placement_type')}
placeholder="Выберите форму расположения" placeholder="Выберите форму расположения"
error={!!errors.location_form} error={!!errors.placement_type}
helperText={errors.location_form} helperText={errors.placement_type}
disabled={loading} disabled={loading}
slotProps={{ slotProps={{
select: { select: {
@ -496,7 +496,7 @@ const ModalEditAddVsp = ({
<StyledTextField <StyledTextField
type="text" type="text"
value={formData.staff_count} value={formData.staff_count}
onChange={handleNumberChange('numbers')} onChange={handleNumberChange('staff_count')}
placeholder="Введите численность" placeholder="Введите численность"
error={!!errors.staff_count} error={!!errors.staff_count}
helperText={errors.staff_count} helperText={errors.staff_count}

View File

@ -192,7 +192,7 @@ const TableDictVsp = ({
enableSorting: false, enableSorting: false,
}, },
{ {
accessorKey: 'location_form', accessorKey: 'placement_type',
header: 'Форма расположения ВСП', header: 'Форма расположения ВСП',
size: 170, size: 170,
minSize: 140, minSize: 140,

View File

@ -1,4 +1,3 @@
// src/components/common/ProjectCardComponent/ModalCreateProject.jsx
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Box, Button, Divider, TextField, Typography } from '@mui/material'; import { Box, Button, Divider, TextField, Typography } from '@mui/material';
import SelectWithOptions from '../AdminPanel/components/SelectWithOptions'; import SelectWithOptions from '../AdminPanel/components/SelectWithOptions';
@ -11,43 +10,41 @@ export const ModalCreateProject = ({
onCreate, onCreate,
config = {}, config = {},
initialData = null, initialData = null,
isEdit = false, title = 'Создание проекта',
projectId = null, buttonText = 'Создать',
}) => { }) => {
const [projectData, setProjectData] = useState(() => { const [projectData, setProjectData] = useState(() => {
// Если есть initialData, используем её, иначе создаем из конфига
if (initialData) { if (initialData) {
return { ...initialData }; return { ...initialData };
} }
const initial = {}; // Иначе создаем из конфига
const data = {};
Object.keys(config).forEach((key) => { Object.keys(config).forEach((key) => {
initial[key] = config[key].defaultValue; data[key] = config[key].defaultValue;
}); });
return initial; return data;
}); });
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
// Обновляем данные при изменении initialData
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
setProjectData({ ...initialData }); setProjectData({ ...initialData });
} else { } else {
const initial = {}; const data = {};
Object.keys(config).forEach((key) => { Object.keys(config).forEach((key) => {
initial[key] = config[key].defaultValue; data[key] = config[key].defaultValue;
}); });
setProjectData(initial); setProjectData(data);
} }
setErrors({}); setErrors({});
}, [initialData, config]); }, [initialData, config, open]);
const handleChange = (field, value) => { const handleChange = (field, value) => {
setProjectData((prev) => ({ setProjectData((prev) => ({
...prev, ...prev,
[field]: value, [field]: value,
})); }));
// Очищаем ошибку для поля при изменении
if (errors[field]) { if (errors[field]) {
setErrors((prev) => ({ setErrors((prev) => ({
...prev, ...prev,
@ -60,7 +57,6 @@ export const ModalCreateProject = ({
if (config.required_field) { if (config.required_field) {
const value = projectData[fieldName]; const value = projectData[fieldName];
// Проверка для разных типов полей
if (config.type === 'multiselect') { if (config.type === 'multiselect') {
if (!value || (Array.isArray(value) && value.length === 0) || value === '') { if (!value || (Array.isArray(value) && value.length === 0) || value === '') {
return `Поле "${fieldName}" обязательно для заполнения`; return `Поле "${fieldName}" обязательно для заполнения`;
@ -73,11 +69,9 @@ export const ModalCreateProject = ({
if (value === null || value === undefined || value === '') { if (value === null || value === undefined || value === '') {
return `Поле "${fieldName}" обязательно для заполнения`; return `Поле "${fieldName}" обязательно для заполнения`;
} }
// Проверка на минимальное значение
if (config.min !== undefined && Number(value) < config.min) { if (config.min !== undefined && Number(value) < config.min) {
return `Значение должно быть не меньше ${config.min}`; return `Значение должно быть не меньше ${config.min}`;
} }
// Проверка на максимальное значение
if (config.max !== undefined && Number(value) > config.max) { if (config.max !== undefined && Number(value) > config.max) {
return `Значение должно быть не больше ${config.max}`; return `Значение должно быть не больше ${config.max}`;
} }
@ -105,7 +99,7 @@ export const ModalCreateProject = ({
const handleClickCreate = () => { const handleClickCreate = () => {
if (validateAllFields()) { if (validateAllFields()) {
onCreate(projectData); onCreate(projectData);
// Не закрываем модалку здесь, пусть это делает родительский компонент onClose();
} }
}; };
@ -225,7 +219,6 @@ export const ModalCreateProject = ({
value={projectData[fieldName] || ''} value={projectData[fieldName] || ''}
onChange={(e) => { onChange={(e) => {
const inputValue = e.target.value; const inputValue = e.target.value;
//только числа
const isValidInput = const isValidInput =
/^\d*\.?\d*$/.test(inputValue) || inputValue === ''; /^\d*\.?\d*$/.test(inputValue) || inputValue === '';
if (isValidInput) { if (isValidInput) {
@ -260,7 +253,7 @@ export const ModalCreateProject = ({
return ( return (
<Modal <Modal
title={isEdit ? 'Редактирование проекта' : 'Создание проекта'} title={title}
open={open} open={open}
onClose={handleClose} onClose={handleClose}
customSize={'43.75'} customSize={'43.75'}
@ -278,7 +271,7 @@ export const ModalCreateProject = ({
onClick={handleClickCreate} onClick={handleClickCreate}
style={{ height: '2.5rem' }} style={{ height: '2.5rem' }}
> >
{isEdit ? 'Сохранить' : 'Создать'} {buttonText}
</PrimaryButton> </PrimaryButton>
</> </>
} }

View File

@ -151,7 +151,7 @@ export const ProjectCardComponent = ({
id, id,
name, name,
org_unit_name, org_unit_name,
year, years,
status, status,
level, level,
project_type, project_type,
@ -180,7 +180,7 @@ export const ProjectCardComponent = ({
const prepareProjectDataForEdit = () => { const prepareProjectDataForEdit = () => {
return { return {
'ССП/РФ': project.org_unit_id || null, 'ССП/РФ': project.org_unit_id || null,
'Год': year || null, 'Год': years || null,
'Тип проекта развития': project_type || null, 'Тип проекта развития': project_type || null,
'Название проекта': currentName, 'Название проекта': currentName,
'Формат ВСП': vsp_format || null, 'Формат ВСП': vsp_format || null,
@ -316,7 +316,7 @@ export const ProjectCardComponent = ({
> >
<CardBody> <CardBody>
<IconWithContent icon={<DateIcon />}> <IconWithContent icon={<DateIcon />}>
{year || 'Год не указан'} {years.join(', ') || 'Год не указан'}
</IconWithContent> </IconWithContent>
<IconWithContent icon={<UserIcon />}> <IconWithContent icon={<UserIcon />}>
{org_unit_name || 'Организация не указана'} {org_unit_name || 'Организация не указана'}