группы
This commit is contained in:
381
src/hook/useTemplateOrderState.ts
Normal file
381
src/hook/useTemplateOrderState.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
import { patchTemplateApi } from '@/entity/template/api/templateApiService'
|
||||
import { generateInitialOrder, generateOrder } from '@/lib/template-order'
|
||||
import { Group, Template } from '@/type/template'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
interface TemplateOrderChange {
|
||||
templateId: string
|
||||
newOrder: number
|
||||
originalOrder: number
|
||||
newGroupId?: string | null
|
||||
originalGroupId?: string | null
|
||||
}
|
||||
|
||||
|
||||
|
||||
const EXPANDED_GROUPS_KEY = 'template-groups-expanded-state'
|
||||
|
||||
// Загрузить состояние сворачивания из localStorage
|
||||
function loadExpandedStatesFromStorage(): Map<string, boolean> {
|
||||
try {
|
||||
const stored = localStorage.getItem(EXPANDED_GROUPS_KEY)
|
||||
if (stored) {
|
||||
const parsed = JSON.parse(stored)
|
||||
return new Map(Object.entries(parsed))
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при загрузке состояния групп из localStorage:', error)
|
||||
}
|
||||
return new Map()
|
||||
}
|
||||
|
||||
// Сохранить состояние сворачивания в localStorage
|
||||
function saveExpandedStatesToStorage(states: Map<string, boolean>) {
|
||||
try {
|
||||
const obj = Object.fromEntries(states)
|
||||
localStorage.setItem(EXPANDED_GROUPS_KEY, JSON.stringify(obj))
|
||||
} catch (error) {
|
||||
console.warn('Ошибка при сохранении состояния групп в localStorage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export function useTemplateOrderState(templates: Template[], groups: Group[] = []) {
|
||||
const [orderChanges, setOrderChanges] = useState<Map<string, TemplateOrderChange>>(new Map())
|
||||
const [groupExpandedStates, setGroupExpandedStates] = useState<Map<string, boolean>>(() =>
|
||||
loadExpandedStatesFromStorage()
|
||||
)
|
||||
const [lastSaveError, setLastSaveError] = useState<string | null>(null)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
// Очистить состояния для несуществующих групп
|
||||
useEffect(() => {
|
||||
// Ждем пока группы загрузятся перед очисткой
|
||||
if (groups.length === 0) return
|
||||
|
||||
const existingGroupIds = new Set(groups.map(g => g.id))
|
||||
// Добавляем 'ungrouped' как валидный ключ
|
||||
existingGroupIds.add('ungrouped')
|
||||
|
||||
const currentStates = new Map(groupExpandedStates)
|
||||
let hasChanges = false
|
||||
|
||||
// Удаляем состояния для групп, которых больше нет
|
||||
for (const groupId of currentStates.keys()) {
|
||||
if (!existingGroupIds.has(groupId)) {
|
||||
currentStates.delete(groupId)
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
setGroupExpandedStates(currentStates)
|
||||
saveExpandedStatesToStorage(currentStates)
|
||||
}
|
||||
}, [groups, groupExpandedStates])
|
||||
|
||||
// Применить изменения порядка локально (для UI)
|
||||
const applyOrderChange = useCallback((templateId: string, newOrder: number, newGroupId?: string | null) => {
|
||||
const template = templates.find(t => t.id === templateId)
|
||||
if (!template) {
|
||||
console.log('❌ Template not found for applyOrderChange:', templateId)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('🔧 applyOrderChange called:', {
|
||||
templateId,
|
||||
templateName: template.name,
|
||||
currentOrder: template.order,
|
||||
newOrder,
|
||||
currentGroup: template.group_id,
|
||||
newGroupId
|
||||
})
|
||||
|
||||
setOrderChanges(prev => {
|
||||
const newChanges = new Map(prev)
|
||||
const originalOrder = template.order ?? 0
|
||||
const originalGroupId = template.group_id
|
||||
|
||||
console.log('📊 Order change details:', {
|
||||
templateId,
|
||||
originalOrder,
|
||||
newOrder,
|
||||
originalGroupId,
|
||||
newGroupId,
|
||||
orderChanged: newOrder !== originalOrder,
|
||||
groupChanged: newGroupId !== originalGroupId
|
||||
})
|
||||
|
||||
// Если ничего не изменилось, убираем изменение
|
||||
if (newOrder === originalOrder && newGroupId === originalGroupId) {
|
||||
console.log('🔄 No changes detected, removing from changes map')
|
||||
newChanges.delete(templateId)
|
||||
} else {
|
||||
const change = {
|
||||
templateId,
|
||||
newOrder,
|
||||
originalOrder,
|
||||
newGroupId,
|
||||
originalGroupId,
|
||||
}
|
||||
console.log('💾 Storing order change:', change)
|
||||
newChanges.set(templateId, change)
|
||||
}
|
||||
|
||||
console.log('📈 Current changes map size:', newChanges.size)
|
||||
console.log('📋 All current changes:', Array.from(newChanges.values()))
|
||||
|
||||
return newChanges
|
||||
})
|
||||
}, [templates])
|
||||
|
||||
// Получить актуальные шаблоны с примененными изменениями
|
||||
const getTemplatesWithChanges = useCallback(() => {
|
||||
const result = templates.map(template => {
|
||||
const change = orderChanges.get(template.id)
|
||||
if (change) {
|
||||
const modifiedTemplate = {
|
||||
...template,
|
||||
order: change.newOrder,
|
||||
group_id: change.newGroupId !== undefined ? change.newGroupId : template.group_id
|
||||
}
|
||||
|
||||
// Логируем только если есть изменения
|
||||
console.log('🔄 Template with applied changes:', {
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
originalOrder: template.order,
|
||||
newOrder: modifiedTemplate.order,
|
||||
originalGroup: template.group_id,
|
||||
newGroup: modifiedTemplate.group_id
|
||||
})
|
||||
|
||||
return modifiedTemplate
|
||||
}
|
||||
return template
|
||||
}).sort((a, b) => {
|
||||
// Правильная обработка null/undefined порядков
|
||||
const orderA = a.order ?? Number.MAX_SAFE_INTEGER
|
||||
const orderB = b.order ?? Number.MAX_SAFE_INTEGER
|
||||
return orderA - orderB
|
||||
})
|
||||
|
||||
return result
|
||||
}, [templates, orderChanges])
|
||||
|
||||
// Переключить состояние развертывания группы
|
||||
const toggleGroupExpanded = useCallback((groupId: string | null) => {
|
||||
// Используем специальный ключ для группы "Без группы"
|
||||
const storageKey = groupId || 'ungrouped'
|
||||
|
||||
setGroupExpandedStates(prev => {
|
||||
const newStates = new Map(prev)
|
||||
const currentState = newStates.get(storageKey) ?? true // По умолчанию развернуто
|
||||
const newState = !currentState
|
||||
newStates.set(storageKey, newState)
|
||||
|
||||
// Сохраняем в localStorage
|
||||
saveExpandedStatesToStorage(newStates)
|
||||
|
||||
return newStates
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Получить состояние развертывания группы
|
||||
const getGroupExpanded = useCallback((groupId: string | null) => {
|
||||
// Используем тот же ключ что и в toggleGroupExpanded
|
||||
const storageKey = groupId || 'ungrouped'
|
||||
return groupExpandedStates.get(storageKey) ?? true // По умолчанию развернуто
|
||||
}, [groupExpandedStates])
|
||||
|
||||
// Универсальная обработка drag & drop
|
||||
const handleDragEnd = useCallback((activeId: string, overId: string) => {
|
||||
if (activeId === overId) return
|
||||
|
||||
const templatesWithChanges = getTemplatesWithChanges()
|
||||
const activeTemplate = templatesWithChanges.find(t => t.id === activeId)
|
||||
|
||||
if (!activeTemplate) return
|
||||
|
||||
// Проверяем на специальные drop зоны для групп
|
||||
if (overId.startsWith('group-drop-')) {
|
||||
// Перетаскивание в группу (в конец)
|
||||
const targetGroupId = overId === 'group-drop-ungrouped' ? null : overId.replace('group-drop-', '')
|
||||
const targetGroupTemplates = templatesWithChanges.filter(t =>
|
||||
(t.group_id || null) === (targetGroupId || null)
|
||||
)
|
||||
const newOrder = generateInitialOrder(targetGroupTemplates, targetGroupId)
|
||||
applyOrderChange(activeId, newOrder, targetGroupId)
|
||||
} else {
|
||||
// Перетаскивание между шаблонами
|
||||
const overTemplate = templatesWithChanges.find(t => t.id === overId)
|
||||
if (!overTemplate) return
|
||||
|
||||
const targetGroupId = overTemplate.group_id
|
||||
const groupTemplates = templatesWithChanges.filter(t =>
|
||||
(t.group_id || null) === (targetGroupId || null)
|
||||
)
|
||||
|
||||
const newOrder = generateOrder(groupTemplates, activeId, overId)
|
||||
applyOrderChange(activeId, newOrder, targetGroupId)
|
||||
}
|
||||
}, [getTemplatesWithChanges, applyOrderChange])
|
||||
|
||||
// Переместить шаблон в группу
|
||||
const moveTemplateToGroup = useCallback((templateId: string, targetGroupId: string | null) => {
|
||||
const template = templates.find(t => t.id === templateId)
|
||||
if (!template) return
|
||||
|
||||
// Генерируем новый порядок для конца целевой группы
|
||||
const templatesWithChanges = getTemplatesWithChanges()
|
||||
const newOrder = generateInitialOrder(templatesWithChanges, targetGroupId)
|
||||
|
||||
applyOrderChange(templateId, newOrder, targetGroupId)
|
||||
}, [templates, getTemplatesWithChanges, applyOrderChange])
|
||||
|
||||
// Сохранить все изменения
|
||||
const saveOrderChanges = useCallback(async (
|
||||
updateTemplateOrder: (templateId: string, newOrder: number) => Promise<void>,
|
||||
moveTemplateToGroupFn: (templateId: string, groupId: string | null) => Promise<void>
|
||||
) => {
|
||||
if (orderChanges.size === 0) return
|
||||
|
||||
console.log('💾 SAVING ORDER CHANGES START')
|
||||
console.log('📋 Changes to save:', Array.from(orderChanges.values()).map(change => ({
|
||||
templateId: change.templateId,
|
||||
oldOrder: change.originalOrder,
|
||||
newOrder: change.newOrder,
|
||||
oldGroup: change.originalGroupId,
|
||||
newGroup: change.newGroupId
|
||||
})))
|
||||
|
||||
setIsSaving(true)
|
||||
setLastSaveError(null)
|
||||
|
||||
try {
|
||||
// Группируем изменения по типу
|
||||
const orderOnlyChanges: TemplateOrderChange[] = []
|
||||
const groupMoveChanges: TemplateOrderChange[] = []
|
||||
const combinedChanges: TemplateOrderChange[] = []
|
||||
|
||||
Array.from(orderChanges.values()).forEach(change => {
|
||||
if (change.newGroupId !== change.originalGroupId) {
|
||||
if (change.newOrder !== change.originalOrder) {
|
||||
// Изменились и группа, и порядок
|
||||
combinedChanges.push(change)
|
||||
} else {
|
||||
// Изменилась только группа
|
||||
groupMoveChanges.push(change)
|
||||
}
|
||||
} else {
|
||||
// Изменился только порядок
|
||||
orderOnlyChanges.push(change)
|
||||
}
|
||||
})
|
||||
|
||||
// Сначала перемещаем между группами (только изменения группы)
|
||||
if (groupMoveChanges.length > 0) {
|
||||
const groupMovePromises = groupMoveChanges.map(change =>
|
||||
moveTemplateToGroupFn(change.templateId, change.newGroupId || null)
|
||||
)
|
||||
await Promise.all(groupMovePromises)
|
||||
}
|
||||
|
||||
// Комбинированные изменения (группа + порядок) - отправляем один запрос
|
||||
if (combinedChanges.length > 0) {
|
||||
// Создаем новую функцию для комбинированного обновления
|
||||
const combinedPromises = combinedChanges.map(async (change) => {
|
||||
const template = templates.find(t => t.id === change.templateId)
|
||||
if (!template) throw new Error('Template not found')
|
||||
|
||||
console.log('🔧 Combined update (group + order):', {
|
||||
templateId: change.templateId,
|
||||
newGroupId: change.newGroupId,
|
||||
newOrder: change.newOrder
|
||||
})
|
||||
|
||||
// Отправляем один запрос с обновлением и группы, и порядка
|
||||
const patchInput = {
|
||||
name: template.name,
|
||||
group_id: change.newGroupId === null ? "ungroup" : change.newGroupId,
|
||||
order: change.newOrder
|
||||
}
|
||||
|
||||
// Используем внутренний API вместо двух отдельных вызовов
|
||||
const result = await patchTemplateApi(change.templateId, patchInput)
|
||||
console.log('Combined update successful:', result)
|
||||
return result
|
||||
})
|
||||
await Promise.all(combinedPromises)
|
||||
|
||||
// Инвалидируем кэш после комбинированных обновлений
|
||||
queryClient.invalidateQueries({ queryKey: ['templates'] })
|
||||
}
|
||||
|
||||
// Сохраняем изменения порядка
|
||||
if (orderOnlyChanges.length > 0) {
|
||||
console.log('Saving order changes:', orderOnlyChanges)
|
||||
const orderPromises = orderOnlyChanges.map(async (change) => {
|
||||
try {
|
||||
await updateTemplateOrder(change.templateId, change.newOrder)
|
||||
console.log(`Successfully updated order for template ${change.templateId}`)
|
||||
} catch (error) {
|
||||
console.error(`Failed to update order for template ${change.templateId}:`, error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
await Promise.all(orderPromises)
|
||||
}
|
||||
|
||||
console.log('✅ SAVE ORDER CHANGES SUCCESS - all changes applied')
|
||||
|
||||
// Очищаем все сохраненные изменения - они уже применены на сервере
|
||||
setOrderChanges(new Map())
|
||||
} catch (error) {
|
||||
console.error('Ошибка при сохранении изменений:', error)
|
||||
setLastSaveError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Неизвестная ошибка при сохранении изменений'
|
||||
)
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}, [orderChanges, queryClient])
|
||||
|
||||
// Отменить все изменения
|
||||
const resetOrderChanges = useCallback(() => {
|
||||
setOrderChanges(new Map())
|
||||
setLastSaveError(null)
|
||||
}, [])
|
||||
|
||||
// Очистить ошибку
|
||||
const clearError = useCallback(() => {
|
||||
setLastSaveError(null)
|
||||
}, [])
|
||||
|
||||
// Сбросить все состояния групп (полезно для отладки)
|
||||
const resetGroupStates = useCallback(() => {
|
||||
setGroupExpandedStates(new Map())
|
||||
saveExpandedStatesToStorage(new Map())
|
||||
}, [])
|
||||
|
||||
return {
|
||||
orderChanges,
|
||||
hasUnsavedChanges: orderChanges.size > 0,
|
||||
changedCount: orderChanges.size,
|
||||
lastSaveError,
|
||||
isSaving,
|
||||
getTemplatesWithChanges,
|
||||
applyOrderChange,
|
||||
handleDragEnd,
|
||||
moveTemplateToGroup,
|
||||
toggleGroupExpanded,
|
||||
getGroupExpanded,
|
||||
saveOrderChanges,
|
||||
resetOrderChanges,
|
||||
clearError,
|
||||
resetGroupStates,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user