группы

This commit is contained in:
Artem
2025-08-03 15:19:07 +03:00
parent 95e89b27bf
commit 4d69217967
17 changed files with 3549 additions and 84 deletions

View 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,
}
}