Formuls
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import { useSpreadsheetEngine } from '../../hooks/useSpreadsheetEngine';
|
||||
|
||||
interface CellData {
|
||||
value: string;
|
||||
@@ -6,6 +7,19 @@ interface CellData {
|
||||
}
|
||||
|
||||
const Spreadsheet: FC = () => {
|
||||
const {
|
||||
cells: engineCells,
|
||||
setCellValue: setEngineCellValue,
|
||||
getCellValue,
|
||||
getCellRawValue,
|
||||
isCalculating,
|
||||
recalculate
|
||||
} = useSpreadsheetEngine({
|
||||
rows: 20,
|
||||
cols: 10,
|
||||
sheetName: 'Sheet1'
|
||||
});
|
||||
|
||||
const [cells, setCells] = useState<CellData[][]>(() => {
|
||||
// Создаем сетку 20x10 с пустыми ячейками
|
||||
return Array(20).fill(null).map(() =>
|
||||
@@ -28,14 +42,50 @@ const Spreadsheet: FC = () => {
|
||||
inputRefs.current = Array(20).fill(null).map(() => Array(10).fill(null));
|
||||
}, []);
|
||||
|
||||
// Синхронизируем локальное состояние с движком
|
||||
useEffect(() => {
|
||||
setCells(prev => {
|
||||
const newCells = [...prev];
|
||||
for (let row = 0; row < 20; row++) {
|
||||
for (let col = 0; col < 10; col++) {
|
||||
const rawValue = getCellRawValue(row, col);
|
||||
if (rawValue !== newCells[row][col].value) {
|
||||
newCells[row][col] = { ...newCells[row][col], value: rawValue };
|
||||
}
|
||||
}
|
||||
}
|
||||
return newCells;
|
||||
});
|
||||
}, [engineCells, getCellRawValue]);
|
||||
|
||||
// Тестовые данные для демонстрации
|
||||
useEffect(() => {
|
||||
// Добавляем тестовые данные
|
||||
setEngineCellValue(0, 0, '10'); // A1 = 10
|
||||
setEngineCellValue(1, 0, '20'); // A2 = 20
|
||||
setEngineCellValue(2, 0, '=A1+A2'); // A3 = A1+A2
|
||||
setEngineCellValue(0, 1, '=A1*2'); // B1 = A1*2
|
||||
setEngineCellValue(1, 1, '=SUM(A1,A2)'); // B2 = SUM(A1,A2)
|
||||
setEngineCellValue(0, 2, '="Результат: " + A3'); // C1 = "Результат: " + A3
|
||||
|
||||
// Демонстрация волатильных функций
|
||||
setEngineCellValue(0, 3, '=RAND()'); // D1 = случайное число
|
||||
setEngineCellValue(1, 3, '=RANDBETWEEN(1,100)'); // D2 = случайное число от 1 до 100
|
||||
setEngineCellValue(2, 3, '=D1*100'); // D3 = D1*100 (зависит от волатильной функции)
|
||||
setEngineCellValue(0, 4, '=TODAY()'); // E1 = сегодняшняя дата
|
||||
setEngineCellValue(1, 4, '=NOW()'); // E2 = текущая дата и время
|
||||
}, [setEngineCellValue]);
|
||||
|
||||
// Синхронизируем значение formula bar с выбранной ячейкой
|
||||
useEffect(() => {
|
||||
if (selectedCell) {
|
||||
setFormulaBarValue(cells[selectedCell.row][selectedCell.col].value);
|
||||
// Показываем сырое значение (формулу) в formula bar
|
||||
const rawValue = getCellRawValue(selectedCell.row, selectedCell.col);
|
||||
setFormulaBarValue(rawValue);
|
||||
} else {
|
||||
setFormulaBarValue('');
|
||||
}
|
||||
}, [selectedCell, cells]);
|
||||
}, [selectedCell, getCellRawValue, engineCells]);
|
||||
|
||||
const handleCellClick = (row: number, col: number) => {
|
||||
setSelectedCell({ row, col });
|
||||
@@ -45,7 +95,7 @@ const Spreadsheet: FC = () => {
|
||||
|
||||
const handleCellDoubleClick = (row: number, col: number) => {
|
||||
setSelectedCell({ row, col });
|
||||
// Сохраняем текущее значение для возможности отмены
|
||||
// Сохраняем текущее значение из UI для возможности отмены
|
||||
setEditingValue(cells[row][col].value);
|
||||
setIsEditing(true);
|
||||
// Фокус на input ячейки и курсор в конец
|
||||
@@ -56,7 +106,7 @@ const Spreadsheet: FC = () => {
|
||||
// Устанавливаем курсор в конец текста
|
||||
input.setSelectionRange(input.value.length, input.value.length);
|
||||
}
|
||||
}, 0);
|
||||
}, 10); // Увеличиваем задержку для надежности
|
||||
};
|
||||
|
||||
const moveSelection = (deltaRow: number, deltaCol: number) => {
|
||||
@@ -71,7 +121,7 @@ const Spreadsheet: FC = () => {
|
||||
|
||||
const startEditing = (focusTarget?: 'cell' | 'formula') => {
|
||||
if (!selectedCell) return;
|
||||
// Сохраняем текущее значение для возможности отмены
|
||||
// Сохраняем текущее значение из UI для возможности отмены
|
||||
setEditingValue(cells[selectedCell.row][selectedCell.col].value);
|
||||
setIsEditing(true);
|
||||
|
||||
@@ -98,23 +148,37 @@ const Spreadsheet: FC = () => {
|
||||
const stopEditing = (save: boolean = true) => {
|
||||
if (!save && selectedCell) {
|
||||
// Отменяем изменения - возвращаем старое значение
|
||||
handleCellChange(selectedCell.row, selectedCell.col, editingValue);
|
||||
setCells(prev => {
|
||||
const newCells = [...prev];
|
||||
newCells[selectedCell.row][selectedCell.col] = {
|
||||
...newCells[selectedCell.row][selectedCell.col],
|
||||
value: editingValue
|
||||
};
|
||||
return newCells;
|
||||
});
|
||||
setFormulaBarValue(editingValue);
|
||||
// Восстанавливаем значение в движке
|
||||
setEngineCellValue(selectedCell.row, selectedCell.col, editingValue);
|
||||
}
|
||||
setIsEditing(false);
|
||||
containerRef.current?.focus();
|
||||
};
|
||||
|
||||
const handleCellChange = (row: number, col: number, value: string) => {
|
||||
// Обновляем локальное состояние для UI (сырое значение)
|
||||
setCells(prev => {
|
||||
const newCells = [...prev];
|
||||
newCells[row][col] = { ...newCells[row][col], value };
|
||||
return newCells;
|
||||
});
|
||||
|
||||
// Обновляем formula bar если изменяется текущая выбранная ячейка
|
||||
if (selectedCell && selectedCell.row === row && selectedCell.col === col) {
|
||||
setFormulaBarValue(value);
|
||||
}
|
||||
|
||||
// Обновляем значение в движке (это может быть медленно, поэтому делаем в конце)
|
||||
setEngineCellValue(row, col, value);
|
||||
};
|
||||
|
||||
const getColumnLabel = (index: number) => {
|
||||
@@ -178,7 +242,7 @@ const Spreadsheet: FC = () => {
|
||||
e.preventDefault();
|
||||
moveSelection(0, 1);
|
||||
break;
|
||||
case 'Enter':
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
if (selectedCell) {
|
||||
setEditingValue(cells[selectedCell.row][selectedCell.col].value);
|
||||
@@ -193,7 +257,7 @@ const Spreadsheet: FC = () => {
|
||||
}, 0);
|
||||
}
|
||||
break;
|
||||
case 'F2':
|
||||
case 'F2':
|
||||
e.preventDefault();
|
||||
if (selectedCell) {
|
||||
setEditingValue(cells[selectedCell.row][selectedCell.col].value);
|
||||
@@ -251,6 +315,11 @@ const Spreadsheet: FC = () => {
|
||||
<div className="text-sm text-gray-600 min-w-[60px]">
|
||||
{selectedCell ? `${getColumnLabel(selectedCell.col)}${selectedCell.row + 1}` : ''}
|
||||
</div>
|
||||
{isCalculating && (
|
||||
<div className="text-xs text-blue-600 animate-pulse">
|
||||
Вычисляется...
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<input
|
||||
ref={formulaBarRef}
|
||||
@@ -382,6 +451,11 @@ const Spreadsheet: FC = () => {
|
||||
: {})
|
||||
}}
|
||||
onClick={() => {
|
||||
// Если в режиме редактирования и это активная ячейка, не обрабатываем клик
|
||||
if (isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleCellClick(rowIndex, colIndex);
|
||||
// Фокусируемся на контейнере, чтобы работали клавиши
|
||||
containerRef.current?.focus();
|
||||
@@ -390,8 +464,14 @@ const Spreadsheet: FC = () => {
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full h-full px-1 text-sm bg-transparent border-none outline-none resize-none cursor-text"
|
||||
value={cell.value}
|
||||
className={`w-full h-full px-1 text-sm bg-transparent border-none outline-none resize-none ${
|
||||
isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex
|
||||
? 'cursor-text'
|
||||
: 'cursor-cell'
|
||||
}`}
|
||||
value={isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex
|
||||
? cell.value
|
||||
: String(getCellValue(rowIndex, colIndex) || '')}
|
||||
onChange={(e) => {
|
||||
handleCellChange(rowIndex, colIndex, e.target.value);
|
||||
setFormulaBarValue(e.target.value);
|
||||
@@ -416,6 +496,11 @@ const Spreadsheet: FC = () => {
|
||||
}
|
||||
}}
|
||||
onClick={(e) => {
|
||||
// Если в режиме редактирования и это активная ячейка, не обрабатываем клик
|
||||
if (isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
setSelectedCell({ row: rowIndex, col: colIndex });
|
||||
setIsEditing(false); // Явно отключаем режим редактирования при клике
|
||||
@@ -430,9 +515,22 @@ const Spreadsheet: FC = () => {
|
||||
// Фокус уже на input, устанавливаем курсор в конец
|
||||
setTimeout(() => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
input.focus();
|
||||
input.setSelectionRange(input.value.length, input.value.length);
|
||||
}, 0);
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
// В режиме редактирования разрешаем нормальное поведение мыши
|
||||
if (isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
// В режиме редактирования разрешаем нормальное поведение мыши
|
||||
if (isEditing && selectedCell?.row === rowIndex && selectedCell?.col === colIndex) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
readOnly={!isEditing || selectedCell?.row !== rowIndex || selectedCell?.col !== colIndex}
|
||||
ref={(el) => {
|
||||
if (inputRefs.current[rowIndex]) {
|
||||
|
||||
Reference in New Issue
Block a user