AI рефакторинг !
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useMultiSheetEngine } from '@/hook/useMultiSheetEngine'
|
||||
import { useSheetAutoSave } from '@/hook/useSheetAutoSave'
|
||||
import { getFileData, getLatestFileForTemplate } from '@/service/fileApiSevice'
|
||||
import { getFileData, getLatestFileForTemplate } from '@/service/fileApiService'
|
||||
import { produce } from 'immer'
|
||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
@@ -24,25 +24,36 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
templateId,
|
||||
onEngineReady,
|
||||
}) => {
|
||||
// Мемоизируем конфигурацию листов
|
||||
const sheetsConfig = useMemo(
|
||||
() => [
|
||||
{ name: SHEET_NAMES.report, rows: ROW_COUNT, cols: COL_COUNT },
|
||||
{ name: SHEET_NAMES.calculations, rows: ROW_COUNT, cols: COL_COUNT },
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
const { revision, updateRevision, setTemplateData, ...multiSheetEngine } =
|
||||
useMultiSheetEngine({
|
||||
sheets: [
|
||||
{ name: SHEET_NAMES.report, rows: ROW_COUNT, cols: COL_COUNT },
|
||||
{ name: SHEET_NAMES.calculations, rows: ROW_COUNT, cols: COL_COUNT },
|
||||
],
|
||||
sheets: sheetsConfig,
|
||||
initialData: templateData || {},
|
||||
})
|
||||
|
||||
// Мемоизируем объект для передачи родительскому компоненту
|
||||
const engineAPI = useMemo(
|
||||
() => ({
|
||||
...multiSheetEngine,
|
||||
updateRevision,
|
||||
}),
|
||||
[multiSheetEngine, updateRevision]
|
||||
)
|
||||
|
||||
// Передаем методы движка родительскому компоненту
|
||||
useEffect(() => {
|
||||
if (onEngineReady) {
|
||||
onEngineReady({
|
||||
...multiSheetEngine,
|
||||
updateRevision,
|
||||
})
|
||||
onEngineReady(engineAPI)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [onEngineReady])
|
||||
}, [onEngineReady, engineAPI])
|
||||
|
||||
const [activeSheet, setActiveSheet] = useState<SheetType>('report')
|
||||
const [selectedCell, setSelectedCell] = useState<{
|
||||
@@ -56,31 +67,61 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
Record<string, Record<string, any>>
|
||||
>({}) // Базовые данные файла
|
||||
|
||||
const [columnWidths, setColumnWidths] = useState<Record<SheetType, number[]>>(
|
||||
// Мемоизируем начальное состояние ширин колонок
|
||||
const initialColumnWidths = useMemo(
|
||||
() => ({
|
||||
report: Array(COL_COUNT).fill(96),
|
||||
calculations: Array(COL_COUNT).fill(96),
|
||||
})
|
||||
}),
|
||||
[]
|
||||
)
|
||||
const gridRefs = useRef<Record<SheetType, any>>({
|
||||
report: null,
|
||||
calculations: null,
|
||||
})
|
||||
|
||||
const containerRefs = useRef<Record<SheetType, HTMLDivElement | null>>({
|
||||
report: null,
|
||||
calculations: null,
|
||||
})
|
||||
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 activeCellInputRef = useRef<HTMLInputElement>(null)
|
||||
const formulaBarRef = useRef<HTMLInputElement | null>(null)
|
||||
const headerRefs = useRef<Record<SheetType, HTMLDivElement | null>>({
|
||||
report: null,
|
||||
calculations: null,
|
||||
})
|
||||
const rowHeaderRefs = useRef<Record<SheetType, HTMLDivElement | null>>({
|
||||
report: null,
|
||||
calculations: null,
|
||||
})
|
||||
const initialTemplateValues = useRef<Record<string, string>>({})
|
||||
|
||||
const cellDataCache = useRef<Record<string, Record<string, CachedCellData>>>(
|
||||
@@ -88,45 +129,43 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
)
|
||||
const lastRevision = useRef<number>(-1)
|
||||
|
||||
useEffect(() => {
|
||||
// Используем baseFileData как источник базовых значений для сравнения
|
||||
const reportBaseData = baseFileData?.[SHEET_NAMES.report]
|
||||
// Мемоизируем обработчик обновления начальных значений
|
||||
const updateInitialValues = useCallback(() => {
|
||||
// Обновляем начальные значения только если есть данные
|
||||
const hasBaseFileData = Object.keys(baseFileData).length > 0
|
||||
const hasTemplateData = Object.keys(templateData).length > 0
|
||||
|
||||
if (reportBaseData && Object.keys(reportBaseData).length > 0) {
|
||||
// Очищаем старые значения
|
||||
initialTemplateValues.current = {}
|
||||
|
||||
Object.entries(reportBaseData).forEach(([cellName, value]) => {
|
||||
initialTemplateValues.current[cellName] = String(value)
|
||||
if (hasBaseFileData) {
|
||||
Object.entries(baseFileData).forEach(([sheetName, sheetData]) => {
|
||||
Object.entries(sheetData).forEach(([cellAddress, value]) => {
|
||||
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 {
|
||||
// Fallback: используем templateData, если baseFileData еще не загружен
|
||||
const reportTemplate = templateData?.[SHEET_NAMES.report]
|
||||
|
||||
if (reportTemplate && Object.keys(reportTemplate).length > 0) {
|
||||
// Очищаем старые значения
|
||||
initialTemplateValues.current = {}
|
||||
|
||||
Object.entries(reportTemplate).forEach(([cellName, value]) => {
|
||||
initialTemplateValues.current[cellName] = String(value)
|
||||
})
|
||||
|
||||
console.log(
|
||||
'✅ Загружено значений templateData для сравнения (fallback):',
|
||||
Object.keys(initialTemplateValues.current).length
|
||||
)
|
||||
} else {
|
||||
console.log('⚠️ DualSpreadsheet: Нет базовых данных для сравнения')
|
||||
}
|
||||
console.log('⚠️ DualSpreadsheet: Нет базовых данных для сравнения')
|
||||
}
|
||||
}, [baseFileData, templateData])
|
||||
|
||||
useEffect(() => {
|
||||
updateInitialValues()
|
||||
}, [updateInitialValues])
|
||||
|
||||
// Мемоизируем обработчик скролла к выбранной ячейке
|
||||
const scrollToSelectedCell = useCallback(() => {
|
||||
if (selectedCell && activeSheet) {
|
||||
const grid = gridRefs.current[activeSheet]
|
||||
grid?.scrollToItem({
|
||||
@@ -137,6 +176,10 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
}
|
||||
}, [selectedCell, activeSheet])
|
||||
|
||||
useEffect(() => {
|
||||
scrollToSelectedCell()
|
||||
}, [scrollToSelectedCell])
|
||||
|
||||
const getCachedCellData = useCallback(
|
||||
(sheetType: SheetType): Record<string, CachedCellData> => {
|
||||
const cacheKey = `${sheetType}-${revision}-${JSON.stringify(columnWidths[sheetType])}`
|
||||
@@ -522,10 +565,28 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
[]
|
||||
)
|
||||
|
||||
const [spreadsheetWidths, setSpreadsheetWidths] = useState({
|
||||
L: 0.5,
|
||||
R: 0.5,
|
||||
})
|
||||
// Мемоизируем состояние ширин таблиц
|
||||
const initialSpreadsheetWidths = useMemo(
|
||||
() => ({
|
||||
L: 0.5,
|
||||
R: 0.5,
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
const [spreadsheetWidths, setSpreadsheetWidths] = useState(
|
||||
initialSpreadsheetWidths
|
||||
)
|
||||
|
||||
// Мемоизируем опции автосохранения
|
||||
const autoSaveOptions = useMemo(
|
||||
() => ({
|
||||
templateId,
|
||||
autoSaveDelay: 2000,
|
||||
enableAutoSave: true,
|
||||
}),
|
||||
[templateId]
|
||||
)
|
||||
|
||||
// Используем новый хук автосохранения
|
||||
const {
|
||||
@@ -541,11 +602,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
multiSheetEngine.getAllModifiedCells,
|
||||
multiSheetEngine.isCalculating,
|
||||
revision,
|
||||
{
|
||||
templateId,
|
||||
autoSaveDelay: 2000,
|
||||
enableAutoSave: true,
|
||||
}
|
||||
autoSaveOptions
|
||||
)
|
||||
|
||||
// Отладка templateId
|
||||
@@ -574,153 +631,138 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
|
||||
const separatorRef = useDraggableDivider(handleDividerDrag)
|
||||
|
||||
// Загружаем сохраненные формулы при инициализации
|
||||
useEffect(() => {
|
||||
// Мемоизируем функцию инициализации данных
|
||||
const initializeData = useCallback(async () => {
|
||||
if (!templateId || isInitialized) return
|
||||
|
||||
// Асинхронная функция для загрузки и слияния данных
|
||||
const initializeData = async () => {
|
||||
let finalBaseData: Record<string, Record<string, any>> = {}
|
||||
let finalBaseData: Record<string, Record<string, any>> = {}
|
||||
|
||||
// 1. ЗАГРУЖАЕМ БАЗОВЫЕ ДАННЫЕ ФАЙЛА
|
||||
if (templateId) {
|
||||
try {
|
||||
console.log('🔍 Ищем файлы для templateId:', templateId)
|
||||
const latestFile = await getLatestFileForTemplate(templateId)
|
||||
|
||||
if (latestFile) {
|
||||
console.log(
|
||||
'📄 Найден файл:',
|
||||
latestFile.name,
|
||||
'ID:',
|
||||
latestFile.id
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. ИСПОЛЬЗУЕМ templateData КАК FALLBACK или основные данные
|
||||
const hasTemplateData =
|
||||
Object.keys(templateData).length > 0 &&
|
||||
Object.values(templateData).some(sheet => Object.keys(sheet).length > 0)
|
||||
|
||||
if (hasTemplateData && Object.keys(finalBaseData).length === 0) {
|
||||
finalBaseData = templateData
|
||||
setBaseFileData(finalBaseData)
|
||||
console.log(
|
||||
'📄 Используем templateData как базовые данные:',
|
||||
finalBaseData
|
||||
)
|
||||
setTemplateData(templateData)
|
||||
}
|
||||
|
||||
// 3. ЗАГРУЖАЕМ ПОЛЬЗОВАТЕЛЬСКИЕ ИЗМЕНЕНИЯ ИЗ API
|
||||
// 1. ЗАГРУЖАЕМ БАЗОВЫЕ ДАННЫЕ ФАЙЛА
|
||||
if (templateId) {
|
||||
try {
|
||||
console.log('🔍 Загружаем пользовательские изменения из API...')
|
||||
const apiSheets = await loadSheets()
|
||||
const latestFile = await getLatestFileForTemplate(templateId)
|
||||
if (latestFile) {
|
||||
console.log(
|
||||
'📁 Найден последний файл для шаблона:',
|
||||
latestFile.filename
|
||||
)
|
||||
const fileData = await getFileData(latestFile.id)
|
||||
|
||||
// Преобразуем листы API в формат данных
|
||||
const userChanges: Record<string, Record<string, any>> = {}
|
||||
Object.entries(apiSheets).forEach(([sheetName, sheet]) => {
|
||||
// Нормализуем имя листа: "Report" -> "L"
|
||||
const normalizedName =
|
||||
sheetName === 'Report'
|
||||
? SHEET_NAMES.report
|
||||
: sheetName === 'Calculations'
|
||||
? SHEET_NAMES.calculations
|
||||
: sheetName
|
||||
|
||||
// Интересуют только листы L и R
|
||||
if (
|
||||
(normalizedName === SHEET_NAMES.report ||
|
||||
normalizedName === SHEET_NAMES.calculations) &&
|
||||
sheet.cells &&
|
||||
Object.keys(sheet.cells).length > 0
|
||||
) {
|
||||
userChanges[normalizedName] = sheet.cells
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. ИСПОЛЬЗУЕМ templateData КАК FALLBACK или основные данные
|
||||
const hasTemplateData =
|
||||
Object.keys(templateData).length > 0 &&
|
||||
Object.values(templateData).some(sheet => Object.keys(sheet).length > 0)
|
||||
|
||||
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 в формат данных
|
||||
const userChanges: Record<string, Record<string, any>> = {}
|
||||
Object.entries(apiSheets).forEach(([sheetName, sheet]) => {
|
||||
// Нормализуем имя листа: "Report" -> "L"
|
||||
const normalizedName =
|
||||
sheetName === 'Report'
|
||||
? SHEET_NAMES.report
|
||||
: sheetName === 'Calculations'
|
||||
? SHEET_NAMES.calculations
|
||||
: sheetName
|
||||
|
||||
// Интересуют только листы L и R
|
||||
if (
|
||||
(normalizedName === SHEET_NAMES.report ||
|
||||
normalizedName === SHEET_NAMES.calculations) &&
|
||||
sheet.cells &&
|
||||
Object.keys(sheet.cells).length > 0
|
||||
) {
|
||||
userChanges[normalizedName] = sheet.cells
|
||||
}
|
||||
})
|
||||
|
||||
console.log(
|
||||
'📂 Загружены пользовательские изменения:',
|
||||
Object.keys(userChanges)
|
||||
)
|
||||
|
||||
// 4. ОБЪЕДИНЯЕМ БАЗОВЫЕ ДАННЫЕ С ПОЛЬЗОВАТЕЛЬСКИМИ ИЗМЕНЕНИЯМИ
|
||||
if (Object.keys(userChanges).length > 0) {
|
||||
console.log(
|
||||
'🔄 Объединяем базовые данные с пользовательскими изменениями'
|
||||
)
|
||||
|
||||
// Создаем объединенные данные: базовые данные файла + пользовательские изменения
|
||||
const mergedData: Record<string, Record<string, any>> = {}
|
||||
|
||||
// Копируем базовые данные файла как основу
|
||||
Object.entries(finalBaseData).forEach(([sheetName, sheetData]) => {
|
||||
mergedData[sheetName] = { ...sheetData }
|
||||
})
|
||||
|
||||
// Применяем пользовательские изменения поверх базовых данных
|
||||
Object.entries(userChanges).forEach(([sheetName, sheetData]) => {
|
||||
if (!mergedData[sheetName]) {
|
||||
mergedData[sheetName] = {}
|
||||
}
|
||||
Object.entries(sheetData).forEach(([cellAddress, value]) => {
|
||||
mergedData[sheetName][cellAddress] = value
|
||||
})
|
||||
})
|
||||
|
||||
console.log(
|
||||
'📂 Загружены пользовательские изменения:',
|
||||
Object.keys(userChanges)
|
||||
'📊 Загружаем объединенные данные (базовые + пользовательские)'
|
||||
)
|
||||
|
||||
// 4. ОБЪЕДИНЯЕМ БАЗОВЫЕ ДАННЫЕ С ПОЛЬЗОВАТЕЛЬСКИМИ ИЗМЕНЕНИЯМИ
|
||||
if (Object.keys(userChanges).length > 0) {
|
||||
console.log(
|
||||
'🔄 Объединяем базовые данные с пользовательскими изменениями'
|
||||
)
|
||||
|
||||
// Создаем объединенные данные: базовые данные файла + пользовательские изменения
|
||||
const mergedData: Record<string, Record<string, any>> = {}
|
||||
|
||||
// Копируем базовые данные файла как основу
|
||||
Object.entries(finalBaseData).forEach(([sheetName, sheetData]) => {
|
||||
mergedData[sheetName] = { ...sheetData }
|
||||
})
|
||||
|
||||
// Применяем пользовательские изменения поверх базовых данных
|
||||
Object.entries(userChanges).forEach(([sheetName, sheetData]) => {
|
||||
if (!mergedData[sheetName]) {
|
||||
mergedData[sheetName] = {}
|
||||
}
|
||||
Object.entries(sheetData).forEach(([cellAddress, value]) => {
|
||||
mergedData[sheetName][cellAddress] = value
|
||||
})
|
||||
})
|
||||
|
||||
console.log(
|
||||
'📊 Загружаем объединенные данные (базовые + пользовательские)'
|
||||
)
|
||||
multiSheetEngine.loadData(mergedData)
|
||||
|
||||
// Обновляем baseline, включая уже сохранённые изменения пользователя,
|
||||
// чтобы их не сохранять повторно
|
||||
} else if (Object.keys(finalBaseData).length > 0) {
|
||||
console.log(
|
||||
'📋 Используем только базовые данные (нет пользовательских изменений)'
|
||||
)
|
||||
multiSheetEngine.loadData(finalBaseData)
|
||||
} else {
|
||||
console.log('⚠️ Нет данных для загрузки')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'❌ Ошибка при загрузке пользовательских изменений:',
|
||||
error
|
||||
multiSheetEngine.loadData(mergedData)
|
||||
} else if (Object.keys(finalBaseData).length > 0) {
|
||||
console.log(
|
||||
'📋 Используем только базовые данные (нет пользовательских изменений)'
|
||||
)
|
||||
|
||||
// В случае ошибки используем только базовые данные
|
||||
if (Object.keys(finalBaseData).length > 0) {
|
||||
console.log('📋 Ошибка API, используем только базовые данные')
|
||||
multiSheetEngine.loadData(finalBaseData)
|
||||
}
|
||||
multiSheetEngine.loadData(finalBaseData)
|
||||
} else {
|
||||
console.log('⚠️ Нет данных для загрузки')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Ошибка при загрузке пользовательских изменений:', error)
|
||||
|
||||
setIsInitialized(true)
|
||||
// В случае ошибки используем только базовые данные
|
||||
if (Object.keys(finalBaseData).length > 0) {
|
||||
console.log('📋 Ошибка API, используем только базовые данные')
|
||||
multiSheetEngine.loadData(finalBaseData)
|
||||
}
|
||||
}
|
||||
|
||||
initializeData()
|
||||
setIsInitialized(true)
|
||||
}, [
|
||||
templateId,
|
||||
templateData,
|
||||
@@ -730,6 +772,11 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
isInitialized,
|
||||
])
|
||||
|
||||
// Загружаем сохраненные формулы при инициализации
|
||||
useEffect(() => {
|
||||
initializeData()
|
||||
}, [initializeData])
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col bg-background">
|
||||
<FormulaBar
|
||||
|
||||
Reference in New Issue
Block a user