Files
protoc-frontend/src/hook/useSheetAutoSave.ts

237 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
}
}