дизайн групп
This commit is contained in:
@@ -36,7 +36,12 @@ interface DropZoneProps {
|
|||||||
isDragActive?: boolean
|
isDragActive?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropZone: React.FC<DropZoneProps> = ({ id, position, isVisible = true, isDragActive = false }) => {
|
const DropZone: React.FC<DropZoneProps> = ({
|
||||||
|
id,
|
||||||
|
position,
|
||||||
|
isVisible = true,
|
||||||
|
isDragActive = false,
|
||||||
|
}) => {
|
||||||
const { setNodeRef, isOver } = useDroppable({
|
const { setNodeRef, isOver } = useDroppable({
|
||||||
id,
|
id,
|
||||||
})
|
})
|
||||||
@@ -47,7 +52,7 @@ const DropZone: React.FC<DropZoneProps> = ({ id, position, isVisible = true, isD
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'absolute top-0 bottom-0 w-3 z-10',
|
'absolute bottom-0 top-0 z-10 w-3',
|
||||||
position === 'left' ? '-left-3' : '-right-3',
|
position === 'left' ? '-left-3' : '-right-3',
|
||||||
'transition-all duration-200 ease-in-out',
|
'transition-all duration-200 ease-in-out',
|
||||||
// Базовое состояние - полностью прозрачная зона
|
// Базовое состояние - полностью прозрачная зона
|
||||||
@@ -59,19 +64,23 @@ const DropZone: React.FC<DropZoneProps> = ({ id, position, isVisible = true, isD
|
|||||||
{/* Активный индикатор только при drag over */}
|
{/* Активный индикатор только при drag over */}
|
||||||
{isOver && (
|
{isOver && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
<div className={clsx(
|
<div
|
||||||
'h-full w-1 bg-primary rounded-full shadow-sm',
|
className={clsx(
|
||||||
'animate-in fade-in duration-200',
|
'h-full w-1 rounded-full bg-primary shadow-sm',
|
||||||
// Добавляем тонкий border для лучшей видимости
|
'duration-200 animate-in fade-in',
|
||||||
'ring-1 ring-primary/20'
|
// Добавляем тонкий border для лучшей видимости
|
||||||
)}>
|
'ring-1 ring-primary/20'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{/* Индикатор направления вставки */}
|
{/* Индикатор направления вставки */}
|
||||||
<div className="absolute top-1/2 -translate-y-1/2 -translate-x-1/2 left-1/2">
|
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
<div className={clsx(
|
<div
|
||||||
'w-2 h-2 bg-primary rounded-full',
|
className={clsx(
|
||||||
'border border-background shadow-sm',
|
'h-2 w-2 rounded-full bg-primary',
|
||||||
'animate-pulse'
|
'border border-background shadow-sm',
|
||||||
)} />
|
'animate-pulse'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +114,9 @@ const DraggableDroppableTemplateCard: React.FC<DraggableTemplateCardProps> = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
transform: dragTransform ? `translate3d(${dragTransform.x}px, ${dragTransform.y}px, 0)` : undefined,
|
transform: dragTransform
|
||||||
|
? `translate3d(${dragTransform.x}px, ${dragTransform.y}px, 0)`
|
||||||
|
: undefined,
|
||||||
zIndex: isDragging ? 1000 : 'auto',
|
zIndex: isDragging ? 1000 : 'auto',
|
||||||
opacity: isDragging ? 0.5 : 1,
|
opacity: isDragging ? 0.5 : 1,
|
||||||
}
|
}
|
||||||
@@ -122,7 +133,7 @@ const DraggableDroppableTemplateCard: React.FC<DraggableTemplateCardProps> = ({
|
|||||||
{...dragAttributes}
|
{...dragAttributes}
|
||||||
{...dragListeners}
|
{...dragListeners}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'transition-shadow duration-200 cursor-grab active:cursor-grabbing relative',
|
'relative cursor-grab transition-shadow duration-200 active:cursor-grabbing',
|
||||||
isDragging && 'shadow-xl'
|
isDragging && 'shadow-xl'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -179,11 +190,11 @@ const DroppableGroup: React.FC<DroppableGroupProps> = ({
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'transition-colors duration-200 relative',
|
'relative transition-colors duration-200',
|
||||||
templates.length === 0
|
templates.length === 0
|
||||||
? 'min-h-[120px] flex items-center justify-center'
|
? 'flex min-h-[120px] items-center justify-center'
|
||||||
: 'grid gap-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6',
|
: 'grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6',
|
||||||
isOver && templates.length === 0 && 'bg-primary/5 rounded-lg'
|
isOver && templates.length === 0 && 'rounded-lg bg-primary/5'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{templates.length > 0 ? (
|
{templates.length > 0 ? (
|
||||||
@@ -203,21 +214,25 @@ const DroppableGroup: React.FC<DroppableGroupProps> = ({
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
/* Минималистичный placeholder для пустой группы */
|
/* Минималистичный placeholder для пустой группы */
|
||||||
<div className={clsx(
|
<div
|
||||||
'flex items-center justify-center py-12 text-center rounded-lg transition-all duration-300',
|
className={clsx(
|
||||||
'border border-dashed border-muted-foreground/15',
|
'flex items-center justify-center rounded-lg py-12 text-center transition-all duration-300',
|
||||||
isOver
|
'border border-dashed border-muted-foreground/15',
|
||||||
? 'border-primary/50 bg-primary/5 scale-[1.02]'
|
isOver
|
||||||
: 'hover:border-muted-foreground/25 hover:bg-muted/20'
|
? 'scale-[1.02] border-primary/50 bg-primary/5'
|
||||||
)}>
|
: 'hover:border-muted-foreground/25 hover:bg-muted/20'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{isOver ? (
|
{isOver ? (
|
||||||
<div className={clsx(
|
<div
|
||||||
'flex items-center gap-3 px-6 py-3 rounded-md',
|
className={clsx(
|
||||||
'bg-background/90 backdrop-blur-sm',
|
'flex items-center gap-3 rounded-md px-6 py-3',
|
||||||
'border border-primary/30 shadow-lg',
|
'bg-background/90 backdrop-blur-sm',
|
||||||
'text-primary font-medium'
|
'border border-primary/30 shadow-lg',
|
||||||
)}>
|
'font-medium text-primary'
|
||||||
<div className="w-2 h-2 bg-primary rounded-full animate-pulse" />
|
)}
|
||||||
|
>
|
||||||
|
<div className="h-2 w-2 animate-pulse rounded-full bg-primary" />
|
||||||
Отпустите чтобы добавить в группу
|
Отпустите чтобы добавить в группу
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -269,7 +284,8 @@ export const TemplateGroup: React.FC<TemplateGroupProps> = ({
|
|||||||
const [internalIsExpanded, setInternalIsExpanded] = useState(true)
|
const [internalIsExpanded, setInternalIsExpanded] = useState(true)
|
||||||
|
|
||||||
// Используем внешнее состояние если передано, иначе внутреннее
|
// Используем внешнее состояние если передано, иначе внутреннее
|
||||||
const isExpanded = externalIsExpanded !== undefined ? externalIsExpanded : internalIsExpanded
|
const isExpanded =
|
||||||
|
externalIsExpanded !== undefined ? externalIsExpanded : internalIsExpanded
|
||||||
|
|
||||||
// Отладка: логируем получение шаблонов в группе
|
// Отладка: логируем получение шаблонов в группе
|
||||||
console.log(`🏗️ TemplateGroup render for "${group?.name || 'Без группы'}":`, {
|
console.log(`🏗️ TemplateGroup render for "${group?.name || 'Без группы'}":`, {
|
||||||
@@ -279,8 +295,8 @@ export const TemplateGroup: React.FC<TemplateGroupProps> = ({
|
|||||||
id: t.id,
|
id: t.id,
|
||||||
name: t.name,
|
name: t.name,
|
||||||
order: t.order,
|
order: t.order,
|
||||||
group_id: t.group_id
|
group_id: t.group_id,
|
||||||
}))
|
})),
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggleExpanded = useCallback(() => {
|
const toggleExpanded = useCallback(() => {
|
||||||
@@ -316,14 +332,24 @@ export const TemplateGroup: React.FC<TemplateGroupProps> = ({
|
|||||||
const groupId = group?.id || 'ungrouped'
|
const groupId = group?.id || 'ungrouped'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6">
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'transition-all duration-200',
|
||||||
|
isExpanded ? 'mb-6' : 'mb-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{/* Заголовок группы */}
|
{/* Заголовок группы */}
|
||||||
<div className="mb-3 flex items-center justify-between rounded-lg p-4 bg-muted/30 min-h-[60px]">
|
<div
|
||||||
<div className="flex items-center gap-2 flex-1">
|
className={clsx(
|
||||||
|
'flex min-h-[40px] items-center justify-between p-2',
|
||||||
|
isExpanded ? 'mb-3' : 'mb-0'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-1 items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-6 w-6 p-0 flex-shrink-0"
|
className="h-6 w-6 flex-shrink-0 p-0"
|
||||||
onClick={toggleExpanded}
|
onClick={toggleExpanded}
|
||||||
>
|
>
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
@@ -334,19 +360,24 @@ export const TemplateGroup: React.FC<TemplateGroupProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
<FolderOpen className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
<FolderOpen className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
||||||
) : (
|
) : (
|
||||||
<FolderClosed className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
<FolderClosed className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<h3 className="text-lg font-semibold">{groupName}</h3>
|
<h3 className="text-lg font-semibold">{groupName}</h3>
|
||||||
|
|
||||||
<Badge variant="secondary" className={clsx(
|
<Badge
|
||||||
"text-xs ml-2",
|
variant="secondary"
|
||||||
showEmptyState && templates.length === 0 && "bg-amber-100 text-amber-800 border-amber-200"
|
className={clsx(
|
||||||
)}>
|
'ml-2 text-xs',
|
||||||
|
showEmptyState &&
|
||||||
|
templates.length === 0 &&
|
||||||
|
'border-amber-200 bg-amber-100 text-amber-800'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{templates.length}
|
{templates.length}
|
||||||
{showEmptyState && templates.length === 0 && " (отфильтровано)"}
|
{showEmptyState && templates.length === 0 && ' (отфильтровано)'}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -405,9 +436,7 @@ export const TemplateGroup: React.FC<TemplateGroupProps> = ({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-muted-foreground/60">
|
<div className="text-muted-foreground/60">
|
||||||
<p className="text-sm font-medium">Нет результатов</p>
|
<p className="text-sm font-medium">Нет результатов</p>
|
||||||
<p className="text-xs">
|
<p className="text-xs">Попробуйте изменить фильтры</p>
|
||||||
Попробуйте изменить фильтры
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import {
|
|||||||
TemplateFilters,
|
TemplateFilters,
|
||||||
} from '@/component/TemplateManager/TemplateFilterSidebar'
|
} from '@/component/TemplateManager/TemplateFilterSidebar'
|
||||||
import { TemplateGroup } from '@/component/TemplateManager/TemplateGroup'
|
import { TemplateGroup } from '@/component/TemplateManager/TemplateGroup'
|
||||||
import { GroupProvider, useGroupContext } from '@/entity/group/model/GroupContext'
|
import {
|
||||||
|
GroupProvider,
|
||||||
|
useGroupContext,
|
||||||
|
} from '@/entity/group/model/GroupContext'
|
||||||
import {
|
import {
|
||||||
apiTemplateWithAttributesToTemplate,
|
apiTemplateWithAttributesToTemplate,
|
||||||
getTemplateWithAttributesApi,
|
getTemplateWithAttributesApi,
|
||||||
@@ -14,7 +17,10 @@ import {
|
|||||||
import { useCategoryContext } from '@/entity/template/model/CategoryContext'
|
import { useCategoryContext } from '@/entity/template/model/CategoryContext'
|
||||||
import { useTemplateContext } from '@/entity/template/model/TemplateContext'
|
import { useTemplateContext } from '@/entity/template/model/TemplateContext'
|
||||||
import { useTemplateOrderState } from '@/hook/useTemplateOrderState'
|
import { useTemplateOrderState } from '@/hook/useTemplateOrderState'
|
||||||
import { generateInitialOrder, groupTemplatesByGroup } from '@/lib/template-order'
|
import {
|
||||||
|
generateInitialOrder,
|
||||||
|
groupTemplatesByGroup,
|
||||||
|
} from '@/lib/template-order'
|
||||||
import { Group, Template, TemplateAttributeDetail } from '@/type/template'
|
import { Group, Template, TemplateAttributeDetail } from '@/type/template'
|
||||||
import TemplateCard from '@/widget/template/ui/TemplateCard'
|
import TemplateCard from '@/widget/template/ui/TemplateCard'
|
||||||
import {
|
import {
|
||||||
@@ -29,10 +35,7 @@ import {
|
|||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
} from '@dnd-kit/core'
|
} from '@dnd-kit/core'
|
||||||
import {
|
import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable'
|
||||||
arrayMove,
|
|
||||||
sortableKeyboardCoordinates,
|
|
||||||
} from '@dnd-kit/sortable'
|
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import {
|
import {
|
||||||
CheckSquare,
|
CheckSquare,
|
||||||
@@ -55,7 +58,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle
|
DialogTitle,
|
||||||
} from 'shared/ui/dialog'
|
} from 'shared/ui/dialog'
|
||||||
import { Input } from 'shared/ui/input'
|
import { Input } from 'shared/ui/input'
|
||||||
import {
|
import {
|
||||||
@@ -220,10 +223,14 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
|
|||||||
{/* Селектор группы */}
|
{/* Селектор группы */}
|
||||||
{showGroupSelector && onGroupChange && (
|
{showGroupSelector && onGroupChange && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-muted-foreground">Группа</label>
|
<label className="text-sm font-medium text-muted-foreground">
|
||||||
|
Группа
|
||||||
|
</label>
|
||||||
<Select
|
<Select
|
||||||
value={selectedGroupId || 'no-group'}
|
value={selectedGroupId || 'no-group'}
|
||||||
onValueChange={(value) => onGroupChange(value === 'no-group' ? null : value)}
|
onValueChange={value =>
|
||||||
|
onGroupChange(value === 'no-group' ? null : value)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-9">
|
<SelectTrigger className="h-9">
|
||||||
<SelectValue placeholder="Выберите группу" />
|
<SelectValue placeholder="Выберите группу" />
|
||||||
@@ -232,7 +239,9 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
|
|||||||
<SelectItem value="no-group">
|
<SelectItem value="no-group">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-2 w-2 rounded-full bg-muted-foreground/40" />
|
<div className="h-2 w-2 rounded-full bg-muted-foreground/40" />
|
||||||
<span className="text-muted-foreground">Без группы</span>
|
<span className="text-muted-foreground">
|
||||||
|
Без группы
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
{groups.map(group => (
|
{groups.map(group => (
|
||||||
@@ -283,11 +292,13 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id="copy-first-sheet-empty"
|
id="copy-first-sheet-empty"
|
||||||
checked={copyFirstSheetEmpty}
|
checked={copyFirstSheetEmpty}
|
||||||
onCheckedChange={(checked) => onCopyFirstSheetEmptyChange?.(checked === true)}
|
onCheckedChange={checked =>
|
||||||
|
onCopyFirstSheetEmptyChange?.(checked === true)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="copy-first-sheet-empty"
|
htmlFor="copy-first-sheet-empty"
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
className="cursor-pointer text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
>
|
>
|
||||||
Не копировать левый лист
|
Не копировать левый лист
|
||||||
</label>
|
</label>
|
||||||
@@ -366,7 +377,9 @@ const TemplatesPageContent = () => {
|
|||||||
AttributeValue[]
|
AttributeValue[]
|
||||||
>([])
|
>([])
|
||||||
const [copyFirstSheetEmpty, setCopyFirstSheetEmpty] = useState(false)
|
const [copyFirstSheetEmpty, setCopyFirstSheetEmpty] = useState(false)
|
||||||
const [copySelectedGroupId, setCopySelectedGroupId] = useState<string | null>(null)
|
const [copySelectedGroupId, setCopySelectedGroupId] = useState<string | null>(
|
||||||
|
null
|
||||||
|
)
|
||||||
const [editName, setEditName] = useState('')
|
const [editName, setEditName] = useState('')
|
||||||
const [editDescription, setEditDescription] = useState('')
|
const [editDescription, setEditDescription] = useState('')
|
||||||
const [editAttributeValues, setEditAttributeValues] = useState<
|
const [editAttributeValues, setEditAttributeValues] = useState<
|
||||||
@@ -441,11 +454,14 @@ const TemplatesPageContent = () => {
|
|||||||
if (newName.trim()) {
|
if (newName.trim()) {
|
||||||
try {
|
try {
|
||||||
// Генерируем order для нового шаблона (в конец группы)
|
// Генерируем order для нового шаблона (в конец группы)
|
||||||
const newOrder = generateInitialOrder(templatesWithChanges, selectedGroupId)
|
const newOrder = generateInitialOrder(
|
||||||
|
templatesWithChanges,
|
||||||
|
selectedGroupId
|
||||||
|
)
|
||||||
|
|
||||||
console.log('Creating new template with order:', {
|
console.log('Creating new template with order:', {
|
||||||
selectedGroupId,
|
selectedGroupId,
|
||||||
newOrder
|
newOrder,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Создаем шаблон с атрибутами, группой и order
|
// Создаем шаблон с атрибутами, группой и order
|
||||||
@@ -587,136 +603,173 @@ const TemplatesPageContent = () => {
|
|||||||
setActiveId(event.active.id as string)
|
setActiveId(event.active.id as string)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleDragEnd = useCallback((event: DragEndEvent) => {
|
const handleDragEnd = useCallback(
|
||||||
const { active, over } = event
|
(event: DragEndEvent) => {
|
||||||
setActiveId(null)
|
const { active, over } = event
|
||||||
|
setActiveId(null)
|
||||||
|
|
||||||
if (!over || active.id === over.id) {
|
if (!over || active.id === over.id) {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
const activeTemplate = templatesWithChanges.find(t => t.id === active.id)
|
|
||||||
if (!activeTemplate) return
|
|
||||||
|
|
||||||
// Определяем, куда был сброшен элемент
|
|
||||||
const overId = over.id as string
|
|
||||||
|
|
||||||
console.log('🚀 DRAG END START:', {
|
|
||||||
activeId: active.id,
|
|
||||||
overId: overId,
|
|
||||||
activeTemplate: {
|
|
||||||
id: activeTemplate.id,
|
|
||||||
name: activeTemplate.name,
|
|
||||||
group_id: activeTemplate.group_id,
|
|
||||||
order: activeTemplate.order
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// Проверяем, это drop на left/right зону карточки или на группу
|
const activeTemplate = templatesWithChanges.find(t => t.id === active.id)
|
||||||
if (overId.startsWith('drop-left-') || overId.startsWith('drop-right-')) {
|
if (!activeTemplate) return
|
||||||
// Определяем позицию (left/right) и ID карточки
|
|
||||||
const isLeftDrop = overId.startsWith('drop-left-')
|
|
||||||
const targetTemplateId = overId.replace(isLeftDrop ? 'drop-left-' : 'drop-right-', '')
|
|
||||||
const overTemplate = templatesWithChanges.find(t => t.id === targetTemplateId)
|
|
||||||
|
|
||||||
if (!overTemplate) return
|
// Определяем, куда был сброшен элемент
|
||||||
|
const overId = over.id as string
|
||||||
|
|
||||||
const sourceGroupId = activeTemplate.group_id
|
console.log('🚀 DRAG END START:', {
|
||||||
const targetGroupId = overTemplate.group_id
|
activeId: active.id,
|
||||||
|
overId: overId,
|
||||||
console.log('🎯 Drop on card edge:', {
|
activeTemplate: {
|
||||||
position: isLeftDrop ? 'left' : 'right',
|
id: activeTemplate.id,
|
||||||
targetTemplateId,
|
name: activeTemplate.name,
|
||||||
sourceGroupId,
|
group_id: activeTemplate.group_id,
|
||||||
targetGroupId
|
order: activeTemplate.order,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if ((sourceGroupId || null) === (targetGroupId || null)) {
|
// Проверяем, это drop на left/right зону карточки или на группу
|
||||||
// Перетаскивание внутри одной группы
|
if (overId.startsWith('drop-left-') || overId.startsWith('drop-right-')) {
|
||||||
const groupTemplates = templatesWithChanges
|
// Определяем позицию (left/right) и ID карточки
|
||||||
.filter(t => (t.group_id || null) === (sourceGroupId || null))
|
const isLeftDrop = overId.startsWith('drop-left-')
|
||||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
const targetTemplateId = overId.replace(
|
||||||
|
isLeftDrop ? 'drop-left-' : 'drop-right-',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
const overTemplate = templatesWithChanges.find(
|
||||||
|
t => t.id === targetTemplateId
|
||||||
|
)
|
||||||
|
|
||||||
const activeIndex = groupTemplates.findIndex(t => t.id === active.id)
|
if (!overTemplate) return
|
||||||
const targetIndex = groupTemplates.findIndex(t => t.id === targetTemplateId)
|
|
||||||
|
|
||||||
if (activeIndex !== targetIndex) {
|
const sourceGroupId = activeTemplate.group_id
|
||||||
// Определяем позицию вставки: слева (перед) или справа (после)
|
const targetGroupId = overTemplate.group_id
|
||||||
const insertIndex = isLeftDrop ? targetIndex : targetIndex + 1
|
|
||||||
const reorderedTemplates = arrayMove(groupTemplates, activeIndex, insertIndex)
|
|
||||||
|
|
||||||
// Пересчитываем order для всех элементов в группе
|
console.log('🎯 Drop on card edge:', {
|
||||||
reorderedTemplates.forEach((template, index) => {
|
position: isLeftDrop ? 'left' : 'right',
|
||||||
const newOrder = (index + 1) * 1000
|
targetTemplateId,
|
||||||
if (template.order !== newOrder) {
|
|
||||||
orderState.applyOrderChange(template.id, newOrder, sourceGroupId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('🔄 Reordering within group:', {
|
|
||||||
groupId: sourceGroupId,
|
|
||||||
activeIndex,
|
|
||||||
targetIndex,
|
|
||||||
insertIndex,
|
|
||||||
position: isLeftDrop ? 'before' : 'after'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Перетаскивание между разными группами
|
|
||||||
const targetGroupTemplates = templatesWithChanges
|
|
||||||
.filter(t => (t.group_id || null) === (targetGroupId || null))
|
|
||||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
|
||||||
|
|
||||||
const targetIndex = targetGroupTemplates.findIndex(t => t.id === targetTemplateId)
|
|
||||||
|
|
||||||
let newOrder: number
|
|
||||||
|
|
||||||
if (isLeftDrop) {
|
|
||||||
// Вставка перед целевой карточкой (слева)
|
|
||||||
if (targetIndex === 0) {
|
|
||||||
// Перед первой карточкой
|
|
||||||
const firstOrder = targetGroupTemplates[0]?.order ?? 1000
|
|
||||||
newOrder = firstOrder > 1000 ? firstOrder / 2 : firstOrder - 500
|
|
||||||
} else {
|
|
||||||
// Между карточками
|
|
||||||
const prevOrder = targetGroupTemplates[targetIndex - 1]?.order ?? 0
|
|
||||||
const currentOrder = targetGroupTemplates[targetIndex]?.order ?? 1000
|
|
||||||
newOrder = (prevOrder + currentOrder) / 2
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Вставка после целевой карточки (справа)
|
|
||||||
const currentOrder = targetGroupTemplates[targetIndex]?.order ?? 1000
|
|
||||||
const nextOrder = targetGroupTemplates[targetIndex + 1]?.order ?? (currentOrder + 2000)
|
|
||||||
newOrder = (currentOrder + nextOrder) / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🔀 Moving between groups with edge positioning:', {
|
|
||||||
sourceGroupId,
|
sourceGroupId,
|
||||||
targetGroupId,
|
targetGroupId,
|
||||||
targetTemplateId,
|
|
||||||
position: isLeftDrop ? 'before' : 'after',
|
|
||||||
newOrder
|
|
||||||
})
|
})
|
||||||
|
|
||||||
orderState.applyOrderChange(active.id as string, newOrder, targetGroupId)
|
if ((sourceGroupId || null) === (targetGroupId || null)) {
|
||||||
|
// Перетаскивание внутри одной группы
|
||||||
|
const groupTemplates = templatesWithChanges
|
||||||
|
.filter(t => (t.group_id || null) === (sourceGroupId || null))
|
||||||
|
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
||||||
|
|
||||||
|
const activeIndex = groupTemplates.findIndex(t => t.id === active.id)
|
||||||
|
const targetIndex = groupTemplates.findIndex(
|
||||||
|
t => t.id === targetTemplateId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (activeIndex !== targetIndex) {
|
||||||
|
// Определяем позицию вставки: слева (перед) или справа (после)
|
||||||
|
const insertIndex = isLeftDrop ? targetIndex : targetIndex + 1
|
||||||
|
const reorderedTemplates = arrayMove(
|
||||||
|
groupTemplates,
|
||||||
|
activeIndex,
|
||||||
|
insertIndex
|
||||||
|
)
|
||||||
|
|
||||||
|
// Пересчитываем order для всех элементов в группе
|
||||||
|
reorderedTemplates.forEach((template, index) => {
|
||||||
|
const newOrder = (index + 1) * 1000
|
||||||
|
if (template.order !== newOrder) {
|
||||||
|
orderState.applyOrderChange(
|
||||||
|
template.id,
|
||||||
|
newOrder,
|
||||||
|
sourceGroupId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('🔄 Reordering within group:', {
|
||||||
|
groupId: sourceGroupId,
|
||||||
|
activeIndex,
|
||||||
|
targetIndex,
|
||||||
|
insertIndex,
|
||||||
|
position: isLeftDrop ? 'before' : 'after',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Перетаскивание между разными группами
|
||||||
|
const targetGroupTemplates = templatesWithChanges
|
||||||
|
.filter(t => (t.group_id || null) === (targetGroupId || null))
|
||||||
|
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
||||||
|
|
||||||
|
const targetIndex = targetGroupTemplates.findIndex(
|
||||||
|
t => t.id === targetTemplateId
|
||||||
|
)
|
||||||
|
|
||||||
|
let newOrder: number
|
||||||
|
|
||||||
|
if (isLeftDrop) {
|
||||||
|
// Вставка перед целевой карточкой (слева)
|
||||||
|
if (targetIndex === 0) {
|
||||||
|
// Перед первой карточкой
|
||||||
|
const firstOrder = targetGroupTemplates[0]?.order ?? 1000
|
||||||
|
newOrder = firstOrder > 1000 ? firstOrder / 2 : firstOrder - 500
|
||||||
|
} else {
|
||||||
|
// Между карточками
|
||||||
|
const prevOrder =
|
||||||
|
targetGroupTemplates[targetIndex - 1]?.order ?? 0
|
||||||
|
const currentOrder =
|
||||||
|
targetGroupTemplates[targetIndex]?.order ?? 1000
|
||||||
|
newOrder = (prevOrder + currentOrder) / 2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Вставка после целевой карточки (справа)
|
||||||
|
const currentOrder =
|
||||||
|
targetGroupTemplates[targetIndex]?.order ?? 1000
|
||||||
|
const nextOrder =
|
||||||
|
targetGroupTemplates[targetIndex + 1]?.order ??
|
||||||
|
currentOrder + 2000
|
||||||
|
newOrder = (currentOrder + nextOrder) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔀 Moving between groups with edge positioning:', {
|
||||||
|
sourceGroupId,
|
||||||
|
targetGroupId,
|
||||||
|
targetTemplateId,
|
||||||
|
position: isLeftDrop ? 'before' : 'after',
|
||||||
|
newOrder,
|
||||||
|
})
|
||||||
|
|
||||||
|
orderState.applyOrderChange(
|
||||||
|
active.id as string,
|
||||||
|
newOrder,
|
||||||
|
targetGroupId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Сброс на пустую группу
|
||||||
|
const isOverGroup =
|
||||||
|
groups.some(g => g.id === overId) || overId === 'ungrouped'
|
||||||
|
|
||||||
|
if (isOverGroup) {
|
||||||
|
const targetGroupId = overId === 'ungrouped' ? null : overId
|
||||||
|
const newOrder = generateInitialOrder(
|
||||||
|
templatesWithChanges,
|
||||||
|
targetGroupId
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('🗂️ Moving to empty group:', {
|
||||||
|
targetGroupId,
|
||||||
|
newOrder,
|
||||||
|
})
|
||||||
|
|
||||||
|
orderState.applyOrderChange(
|
||||||
|
active.id as string,
|
||||||
|
newOrder,
|
||||||
|
targetGroupId
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
// Сброс на пустую группу
|
[templatesWithChanges, orderState, groups]
|
||||||
const isOverGroup = groups.some(g => g.id === overId) || overId === 'ungrouped'
|
)
|
||||||
|
|
||||||
if (isOverGroup) {
|
|
||||||
const targetGroupId = overId === 'ungrouped' ? null : overId
|
|
||||||
const newOrder = generateInitialOrder(templatesWithChanges, targetGroupId)
|
|
||||||
|
|
||||||
console.log('🗂️ Moving to empty group:', {
|
|
||||||
targetGroupId,
|
|
||||||
newOrder
|
|
||||||
})
|
|
||||||
|
|
||||||
orderState.applyOrderChange(active.id as string, newOrder, targetGroupId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [templatesWithChanges, orderState, groups])
|
|
||||||
|
|
||||||
const handleDragOver = useCallback((event: DragOverEvent) => {
|
const handleDragOver = useCallback((event: DragOverEvent) => {
|
||||||
// Можно добавить дополнительную логику если нужно
|
// Можно добавить дополнительную логику если нужно
|
||||||
@@ -724,7 +777,11 @@ const TemplatesPageContent = () => {
|
|||||||
|
|
||||||
// Проверяем есть ли активные фильтры
|
// Проверяем есть ли активные фильтры
|
||||||
const hasActiveFilters = useMemo(() => {
|
const hasActiveFilters = useMemo(() => {
|
||||||
return !!(filters.name || filters.description || Object.keys(filters.attributes).length > 0)
|
return !!(
|
||||||
|
filters.name ||
|
||||||
|
filters.description ||
|
||||||
|
Object.keys(filters.attributes).length > 0
|
||||||
|
)
|
||||||
}, [filters])
|
}, [filters])
|
||||||
|
|
||||||
// Фильтрация шаблонов
|
// Фильтрация шаблонов
|
||||||
@@ -790,17 +847,24 @@ const TemplatesPageContent = () => {
|
|||||||
|
|
||||||
// Группировка шаблонов по группам с правильной сортировкой
|
// Группировка шаблонов по группам с правильной сортировкой
|
||||||
const groupedTemplates = useMemo(() => {
|
const groupedTemplates = useMemo(() => {
|
||||||
console.log('🗂️ Grouping templates, input:',
|
console.log(
|
||||||
filteredTemplates.map(t => ({ id: t.id, name: t.name, group_id: t.group_id, order: t.order }))
|
'🗂️ Grouping templates, input:',
|
||||||
|
filteredTemplates.map(t => ({
|
||||||
|
id: t.id,
|
||||||
|
name: t.name,
|
||||||
|
group_id: t.group_id,
|
||||||
|
order: t.order,
|
||||||
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
const grouped = groupTemplatesByGroup(filteredTemplates)
|
const grouped = groupTemplatesByGroup(filteredTemplates)
|
||||||
|
|
||||||
console.log('🗂️ Grouped templates (before sorting):',
|
console.log(
|
||||||
|
'🗂️ Grouped templates (before sorting):',
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(grouped).map(([key, templates]) => [
|
Object.entries(grouped).map(([key, templates]) => [
|
||||||
key,
|
key,
|
||||||
templates.map(t => ({ id: t.id, name: t.name, order: t.order }))
|
templates.map(t => ({ id: t.id, name: t.name, order: t.order })),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -817,11 +881,12 @@ const TemplatesPageContent = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('🗂️ Grouped templates (after sorting):',
|
console.log(
|
||||||
|
'🗂️ Grouped templates (after sorting):',
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(sortedGrouped).map(([key, templates]) => [
|
Object.entries(sortedGrouped).map(([key, templates]) => [
|
||||||
key,
|
key,
|
||||||
templates.map(t => ({ id: t.id, name: t.name, order: t.order }))
|
templates.map(t => ({ id: t.id, name: t.name, order: t.order })),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -837,8 +902,12 @@ const TemplatesPageContent = () => {
|
|||||||
|
|
||||||
const totalTemplates = templatesWithChanges.length
|
const totalTemplates = templatesWithChanges.length
|
||||||
const filteredCount = filteredTemplates.length
|
const filteredCount = filteredTemplates.length
|
||||||
const filteredGroups = new Set(filteredTemplates.map(t => t.group_id || null))
|
const filteredGroups = new Set(
|
||||||
const totalGroups = new Set(templatesWithChanges.map(t => t.group_id || null))
|
filteredTemplates.map(t => t.group_id || null)
|
||||||
|
)
|
||||||
|
const totalGroups = new Set(
|
||||||
|
templatesWithChanges.map(t => t.group_id || null)
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalTemplates,
|
totalTemplates,
|
||||||
@@ -873,7 +942,12 @@ const TemplatesPageContent = () => {
|
|||||||
hasUnsavedChanges={orderState.hasUnsavedChanges}
|
hasUnsavedChanges={orderState.hasUnsavedChanges}
|
||||||
isSaving={orderState.isSaving}
|
isSaving={orderState.isSaving}
|
||||||
lastSaveError={orderState.lastSaveError}
|
lastSaveError={orderState.lastSaveError}
|
||||||
onSave={() => orderState.saveOrderChanges(updateTemplateOrder, moveTemplateToGroup)}
|
onSave={() =>
|
||||||
|
orderState.saveOrderChanges(
|
||||||
|
updateTemplateOrder,
|
||||||
|
moveTemplateToGroup
|
||||||
|
)
|
||||||
|
}
|
||||||
onClearError={orderState.clearError}
|
onClearError={orderState.clearError}
|
||||||
changedCount={orderState.changedCount}
|
changedCount={orderState.changedCount}
|
||||||
/>
|
/>
|
||||||
@@ -1022,13 +1096,20 @@ const TemplatesPageContent = () => {
|
|||||||
{filterStats && (
|
{filterStats && (
|
||||||
<div className="mb-4 rounded-lg bg-muted/50 p-3">
|
<div className="mb-4 rounded-lg bg-muted/50 p-3">
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
Показано <span className="font-medium text-foreground">{filterStats.filteredTemplates}</span> из {filterStats.totalTemplates} шаблонов
|
Показано{' '}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{filterStats.filteredTemplates}
|
||||||
|
</span>{' '}
|
||||||
|
из {filterStats.totalTemplates} шаблонов
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 text-xs">
|
<div className="flex items-center gap-4 text-xs">
|
||||||
<span>
|
<span>
|
||||||
Групп с результатами: <span className="font-medium text-foreground">{filterStats.groupsWithResults}</span>
|
Групп с результатами:{' '}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{filterStats.groupsWithResults}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{filterStats.emptyGroups > 0 && (
|
{filterStats.emptyGroups > 0 && (
|
||||||
<span className="text-amber-600">
|
<span className="text-amber-600">
|
||||||
@@ -1039,7 +1120,8 @@ const TemplatesPageContent = () => {
|
|||||||
</div>
|
</div>
|
||||||
{Object.keys(filters.attributes).length > 0 && (
|
{Object.keys(filters.attributes).length > 0 && (
|
||||||
<div className="mt-1 text-xs">
|
<div className="mt-1 text-xs">
|
||||||
Активных фильтров по атрибутам: {Object.keys(filters.attributes).length}
|
Активных фильтров по атрибутам:{' '}
|
||||||
|
{Object.keys(filters.attributes).length}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1055,14 +1137,19 @@ const TemplatesPageContent = () => {
|
|||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
>
|
>
|
||||||
{/* Отображение групп шаблонов */}
|
{/* Отображение групп шаблонов */}
|
||||||
<div className={clsx(
|
<div
|
||||||
"space-y-6 transition-opacity duration-200",
|
className={clsx(
|
||||||
activeId && "opacity-90"
|
'transition-opacity duration-200',
|
||||||
)}>
|
activeId && 'opacity-90'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{/* Все группы - показываем все группы при фильтрации для сохранения контекста */}
|
{/* Все группы - показываем все группы при фильтрации для сохранения контекста */}
|
||||||
{sortedGroups.map(group => {
|
{sortedGroups.map(group => {
|
||||||
const groupTemplates = groupedTemplates[group.id] || []
|
const groupTemplates = groupedTemplates[group.id] || []
|
||||||
const showGroup = !hasActiveFilters || groupTemplates.length > 0 || hasActiveFilters
|
const showGroup =
|
||||||
|
!hasActiveFilters ||
|
||||||
|
groupTemplates.length > 0 ||
|
||||||
|
hasActiveFilters
|
||||||
|
|
||||||
if (!showGroup) return null
|
if (!showGroup) return null
|
||||||
|
|
||||||
@@ -1081,9 +1168,13 @@ const TemplatesPageContent = () => {
|
|||||||
onDeleteGroup={handleDeleteGroup}
|
onDeleteGroup={handleDeleteGroup}
|
||||||
activeFilters={filters.attributes}
|
activeFilters={filters.attributes}
|
||||||
isExpanded={orderState.getGroupExpanded(group.id)}
|
isExpanded={orderState.getGroupExpanded(group.id)}
|
||||||
onToggleExpanded={() => orderState.toggleGroupExpanded(group.id)}
|
onToggleExpanded={() =>
|
||||||
|
orderState.toggleGroupExpanded(group.id)
|
||||||
|
}
|
||||||
onCreateTemplate={handleCreateTemplate}
|
onCreateTemplate={handleCreateTemplate}
|
||||||
showEmptyState={hasActiveFilters && groupTemplates.length === 0}
|
showEmptyState={
|
||||||
|
hasActiveFilters && groupTemplates.length === 0
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -1102,48 +1193,63 @@ const TemplatesPageContent = () => {
|
|||||||
isExpanded={orderState.getGroupExpanded(null)}
|
isExpanded={orderState.getGroupExpanded(null)}
|
||||||
onToggleExpanded={() => orderState.toggleGroupExpanded(null)}
|
onToggleExpanded={() => orderState.toggleGroupExpanded(null)}
|
||||||
onCreateTemplate={handleCreateTemplate}
|
onCreateTemplate={handleCreateTemplate}
|
||||||
showEmptyState={hasActiveFilters && (!groupedTemplates.ungrouped || groupedTemplates.ungrouped.length === 0)}
|
showEmptyState={
|
||||||
|
hasActiveFilters &&
|
||||||
|
(!groupedTemplates.ungrouped ||
|
||||||
|
groupedTemplates.ungrouped.length === 0)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Если нет групп и нет шаблонов вообще, показываем кнопку создания */}
|
{/* Если нет групп и нет шаблонов вообще, показываем кнопку создания */}
|
||||||
{sortedGroups.length === 0 && templatesWithChanges.length === 0 && (
|
{sortedGroups.length === 0 &&
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
templatesWithChanges.length === 0 && (
|
||||||
<div className="text-muted-foreground mb-6">
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
<p className="text-lg font-medium">Нет групп и шаблонов</p>
|
<div className="mb-6 text-muted-foreground">
|
||||||
<p className="text-sm">Создайте группу или шаблон для начала работы</p>
|
<p className="text-lg font-medium">
|
||||||
|
Нет групп и шаблонов
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
Создайте группу или шаблон для начала работы
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Button
|
||||||
|
onClick={() => handleCreateTemplate()}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
Создать шаблон
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
)}
|
||||||
<Button
|
|
||||||
onClick={() => handleCreateTemplate()}
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
Создать шаблон
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* DragOverlay для красивого перетаскивания */}
|
{/* DragOverlay для красивого перетаскивания */}
|
||||||
<DragOverlay>
|
<DragOverlay>
|
||||||
{activeId ? (
|
{activeId ? (
|
||||||
<div className={clsx(
|
<div
|
||||||
'transform transition-transform duration-200',
|
className={clsx(
|
||||||
'rotate-2 scale-105 opacity-90',
|
'transform transition-transform duration-200',
|
||||||
'drop-shadow-2xl shadow-2xl',
|
'rotate-2 scale-105 opacity-90',
|
||||||
'pointer-events-none z-[9999]'
|
'shadow-2xl drop-shadow-2xl',
|
||||||
)}>
|
'pointer-events-none z-[9999]'
|
||||||
<div className={clsx(
|
)}
|
||||||
'rounded-lg overflow-hidden',
|
>
|
||||||
'bg-background border-0',
|
<div
|
||||||
// Полностью убираем все borders из вложенных элементов
|
className={clsx(
|
||||||
'[&_*]:!border-0 [&_*]:!border-transparent [&_*]:!ring-0',
|
'overflow-hidden rounded-lg',
|
||||||
// Добавляем собственную тень и border
|
'border-0 bg-background',
|
||||||
'ring-2 ring-primary/20 shadow-primary/10'
|
// Полностью убираем все borders из вложенных элементов
|
||||||
)}>
|
'[&_*]:!border-0 [&_*]:!border-transparent [&_*]:!ring-0',
|
||||||
|
// Добавляем собственную тень и border
|
||||||
|
'shadow-primary/10 ring-2 ring-primary/20'
|
||||||
|
)}
|
||||||
|
>
|
||||||
<TemplateCard
|
<TemplateCard
|
||||||
template={templatesWithChanges.find(t => t.id === activeId)!}
|
template={
|
||||||
|
templatesWithChanges.find(t => t.id === activeId)!
|
||||||
|
}
|
||||||
selectionMode={false}
|
selectionMode={false}
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
activeFilters={filters.attributes}
|
activeFilters={filters.attributes}
|
||||||
@@ -1154,24 +1260,28 @@ const TemplatesPageContent = () => {
|
|||||||
</DragOverlay>
|
</DragOverlay>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
|
|
||||||
{filteredTemplates.length === 0 && templates.length > 0 && hasActiveFilters && (
|
{filteredTemplates.length === 0 &&
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
templates.length > 0 &&
|
||||||
<div className="text-muted-foreground mb-6">
|
hasActiveFilters && (
|
||||||
<p className="text-lg font-medium">Шаблоны не найдены</p>
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
<p className="text-sm">Ни один шаблон не соответствует заданным фильтрам</p>
|
<div className="mb-6 text-muted-foreground">
|
||||||
|
<p className="text-lg font-medium">Шаблоны не найдены</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
Ни один шаблон не соответствует заданным фильтрам
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() =>
|
||||||
|
setFilters({ name: '', description: '', attributes: {} })
|
||||||
|
}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
Сбросить все фильтры
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
)}
|
||||||
variant="outline"
|
|
||||||
onClick={() => setFilters({ name: '', description: '', attributes: {} })}
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
Сбросить все фильтры
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user