237 lines
7.5 KiB
TypeScript
237 lines
7.5 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
||
import { useDebounce } from 'use-debounce'
|
||
import {
|
||
ApiSheet,
|
||
createSheet,
|
||
getSheetsByTemplate,
|
||
patchSheet,
|
||
} from '../service/sheetApiService'
|
||
|
||
export interface UseSheetAutoSaveOptions {
|
||
templateId?: string
|
||
autoSaveDelay?: number // задержка автосохранения в мс
|
||
enableAutoSave?: boolean
|
||
}
|
||
|
||
export const useSheetAutoSave = (
|
||
getAllModifiedCells: () => Record<string, Record<string, any>>,
|
||
isCalculating: boolean,
|
||
revision: number,
|
||
options: UseSheetAutoSaveOptions = {}
|
||
) => {
|
||
console.log('getAllModifiedCells', getAllModifiedCells())
|
||
const memoizedGetAllModifiedCells = useCallback(getAllModifiedCells, [
|
||
getAllModifiedCells,
|
||
])
|
||
|
||
const { templateId, autoSaveDelay = 2000, enableAutoSave = true } = options
|
||
|
||
// Состояние
|
||
const [lastSavedRevision, setLastSavedRevision] = useState(0)
|
||
const [isSaving, setIsSaving] = useState(false)
|
||
const [lastSaveError, setLastSaveError] = useState<string | null>(null)
|
||
const [sheets, setSheets] = useState<Record<string, ApiSheet>>({})
|
||
const [isLoading, setIsLoading] = useState(false)
|
||
|
||
// Дебаунсинг ревизии для автосохранения
|
||
const [debouncedRevision] = useDebounce(revision, autoSaveDelay)
|
||
|
||
// Загрузка листов для шаблона при инициализации
|
||
const loadSheets = useCallback(async () => {
|
||
if (!templateId) return {}
|
||
|
||
setIsLoading(true)
|
||
try {
|
||
const response = await getSheetsByTemplate(templateId)
|
||
|
||
const sheetsMap = response.sheets.reduce<Record<string, ApiSheet>>(
|
||
(acc, sheet) => {
|
||
const existingSheet = acc[sheet.name]
|
||
|
||
if (existingSheet) {
|
||
// Если лист уже есть, объединяем ячейки
|
||
acc[sheet.name] = {
|
||
...sheet, // берем актуальную версию листа
|
||
cells: {
|
||
...existingSheet.cells, // старые ячейки
|
||
...sheet.cells, // новые заменяют старые с тем же ключом
|
||
},
|
||
}
|
||
} else {
|
||
acc[sheet.name] = { ...sheet }
|
||
}
|
||
|
||
return acc
|
||
},
|
||
{}
|
||
)
|
||
|
||
setSheets(sheetsMap)
|
||
return sheetsMap
|
||
} catch (error) {
|
||
console.error('❌ Ошибка при загрузке листов:', error)
|
||
setLastSaveError('Ошибка загрузки листов')
|
||
return {}
|
||
} finally {
|
||
setIsLoading(false)
|
||
}
|
||
}, [templateId])
|
||
|
||
// Загрузка листов при изменении templateId
|
||
useEffect(() => {
|
||
if (templateId) {
|
||
loadSheets()
|
||
}
|
||
}, [templateId, loadSheets])
|
||
|
||
// Основная функция сохранения через API
|
||
const performSave = useCallback(
|
||
async (data: Record<string, Record<string, any>>) => {
|
||
if (!templateId) return
|
||
|
||
// Получаем список всех листов, которые нужно обработать:
|
||
// 1. Листы с данными (из data)
|
||
// 2. Существующие листы, которые могли быть очищены
|
||
const dataSheetNames = Object.keys(data)
|
||
const existingSheetNames = Object.keys(sheets)
|
||
const allSheetNames = new Set([...dataSheetNames, ...existingSheetNames])
|
||
|
||
if (allSheetNames.size === 0) return
|
||
|
||
setIsSaving(true)
|
||
setLastSaveError(null)
|
||
|
||
try {
|
||
const savePromises = Array.from(allSheetNames).map(async sheetName => {
|
||
const sheetCells = data[sheetName] || {} // если данных нет, используем пустой объект
|
||
const existingSheet = sheets[sheetName]
|
||
|
||
if (existingSheet) {
|
||
// Всегда сохраняем, даже если ячейки пустые (очистка листа)
|
||
await patchSheet(existingSheet.id, {
|
||
cells: sheetCells,
|
||
})
|
||
|
||
// Обновляем локальное состояние
|
||
setSheets(prev => ({
|
||
...prev,
|
||
[sheetName]: {
|
||
...existingSheet,
|
||
cells: sheetCells,
|
||
updated_at: new Date().toISOString(),
|
||
},
|
||
}))
|
||
} else {
|
||
// Создаем новый лист только если есть данные
|
||
if (Object.keys(sheetCells).length > 0) {
|
||
const newSheet = await createSheet({
|
||
name: sheetName,
|
||
cells: sheetCells,
|
||
template_id: templateId,
|
||
})
|
||
|
||
// Получаем полную информацию о созданном листе
|
||
const fullSheetData: ApiSheet = {
|
||
id: newSheet.id,
|
||
name: sheetName,
|
||
cells: sheetCells,
|
||
template_id: templateId,
|
||
created_at: new Date().toISOString(),
|
||
updated_at: new Date().toISOString(),
|
||
deleted_at: null,
|
||
}
|
||
|
||
// Обновляем локальное состояние
|
||
setSheets(prev => ({
|
||
...prev,
|
||
[sheetName]: fullSheetData,
|
||
}))
|
||
}
|
||
}
|
||
})
|
||
|
||
await Promise.all(savePromises)
|
||
setLastSavedRevision(revision)
|
||
} catch (error) {
|
||
const errorMessage =
|
||
error instanceof Error ? error.message : 'Неизвестная ошибка'
|
||
setLastSaveError(errorMessage)
|
||
console.error('❌ Ошибка при сохранении листов:', errorMessage)
|
||
} finally {
|
||
setIsSaving(false)
|
||
}
|
||
},
|
||
[templateId, sheets, revision]
|
||
)
|
||
|
||
// Автосохранение при изменении дебаунсированной ревизии
|
||
useEffect(() => {
|
||
const shouldAutoSave =
|
||
enableAutoSave &&
|
||
debouncedRevision > lastSavedRevision &&
|
||
!isCalculating &&
|
||
!isSaving &&
|
||
templateId
|
||
|
||
if (shouldAutoSave) {
|
||
console.log('✅ Выполняем автосохранение для ревизии:', debouncedRevision)
|
||
performSave(memoizedGetAllModifiedCells())
|
||
}
|
||
}, [
|
||
enableAutoSave,
|
||
debouncedRevision,
|
||
lastSavedRevision,
|
||
isCalculating,
|
||
isSaving,
|
||
templateId,
|
||
memoizedGetAllModifiedCells,
|
||
performSave,
|
||
])
|
||
|
||
// Ручное сохранение
|
||
const manualSave = useCallback(() => {
|
||
const currentData = memoizedGetAllModifiedCells()
|
||
performSave(currentData)
|
||
}, [memoizedGetAllModifiedCells, performSave])
|
||
|
||
// Функция загрузки сохраненных листов
|
||
const loadSavedSheets = useCallback((): Record<
|
||
string,
|
||
Record<string, any>
|
||
> => {
|
||
const result: Record<string, Record<string, any>> = {}
|
||
|
||
Object.entries(sheets).forEach(([sheetName, sheet]) => {
|
||
if (sheet.cells && Object.keys(sheet.cells).length > 0) {
|
||
result[sheetName] = sheet.cells
|
||
}
|
||
})
|
||
|
||
return result
|
||
}, [sheets])
|
||
|
||
// Вычисляемые значения
|
||
const isDataChanged = revision > lastSavedRevision
|
||
const hasUnsavedChanges = isDataChanged && !isSaving
|
||
|
||
return {
|
||
// Состояние
|
||
isSaving,
|
||
isLoading,
|
||
isDataChanged,
|
||
hasUnsavedChanges,
|
||
lastSaveError,
|
||
lastSavedRevision,
|
||
sheets,
|
||
|
||
// Методы
|
||
manualSave,
|
||
loadSavedSheets,
|
||
loadSheets,
|
||
|
||
// Утилиты
|
||
getCurrentData: memoizedGetAllModifiedCells,
|
||
clearError: () => setLastSaveError(null),
|
||
}
|
||
}
|