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>, 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(null) const [sheets, setSheets] = useState>({}) 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>( (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>) => { 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 > => { const result: Record> = {} 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), } }