починка багов
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user