починка багов

This commit is contained in:
2025-07-23 16:08:50 +03:00
parent 3fc352f8e9
commit 907b3e315e
24 changed files with 1249 additions and 1430 deletions

View File

@@ -1,6 +1,6 @@
import { useFileData, useLatestFileForTemplate } from '@/hook/useFileQueries'
import { useMultiSheetEngine } from '@/hook/useMultiSheetEngine'
import { useSheetAutoSave } from '@/hook/useSheetAutoSave'
import { getFileData, getLatestFileForTemplate } from '@/service/fileApiService'
import { produce } from 'immer'
import React, {
FC,
@@ -78,6 +78,10 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
row: number
col: number
} | null>(null)
// Флаг, что пользователь изменил хотя бы одну ячейку
const [userEdited, setUserEdited] = useState(false)
// Состояние для множественного выделения
const [multiSelection, setMultiSelection] = useState<CellSelection[]>([])
const [isDragging, setIsDragging] = useState(false)
@@ -95,7 +99,14 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
>({}) // Базовые данные файла
const [highlightedCells, setHighlightedCells] = useState<HighlightedCell[]>(
[]
) // Подсвеченные ячейки для формул
)
// React Query хуки для оптимизации API запросов
const { data: latestFile, isLoading: isLoadingLatestFile } =
useLatestFileForTemplate(templateId)
const { data: fileData, isLoading: isLoadingFileData } = useFileData(
latestFile?.id
)
// Мемоизируем начальное состояние ширин колонок
const initialColumnWidths = useMemo(
@@ -109,46 +120,22 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
const [columnWidths, setColumnWidths] =
useState<Record<SheetType, number[]>>(initialColumnWidths)
// Мемоизируем начальные refs объекты
const initialGridRefs = useMemo(
() => ({
report: null,
calculations: null,
}),
[]
)
const initialContainerRefs = useMemo(
() => ({
report: null,
calculations: null,
}),
[]
)
const initialHeaderRefs = useMemo(
() => ({
report: null,
calculations: null,
}),
[]
)
const initialRowHeaderRefs = useMemo(
() => ({
report: null,
calculations: null,
}),
[]
)
const gridRefs = useRef<Record<SheetType, any>>(initialGridRefs)
const containerRefs =
useRef<Record<SheetType, HTMLDivElement | null>>(initialContainerRefs)
const headerRefs =
useRef<Record<SheetType, HTMLDivElement | null>>(initialHeaderRefs)
const rowHeaderRefs =
useRef<Record<SheetType, HTMLDivElement | null>>(initialRowHeaderRefs)
const gridRefs = useRef<Record<SheetType, any>>({
report: null,
calculations: null,
})
const containerRefs = useRef<Record<SheetType, HTMLDivElement | null>>({
report: null,
calculations: null,
})
const headerRefs = useRef<Record<SheetType, HTMLDivElement | null>>({
report: null,
calculations: null,
})
const rowHeaderRefs = useRef<Record<SheetType, HTMLDivElement | null>>({
report: null,
calculations: null,
})
const activeCellInputRef = useRef<HTMLInputElement | null>(null)
const formulaBarRef = useRef<HTMLInputElement | null>(null)
@@ -171,22 +158,12 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
initialTemplateValues.current[cellAddress] = String(value || '')
})
})
// console.log(
// '✅ Загружено значений baseFileData для сравнения (приоритет):',
// Object.keys(initialTemplateValues.current).length
//)
} else if (hasTemplateData) {
Object.entries(templateData).forEach(([sheetName, sheetData]) => {
Object.entries(sheetData).forEach(([cellAddress, value]) => {
initialTemplateValues.current[cellAddress] = String(value || '')
})
})
// console.log(
// '✅ Загружено значений templateData для сравнения (fallback):',
// Object.keys(initialTemplateValues.current).length
// )
} else {
// console.log('⚠️ DualSpreadsheet: Нет базовых данных для сравнения')
}
}, [baseFileData, templateData])
@@ -212,7 +189,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
const getCachedCellData = useCallback(
(sheetType: SheetType): Record<string, CachedCellData> => {
const cacheKey = `${sheetType}-${revision}-${JSON.stringify(columnWidths[sheetType])}`
const cacheKey = `${sheetType}-${revision}-${columnWidths[sheetType].join('-')}`
if (cellDataCache.current[cacheKey]) {
return cellDataCache.current[cacheKey]
@@ -262,19 +239,6 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
const cellName = getColumnLabel(col) + (row + 1)
const baseFileValue = initialTemplateValues.current[cellName] ?? '' // Базовое значение из файла
// Отладка только для измененных ячеек
if (
sheetType === 'report' &&
baseFileValue &&
rawValue !== baseFileValue
) {
// console.log(`🔧 Измененная ячейка ${cellName}:`, {
// baseFileValue,
// rawValue,
// isModified: true,
// })
}
// Нормализуем значения для сравнения
const normalizeValue = (value: any): string => {
if (value === null || value === undefined) return ''
@@ -290,15 +254,6 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
const isModified =
sheetType === 'report' && normalizedRaw !== normalizedBaseFile
// Отладка только для измененных ячеек
if (sheetType === 'report' && isModified) {
// console.log(`🔍 Измененная ячейка ${cellName}:`, {
// baseFileValue,
// rawValue,
// isModified,
// })
}
cachedData[cellKey] = {
displayValue,
rawValue,
@@ -343,11 +298,6 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
)
setHighlightedCells(highlights)
// console.log('🎨 Подсветка ячеек для формулы:', formula, {
// references,
// highlights,
// })
} catch (error) {
console.error('Ошибка при обновлении подсветки ячеек:', error)
setHighlightedCells([])
@@ -422,23 +372,6 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
(save: boolean) => {
if (save && selectedCell) {
const sheetName = SHEET_NAMES[activeSheet]
const cellName =
getColumnLabel(selectedCell.col) + (selectedCell.row + 1)
const oldValue = multiSheetEngine.getCellRawValue(
sheetName,
selectedCell.row,
selectedCell.col
)
const baseFileValue = initialTemplateValues.current[cellName] ?? '' // Базовое значение из файла
// console.log('💾 Сохранение ячейки:', {
//cellName,
//oldValue,
//newValue: editingValue,
//baseFileValue,
//isChanged: oldValue !== editingValue,
//isModifiedFromBaseFile: baseFileValue !== editingValue,
//})
multiSheetEngine.setCellValueWithoutRecalc(
sheetName,
@@ -461,23 +394,25 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
(newValue: string) => {
if (isEditing) {
setEditingValue(newValue)
if (!userEdited) setUserEdited(true)
}
},
[isEditing]
[isEditing, userEdited]
)
const handleClearCell = useCallback(() => {
if (!selectedCell) return
const sheetName = SHEET_NAMES[activeSheet]
multiSheetEngine.setCellValueWithoutRecalc(
sheetName,
selectedCell.row,
selectedCell.col,
''
)
setUserEdited(true)
updateRevision()
multiSheetEngine.debouncedRecalc()
// Сбрасываем множественное выделение при очистке одиночной ячейки
setMultiSelection([])
}, [activeSheet, selectedCell, multiSheetEngine, updateRevision])
@@ -515,22 +450,19 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
const sheetName = SHEET_NAMES[activeSheet]
const cellsToDelete = getAllCellsInSelections(multiSelection)
cellsToDelete.forEach(({ row, col }: { row: number; col: number }) => {
cellsToDelete.forEach(({ row, col }) => {
multiSheetEngine.setCellValueWithoutRecalc(sheetName, row, col, '')
})
setUserEdited(true)
updateRevision()
multiSheetEngine.debouncedRecalc()
// Очищаем выделение после удаления
setMultiSelection([])
}, [multiSelection, activeSheet, multiSheetEngine, updateRevision])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (isEditing) {
return
}
if (isEditing) return
if (!selectedCell && e.key.match(/^(Arrow|Enter|F2|Delete|Backspace)$/)) {
moveSelection(0, 0)
@@ -595,6 +527,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
setEditingSource('cell')
setEditingValue(e.key)
setIsEditing(true)
if (!userEdited) setUserEdited(true)
}
},
[
@@ -610,13 +543,6 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
const handleCellClick = useCallback(
(row: number, col: number, event?: React.MouseEvent) => {
// console.log('🖱️ Обычный клик по ячейке:', {
// row,
//col,
//isEditing,
//activeSheet,
//})
if (isEditing) {
stopEditing(true)
}
@@ -710,44 +636,23 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
// Новый обработчик для кликов в режиме редактирования - вставляет ссылку
const handleCellReferenceClick = useCallback(
(targetSheetType: SheetType, row: number, col: number) => {
// console.log('🔗 Клик для вставки ссылки:', {
//targetSheetType,
//row,
//col,
//isEditing,
//selectedCell,
//editingValue,
//})
// Проверяем, что мы в режиме редактирования
if (!isEditing || !selectedCell) {
// console.log(
// '⚠️ Игнорируем клик ссылки: не в режиме редактирования или нет выбранной ячейки'
//)
if (!isEditing || !selectedCell || !editingValue.startsWith('=')) {
stopEditing(true)
setSelectedCell({ row, col })
return
}
// Создаем ссылку на ячейку
let cellReference: string
// Если это другой лист, делаем квалифицированную ссылку
if (targetSheetType !== activeSheet) {
cellReference = createQualifiedCellReference(targetSheetType, row, col)
// console.log('📋 Создана межлистовая ссылка:', cellReference)
} else {
// Если это тот же лист, делаем локальную ссылку
cellReference = createCellAddress(row, col)
// console.log('📋 Создана локальная ссылка:', cellReference)
}
// Добавляем ссылку к текущему значению редактирования
const newValue = editingValue + cellReference
// console.log('✏️ Новое значение формулы:', {
//oldValue: editingValue,
//newValue,
//})
setEditingValue(newValue)
if (!userEdited) setUserEdited(true)
if (editingSource === 'formula') {
if (formulaBarRef.current) {
@@ -795,13 +700,12 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
const handleFormulaBarChange = useCallback(
(newValue: string) => {
if (selectedCell) {
if (!isEditing) {
setIsEditing(true)
}
if (!isEditing) setIsEditing(true)
setEditingValue(newValue)
if (!userEdited) setUserEdited(true)
}
},
[selectedCell, isEditing]
[selectedCell, isEditing, userEdited]
)
const handleFormulaBarFocus = useCallback(() => {
@@ -860,32 +764,25 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
)
// Мемоизируем состояние ширин таблиц
const initialSpreadsheetWidths = useMemo(
() => ({
L: 0.5,
R: 0.5,
}),
[]
)
const [spreadsheetWidths, setSpreadsheetWidths] = useState(
initialSpreadsheetWidths
)
const [spreadsheetWidths, setSpreadsheetWidths] = useState({
L: 0.5,
R: 0.5,
})
// Мемоизируем опции автосохранения
const autoSaveOptions = useMemo(
() => ({
templateId,
autoSaveDelay: 2000,
enableAutoSave,
enableAutoSave: enableAutoSave && userEdited,
}),
[templateId, enableAutoSave]
[templateId, enableAutoSave, userEdited]
)
// Мемоизируем getAllModifiedCells для предотвращения частых ререндеров хука автосохранения
const memoizedGetAllModifiedCells = useCallback(
multiSheetEngine.getAllModifiedCells,
[multiSheetEngine.getAllModifiedCells]
[revision] // Используем revision вместо функции для более стабильной мемоизации
)
// Используем новый хук автосохранения
@@ -896,8 +793,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
lastSaveError,
manualSave,
clearError,
loadSavedSheets,
loadSheets, // Добавляем асинхронную функцию загрузки
loadSheets,
} = useSheetAutoSave(
memoizedGetAllModifiedCells,
multiSheetEngine.isCalculating,
@@ -924,33 +820,12 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
let finalBaseData: Record<string, Record<string, any>> = {}
// 1. ЗАГРУЖАЕМ БАЗОВЫЕ ДАННЫЕ ФАЙЛА
if (templateId) {
try {
const latestFile = await getLatestFileForTemplate(templateId)
if (latestFile) {
// console.log('📁 Найден последний файл для шаблона:', latestFile.name)
const fileData = await getFileData(latestFile.id)
// TODO: Пока getFileData возвращает пустые данные, используем templateData
// Когда API будет готов, здесь будут реальные данные файла
if (Object.keys(fileData.data).length > 0) {
finalBaseData = { [SHEET_NAMES.report]: fileData.data }
setBaseFileData(finalBaseData)
// Сохраняем baseline, чтобы в L попадали только пользовательские изменения
setTemplateData(finalBaseData)
// console.log('📄 Загружены базовые данные файла:', finalBaseData)
} else {
// console.log(
// '⚠️ getFileData вернул пустые данные, используем templateData'
//)
}
} else {
// console.log('⚠️ Файл не найден для templateId:', templateId)
}
} catch (error) {
console.error('❌ Ошибка при загрузке данных файла:', error)
}
// 1. ИСПОЛЬЗУЕМ ДАННЫЕ ИЗ REACT QUERY КЕША
if (fileData && Object.keys(fileData.data).length > 0) {
finalBaseData = { [SHEET_NAMES.report]: fileData.data }
setBaseFileData(finalBaseData)
// Сохраняем baseline, чтобы в листы попадали только пользовательские изменения
setTemplateData(finalBaseData)
}
// 2. ИСПОЛЬЗУЕМ templateData КАК FALLBACK или основные данные
@@ -961,16 +836,11 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
if (hasTemplateData && Object.keys(finalBaseData).length === 0) {
finalBaseData = templateData
setBaseFileData(finalBaseData)
// console.log(
// '📄 Используем templateData как базовые данные:',
// finalBaseData
//)
setTemplateData(templateData)
}
// 3. ЗАГРУЖАЕМ ПОЛЬЗОВАТЕЛЬСКИЕ ИЗМЕНЕНИЯ ИЗ API
try {
// console.log('🔍 Загружаем пользовательские изменения из API...')
const apiSheets = await loadSheets()
// Преобразуем листы API в формат данных
@@ -995,17 +865,8 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
}
})
// console.log(
// '📂 Загружены пользовательские изменения:',
// Object.keys(userChanges)
//)
// 4. ОБЪЕДИНЯЕМ БАЗОВЫЕ ДАННЫЕ С ПОЛЬЗОВАТЕЛЬСКИМИ ИЗМЕНЕНИЯМИ
if (Object.keys(userChanges).length > 0) {
// console.log(
// '🔄 Объединяем базовые данные с пользовательскими изменениями'
//)
// Создаем объединенные данные: базовые данные файла + пользовательские изменения
const mergedData: Record<string, Record<string, any>> = {}
@@ -1024,24 +885,15 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
})
})
// console.log(
// '📊 Загружаем объединенные данные (базовые + пользовательские)'
//)
multiSheetEngine.loadData(mergedData)
} else if (Object.keys(finalBaseData).length > 0) {
// console.log(
// '📋 Используем только базовые данные (нет пользовательских изменений)'
//)
multiSheetEngine.loadData(finalBaseData)
} else {
// console.log('⚠️ Нет данных для загрузки')
}
} catch (error) {
console.error('❌ Ошибка при загрузке пользовательских изменений:', error)
// В случае ошибки используем только базовые данные
if (Object.keys(finalBaseData).length > 0) {
// console.log('📋 Ошибка API, используем только базовые данные')
multiSheetEngine.loadData(finalBaseData)
}
}
@@ -1050,6 +902,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
}, [
templateId,
templateData,
fileData,
loadSheets,
multiSheetEngine,
setTemplateData,
@@ -1057,9 +910,22 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
])
// Загружаем сохраненные формулы при инициализации
// Ждем загрузки данных из React Query перед инициализацией
useEffect(() => {
initializeData()
}, [initializeData])
// Инициализируем только когда все данные загружены или их нет вообще
const canInitialize =
!isLoadingLatestFile && !isLoadingFileData && templateId && !isInitialized
if (canInitialize) {
initializeData()
}
}, [
initializeData,
isLoadingLatestFile,
isLoadingFileData,
templateId,
isInitialized,
])
return (
<div className="flex h-full flex-col bg-background">