2026-07-24 17:26:05 +03:00

217 lines
5.7 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 React, { memo, useEffect, useRef, useLayoutEffect, useState, useCallback, useMemo } from 'react';
import { isFormula, extractFormula, calculateFormula, formatNumber } from '../../utils/formulaUtils';
import { saveToStorage, loadFromStorage } from '../../utils/localStorageUtils';
import { useRealtime } from '../../contexts/RealtimeContext';
const EDITOR_STYLES = {
position: 'absolute',
zIndex: 12,
top: 0,
};
const FORMULA_INDICATOR_STYLES = {
position: 'absolute',
top: '-20px',
left: '4px',
fontSize: '10px',
color: '#666',
background: '#f0f0f0',
padding: '2px 6px',
borderRadius: '3px',
pointerEvents: 'none',
};
const TEXTAREA_STYLES = {
width: '100%',
height: '3rem',
fieldSizing: 'content',
fontSize: 'inherit',
};
const EditCell = memo(({ refCell, onChange, disabled, value, tableId, cellId, table }) => {
const { endEditing: contextEndEditing } = useRealtime();
const storageKey = `formula_${tableId}_${cellId}`;
const [state, setState] = useState(() => ({
currentValue: value,
displayValue: value,
isFormulaMode: false,
}));
// Позиция ячейки
const [position, setPosition] = useState();
const textareaRef = useRef(null);
const refFinished = useRef(false);
// Загрузка сохраненной формулы
useEffect(() => {
const savedFormula = loadFromStorage(storageKey);
if (savedFormula && isFormula(savedFormula)) {
try {
const formula = extractFormula(savedFormula);
const result = calculateFormula(formula);
setState({
currentValue: savedFormula,
displayValue: formatNumber(result),
isFormulaMode: true,
});
} catch {
setState({
currentValue: savedFormula,
displayValue: '#ОШИБКА',
isFormulaMode: true,
});
}
}
}, [storageKey]);
// Вычисление позиции
useLayoutEffect(() => {
if (!refCell?.current) return;
const td = refCell.current.offsetParent;
const tr = td?.offsetParent;
if (td && tr) {
setPosition({
left: td.offsetLeft,
width: td.offsetWidth,
translateY: tr.style.transform || '',
});
}
}, [refCell]);
// Auto-resize
useLayoutEffect(() => {
const el = textareaRef.current;
if (!el) return;
const resize = () => {
el.style.minHeight = '3rem';
el.style.height = '3rem';
el.style.height = el.scrollHeight + 'px';
el.style.maxHeight = '180px';
};
resize();
const observer = new ResizeObserver(resize);
observer.observe(el);
return () => observer.disconnect();
}, [state.currentValue]);
const handleSave = useCallback(() => {
const { currentValue } = state;
if (isFormula(currentValue)) {
saveToStorage(storageKey, currentValue);
try {
const formula = extractFormula(currentValue);
const result = calculateFormula(formula);
const formattedResult = formatNumber(result);
setState(prev => ({
...prev,
displayValue: formattedResult,
isFormulaMode: true,
}));
onChange?.(formattedResult);
} catch {
setState(prev => ({
...prev,
displayValue: '#ОШИБКА',
isFormulaMode: true,
}));
onChange?.(currentValue);
}
} else {
localStorage.removeItem(storageKey);
setState(prev => ({
...prev,
displayValue: currentValue,
isFormulaMode: false,
}));
onChange?.(currentValue);
}
}, [state, storageKey, onChange]);
const finishEditing = useCallback(() => {
if (refFinished.current) return;
refFinished.current = true;
if (!disabled) {
handleSave();
}
const editingCell = table.getState().editingCell;
if (editingCell) {
contextEndEditing?.(editingCell.row, editingCell.column);
}
}, [disabled, handleSave, table, contextEndEditing]);
useEffect(() => {
const handleClickOutside = (event) => {
const el = textareaRef.current;
if (el && !el.contains(event.target)) {
finishEditing();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [finishEditing]);
const handleChange = useCallback((e) => {
const value = e.target.value;
setState(prev => ({
...prev,
currentValue: value,
displayValue: value,
}));
}, []);
const handleKeyDown = useCallback((e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
finishEditing();
}
}, [finishEditing]);
const editorStyles = useMemo(() => ({
...EDITOR_STYLES,
left: position?.left || 0,
width: position?.width || 0,
transform: position?.translateY || 0,
}), [position]);
const textareaStyles = useMemo(() => ({
...TEXTAREA_STYLES,
backgroundColor: state.isFormulaMode ? '#f8f9fa' : 'white',
border: state.isFormulaMode ? '2px solid #4a90d9' : undefined,
}), [state.isFormulaMode]);
return (
<tr>
<td>
{position &&
<div style={editorStyles}>
{state.isFormulaMode && (
<div style={FORMULA_INDICATOR_STYLES}>fx</div>
)}
<textarea
ref={textareaRef}
disabled={disabled}
style={textareaStyles}
onChange={handleChange}
value={state.currentValue}
onKeyDown={handleKeyDown}
placeholder={state.isFormulaMode ? 'Введите формулу (начинается с =)' : ''}
/>
</div>
}
</td>
</tr>
);
});
EditCell.displayName = 'EditCell';
export default EditCell;