отображение карточек более красивое
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-animated-numbers": "^1.1.1",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-day-picker": "^9.8.0",
|
"react-day-picker": "^9.8.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
@@ -23,15 +23,7 @@ export const CategoryFieldsSelector: React.FC<CategoryFieldsSelectorProps> = ({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
compact = false, // По умолчанию обычный режим
|
compact = false, // По умолчанию обычный режим
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { attributes, isLoading } = useCategoryContext()
|
||||||
attributes,
|
|
||||||
getRequiredAttributes,
|
|
||||||
getOptionalAttributes,
|
|
||||||
isLoading,
|
|
||||||
} = useCategoryContext()
|
|
||||||
|
|
||||||
const requiredAttributes = getRequiredAttributes()
|
|
||||||
const optionalAttributes = getOptionalAttributes()
|
|
||||||
|
|
||||||
const getAttributeValue = (attributeId: string): string => {
|
const getAttributeValue = (attributeId: string): string => {
|
||||||
const value = selectedValues.find(v => v.attributeId === attributeId)?.value
|
const value = selectedValues.find(v => v.attributeId === attributeId)?.value
|
||||||
@@ -92,29 +84,6 @@ export const CategoryFieldsSelector: React.FC<CategoryFieldsSelectorProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderAttributeSection = (
|
|
||||||
attributesList: Attribute[],
|
|
||||||
title: string,
|
|
||||||
titleColor: string = ''
|
|
||||||
) => {
|
|
||||||
if (attributesList.length === 0) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={compact ? 'space-y-3' : 'space-y-4'}>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Label
|
|
||||||
className={`text-sm font-semibold ${titleColor} ${compact ? 'text-xs' : ''}`}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className={compact ? 'grid grid-cols-2 gap-3' : 'space-y-4'}>
|
|
||||||
{attributesList.map(renderAttribute)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -127,17 +96,11 @@ export const CategoryFieldsSelector: React.FC<CategoryFieldsSelectorProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={compact ? 'space-y-4' : 'space-y-6'}>
|
<div className={compact ? 'space-y-4' : 'space-y-6'}>
|
||||||
{/* Обязательные атрибуты */}
|
{attributes.length > 0 ? (
|
||||||
{renderAttributeSection(
|
<div className={compact ? 'grid grid-cols-2 gap-3' : 'space-y-4'}>
|
||||||
requiredAttributes,
|
{attributes.map(renderAttribute)}
|
||||||
'Обязательные атрибуты',
|
</div>
|
||||||
'text-red-600'
|
) : (
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Необязательные атрибуты */}
|
|
||||||
{renderAttributeSection(optionalAttributes, 'Дополнительные атрибуты')}
|
|
||||||
|
|
||||||
{attributes.length === 0 && (
|
|
||||||
<div className="py-4 text-center">
|
<div className="py-4 text-center">
|
||||||
<p className="text-sm text-muted-foreground">Атрибуты не настроены</p>
|
<p className="text-sm text-muted-foreground">Атрибуты не настроены</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,21 +118,6 @@ export const TemplateFilterSidebar: React.FC<TemplateFilterSidebarProps> = ({
|
|||||||
|
|
||||||
<SidebarSeparator />
|
<SidebarSeparator />
|
||||||
|
|
||||||
{/* Фильтр по описанию */}
|
|
||||||
<SidebarGroup>
|
|
||||||
<Label className="text-sm font-medium">Поиск по описанию</Label>
|
|
||||||
<SidebarGroupContent>
|
|
||||||
<AutocompleteInput
|
|
||||||
label=""
|
|
||||||
value={filters.description}
|
|
||||||
onValueChange={value => updateFilters({ description: value })}
|
|
||||||
suggestions={descriptionSuggestions}
|
|
||||||
/>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
|
|
||||||
<SidebarSeparator />
|
|
||||||
|
|
||||||
{/* Активные фильтры по атрибутам */}
|
{/* Активные фильтры по атрибутам */}
|
||||||
{Object.keys(filters.attributes).length > 0 && (
|
{Object.keys(filters.attributes).length > 0 && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -114,7 +114,9 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
|||||||
const [localStorageEnabled, setLocalStorageEnabled] = useState(false)
|
const [localStorageEnabled, setLocalStorageEnabled] = useState(false)
|
||||||
const [currentValue, setCurrentValue] = useState(value || '')
|
const [currentValue, setCurrentValue] = useState(value || '')
|
||||||
const [isAnimating, setIsAnimating] = useState(false)
|
const [isAnimating, setIsAnimating] = useState(false)
|
||||||
const [rollingDigits, setRollingDigits] = useState<string[]>([])
|
const [animatingDigits, setAnimatingDigits] = useState<(number | null)[]>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
const [flyingDigit, setFlyingDigit] = useState<{
|
const [flyingDigit, setFlyingDigit] = useState<{
|
||||||
show: boolean
|
show: boolean
|
||||||
x: number
|
x: number
|
||||||
@@ -178,8 +180,6 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
|||||||
|
|
||||||
const currentNum = parseFloat(currentValue) || 0
|
const currentNum = parseFloat(currentValue) || 0
|
||||||
const newNum = currentNum + 1
|
const newNum = currentNum + 1
|
||||||
const currentStr = currentNum.toString()
|
|
||||||
const newStr = newNum.toString()
|
|
||||||
|
|
||||||
setIsAnimating(true)
|
setIsAnimating(true)
|
||||||
|
|
||||||
@@ -189,164 +189,92 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
|||||||
) as HTMLElement
|
) as HTMLElement
|
||||||
if (buttonElement) {
|
if (buttonElement) {
|
||||||
const rect = buttonElement.getBoundingClientRect()
|
const rect = buttonElement.getBoundingClientRect()
|
||||||
const inputElement = buttonElement
|
|
||||||
.closest('.relative')
|
|
||||||
?.querySelector('input')
|
|
||||||
const inputRect = inputElement?.getBoundingClientRect()
|
|
||||||
|
|
||||||
if (inputRect) {
|
setFlyingDigit({
|
||||||
setFlyingDigit({
|
show: true,
|
||||||
show: true,
|
x: rect.left + rect.width / 2,
|
||||||
x: rect.left + rect.width / 2,
|
y: rect.top + rect.height / 2,
|
||||||
y: rect.top + rect.height / 2,
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Убираем летящую цифру через 400мс
|
||||||
|
setTimeout(() => {
|
||||||
|
setFlyingDigit(prev => ({ ...prev, show: false }))
|
||||||
|
}, 400)
|
||||||
|
|
||||||
|
// Преобразуем числа в массивы цифр
|
||||||
|
const currentStr = currentNum.toString()
|
||||||
|
const newStr = newNum.toString()
|
||||||
|
|
||||||
|
// Определяем максимальную длину для выравнивания
|
||||||
|
const maxLength = Math.max(currentStr.length, newStr.length)
|
||||||
|
const currentDigits = currentStr
|
||||||
|
.padStart(maxLength, '0')
|
||||||
|
.split('')
|
||||||
|
.map(d => parseInt(d))
|
||||||
|
const newDigits = newStr
|
||||||
|
.padStart(maxLength, '0')
|
||||||
|
.split('')
|
||||||
|
.map(d => parseInt(d))
|
||||||
|
|
||||||
|
// Инициализируем массив анимируемых цифр
|
||||||
|
setAnimatingDigits(currentDigits)
|
||||||
|
|
||||||
|
// Анимация всех цифр
|
||||||
|
const startTime = Date.now()
|
||||||
|
const duration = 600
|
||||||
|
|
||||||
|
const animateDigits = () => {
|
||||||
|
const elapsed = Date.now() - startTime
|
||||||
|
const progress = Math.min(elapsed / duration, 1)
|
||||||
|
|
||||||
|
// Используем easing функцию для плавности
|
||||||
|
const easeOutQuad = (t: number) => t * (2 - t)
|
||||||
|
const smoothProgress = easeOutQuad(progress)
|
||||||
|
|
||||||
|
// Анимируем каждую цифру отдельно
|
||||||
|
const currentAnimatedDigits = currentDigits.map((startDigit, index) => {
|
||||||
|
const endDigit = newDigits[index]
|
||||||
|
|
||||||
|
// Если цифра не изменилась, возвращаем исходную
|
||||||
|
if (startDigit === endDigit) {
|
||||||
|
return startDigit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Интерполируем между старой и новой цифрой
|
||||||
|
const currentDigit =
|
||||||
|
startDigit + (endDigit - startDigit) * smoothProgress
|
||||||
|
return Math.round(currentDigit)
|
||||||
|
})
|
||||||
|
|
||||||
|
setAnimatingDigits(currentAnimatedDigits)
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(animateDigits)
|
||||||
|
} else {
|
||||||
|
setAnimatingDigits([])
|
||||||
|
setIsAnimating(false)
|
||||||
|
handleValueChange(newNum.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, увеличивается ли количество разрядов
|
requestAnimationFrame(animateDigits)
|
||||||
const isAddingDigit = newStr.length > currentStr.length
|
|
||||||
|
|
||||||
if (isAddingDigit) {
|
|
||||||
// Если добавляется разряд, делаем анимацию сдвига
|
|
||||||
setRollingDigits(currentStr.split(''))
|
|
||||||
|
|
||||||
// Анимация сдвига при добавлении разряда
|
|
||||||
const duration = 800
|
|
||||||
const startTime = Date.now()
|
|
||||||
|
|
||||||
const animateSlide = () => {
|
|
||||||
const elapsed = Date.now() - startTime
|
|
||||||
const progress = Math.min(elapsed / duration, 1)
|
|
||||||
|
|
||||||
// Убираем летящую цифру через 400мс
|
|
||||||
if (elapsed > 400) {
|
|
||||||
setFlyingDigit(prev => ({ ...prev, show: false }))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress < 1) {
|
|
||||||
// Плавное появление новой цифры слева и изменение старых
|
|
||||||
const easeOutQuad = (t: number) => t * (2 - t)
|
|
||||||
const smoothProgress = easeOutQuad(progress)
|
|
||||||
|
|
||||||
// Разбираем целевое число на цифры
|
|
||||||
const targetDigits = newStr.split('')
|
|
||||||
const currentDigits = currentStr.split('')
|
|
||||||
|
|
||||||
if (smoothProgress > 0.2) {
|
|
||||||
// Создаем анимированный массив цифр
|
|
||||||
const animatedDigits = targetDigits.map((targetDigit, index) => {
|
|
||||||
if (index === 0) {
|
|
||||||
// Первая цифра (новая) - плавно появляется
|
|
||||||
return targetDigit
|
|
||||||
} else {
|
|
||||||
// Остальные цифры - плавно меняются от старых к новым
|
|
||||||
const oldDigit = currentDigits[index - 1] || '0'
|
|
||||||
const oldNum = parseInt(oldDigit)
|
|
||||||
const targetNum = parseInt(targetDigit)
|
|
||||||
|
|
||||||
// Интерполяция между старой и новой цифрой
|
|
||||||
const currentNum =
|
|
||||||
oldNum + (targetNum - oldNum) * smoothProgress
|
|
||||||
|
|
||||||
// В конце анимации фиксируем на целевой цифре
|
|
||||||
if (progress > 0.9) {
|
|
||||||
return targetDigit
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(currentNum).toString()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setRollingDigits(animatedDigits)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Финал
|
|
||||||
setRollingDigits([])
|
|
||||||
setIsAnimating(false)
|
|
||||||
handleValueChange(newStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress < 1) {
|
|
||||||
requestAnimationFrame(animateSlide)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animateSlide)
|
|
||||||
} else {
|
|
||||||
// Обычная анимация для случаев без добавления разряда
|
|
||||||
const maxLength = Math.max(currentStr.length, newStr.length)
|
|
||||||
const currentPadded = currentStr.padStart(maxLength, '0')
|
|
||||||
const newPadded = newStr.padStart(maxLength, '0')
|
|
||||||
|
|
||||||
// Создаем массив с начальными значениями
|
|
||||||
setRollingDigits(currentPadded.split(''))
|
|
||||||
|
|
||||||
// Анимация плавного перехода цифр
|
|
||||||
const duration = 800
|
|
||||||
const startTime = Date.now()
|
|
||||||
|
|
||||||
const animateTransition = () => {
|
|
||||||
const elapsed = Date.now() - startTime
|
|
||||||
const progress = Math.min(elapsed / duration, 1)
|
|
||||||
|
|
||||||
// Убираем летящую цифру через 400мс
|
|
||||||
if (elapsed > 400) {
|
|
||||||
setFlyingDigit(prev => ({ ...prev, show: false }))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress < 1) {
|
|
||||||
// Используем функцию сглаживания для более плавной анимации
|
|
||||||
const easeOutQuad = (t: number) => t * (2 - t)
|
|
||||||
const smoothProgress = easeOutQuad(progress)
|
|
||||||
|
|
||||||
const newRolling = currentPadded
|
|
||||||
.split('')
|
|
||||||
.map((oldDigit, index) => {
|
|
||||||
const targetDigit = newPadded[index]
|
|
||||||
|
|
||||||
// Если цифра не изменилась, оставляем как есть
|
|
||||||
if (oldDigit === targetDigit) {
|
|
||||||
return oldDigit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Для изменившихся цифр - прямой переход с интерполяцией
|
|
||||||
const oldNum = parseInt(oldDigit)
|
|
||||||
const targetNum = parseInt(targetDigit)
|
|
||||||
|
|
||||||
// Прямая интерполяция между старой и новой цифрой
|
|
||||||
const currentNum =
|
|
||||||
oldNum + (targetNum - oldNum) * smoothProgress
|
|
||||||
|
|
||||||
// В конце анимации фиксируем на целевой цифре
|
|
||||||
if (progress > 0.95) {
|
|
||||||
return targetDigit
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round(currentNum).toString()
|
|
||||||
})
|
|
||||||
|
|
||||||
setRollingDigits(newRolling)
|
|
||||||
} else {
|
|
||||||
// Финал
|
|
||||||
setRollingDigits([])
|
|
||||||
setIsAnimating(false)
|
|
||||||
handleValueChange(newStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress < 1) {
|
|
||||||
requestAnimationFrame(animateTransition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animateTransition)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Определяем отображаемое значение
|
// Проверяем, является ли текущее значение числом
|
||||||
const displayValue =
|
const isNumericValue =
|
||||||
isAnimating && rollingDigits.length > 0
|
!isNaN(parseFloat(currentValue)) && isFinite(parseFloat(currentValue))
|
||||||
? rollingDigits.join('').replace(/^0+/, '') || '0'
|
|
||||||
: currentValue
|
// Функция для отображения значения с анимированными цифрами
|
||||||
|
const getDisplayValue = () => {
|
||||||
|
if (!isAnimating || !isNumericValue || animatingDigits.length === 0) {
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Собираем число из анимированных цифр и убираем ведущие нули
|
||||||
|
const animatedStr = animatingDigits.join('')
|
||||||
|
return animatedStr.replace(/^0+/, '') || '0'
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative space-y-1">
|
<div className="relative space-y-1">
|
||||||
@@ -431,7 +359,7 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
|||||||
<div className="relative flex items-center">
|
<div className="relative flex items-center">
|
||||||
<Input
|
<Input
|
||||||
placeholder={config.placeholder}
|
placeholder={config.placeholder}
|
||||||
value={displayValue}
|
value={getDisplayValue()}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
if (!isAnimating) {
|
if (!isAnimating) {
|
||||||
// Автоматически заменяем запятые на точки
|
// Автоматически заменяем запятые на точки
|
||||||
@@ -441,10 +369,10 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
|||||||
}}
|
}}
|
||||||
required={config.required}
|
required={config.required}
|
||||||
className={`${config.showIncrementButton ? 'pr-10' : ''} ${
|
className={`${config.showIncrementButton ? 'pr-10' : ''} ${
|
||||||
isAnimating
|
isAnimating && isNumericValue
|
||||||
? 'font-mono text-lg font-bold tracking-wider text-blue-600'
|
? 'font-mono text-lg font-bold tracking-wider text-blue-600 transition-all duration-300'
|
||||||
: ''
|
: ''
|
||||||
} transition-all duration-300`}
|
}`}
|
||||||
readOnly={isAnimating}
|
readOnly={isAnimating}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,12 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from '@/component/ui/dialog'
|
} from '@/component/ui/dialog'
|
||||||
import { Input } from '@/component/ui/input'
|
import { Input } from '@/component/ui/input'
|
||||||
import { Label } from '@/component/ui/label'
|
|
||||||
import { Separator } from '@/component/ui/separator'
|
import { Separator } from '@/component/ui/separator'
|
||||||
import {
|
import {
|
||||||
SidebarInset,
|
SidebarInset,
|
||||||
SidebarProvider,
|
SidebarProvider,
|
||||||
SidebarTrigger,
|
SidebarTrigger,
|
||||||
} from '@/component/ui/sidebar'
|
} from '@/component/ui/sidebar'
|
||||||
import { Textarea } from '@/component/ui/textarea'
|
|
||||||
import {
|
import {
|
||||||
apiTemplateWithAttributesToTemplate,
|
apiTemplateWithAttributesToTemplate,
|
||||||
getTemplateWithAttributesApi,
|
getTemplateWithAttributesApi,
|
||||||
@@ -36,9 +34,7 @@ import {
|
|||||||
CheckSquare,
|
CheckSquare,
|
||||||
Copy,
|
Copy,
|
||||||
Edit3,
|
Edit3,
|
||||||
FileText,
|
|
||||||
Plus,
|
Plus,
|
||||||
Settings2,
|
|
||||||
Square,
|
Square,
|
||||||
Tag,
|
Tag,
|
||||||
Trash2,
|
Trash2,
|
||||||
@@ -162,16 +158,12 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Settings2 className={`h-4 w-4 ${iconColor}`} />
|
<Type className={`h-4 w-4 ${iconColor}`} />
|
||||||
<CardTitle className="text-sm">Основная информация</CardTitle>
|
<CardTitle className="text-sm">{nameLabel}</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="flex items-center gap-2 text-sm font-medium">
|
|
||||||
<Type className="h-3 w-3" />
|
|
||||||
{nameLabel}
|
|
||||||
</Label>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder={`Введите ${nameLabel.toLowerCase()}`}
|
placeholder={`Введите ${nameLabel.toLowerCase()}`}
|
||||||
value={name}
|
value={name}
|
||||||
@@ -179,19 +171,6 @@ const TemplateForm: React.FC<TemplateFormProps> = ({
|
|||||||
className="h-9"
|
className="h-9"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="flex items-center gap-2 text-sm font-medium">
|
|
||||||
<FileText className="h-3 w-3" />
|
|
||||||
{descriptionLabel}
|
|
||||||
</Label>
|
|
||||||
<Textarea
|
|
||||||
placeholder={`${descriptionLabel}...`}
|
|
||||||
value={templateDescription}
|
|
||||||
onChange={e => onDescriptionChange(e.target.value)}
|
|
||||||
className="min-h-[80px] resize-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -665,7 +644,7 @@ export const TemplatesOverviewPage = () => {
|
|||||||
<section
|
<section
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'grid gap-4',
|
'grid gap-4',
|
||||||
'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5'
|
'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{filteredTemplates.map(t => (
|
{filteredTemplates.map(t => (
|
||||||
|
|||||||
@@ -3,19 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/component/ui/card'
|
|||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/component/ui/popover'
|
import { Popover, PopoverContent, PopoverTrigger } from '@/component/ui/popover'
|
||||||
import { Template, TemplateAttributeDetail } from '@/type/template'
|
import { Template, TemplateAttributeDetail } from '@/type/template'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import {
|
import { Check, Copy, MoreVertical, Pencil, Trash2 } from 'lucide-react'
|
||||||
Award,
|
|
||||||
Check,
|
|
||||||
Cog,
|
|
||||||
Copy,
|
|
||||||
Cpu,
|
|
||||||
FileText,
|
|
||||||
MoreVertical,
|
|
||||||
Pencil,
|
|
||||||
Settings,
|
|
||||||
Trash2,
|
|
||||||
Wrench,
|
|
||||||
} from 'lucide-react'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
@@ -30,26 +18,6 @@ interface TemplateCardProps {
|
|||||||
activeFilters?: { [attributeId: string]: string } // Добавляем пропс для активных фильтров
|
activeFilters?: { [attributeId: string]: string } // Добавляем пропс для активных фильтров
|
||||||
}
|
}
|
||||||
|
|
||||||
// Мапинг атрибутов на иконки и определение основных атрибутов
|
|
||||||
const getAttributeIcon = (attributeName: string) => {
|
|
||||||
const name = attributeName.toLowerCase()
|
|
||||||
if (name.includes('тип') || name.includes('прибор')) return Cpu
|
|
||||||
if (name.includes('госреестр') || name.includes('реестр')) return Award
|
|
||||||
if (name.includes('исполнение')) return Cog
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPrimaryAttribute = (attributeName: string) => {
|
|
||||||
const name = attributeName.toLowerCase()
|
|
||||||
return (
|
|
||||||
name.includes('тип') ||
|
|
||||||
name.includes('прибор') ||
|
|
||||||
name.includes('госреестр') ||
|
|
||||||
name.includes('реестр') ||
|
|
||||||
name.includes('исполнение')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const TemplateCard: React.FC<TemplateCardProps> = ({
|
const TemplateCard: React.FC<TemplateCardProps> = ({
|
||||||
template,
|
template,
|
||||||
selectionMode = false,
|
selectionMode = false,
|
||||||
@@ -79,32 +47,6 @@ const TemplateCard: React.FC<TemplateCardProps> = ({
|
|||||||
onDelete?.(template.id)
|
onDelete?.(template.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Определяем какие атрибуты показывать
|
|
||||||
const getDisplayAttributes = () => {
|
|
||||||
if (!template.attributes)
|
|
||||||
return { primaryAttributes: [], filteredAttributes: [] }
|
|
||||||
|
|
||||||
const attributesWithValues = template.attributes.filter(
|
|
||||||
attr => attr.value && attr.value.trim()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Находим основные атрибуты (с иконками) независимо от порядка
|
|
||||||
const primaryAttributes = attributesWithValues.filter(attr =>
|
|
||||||
isPrimaryAttribute(attr.attribute_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Добавляем отфильтрованные атрибуты если они не являются основными
|
|
||||||
const filteredAttributes = attributesWithValues.filter(
|
|
||||||
attr =>
|
|
||||||
activeFilters[attr.attribute_id] &&
|
|
||||||
!isPrimaryAttribute(attr.attribute_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
return { primaryAttributes, filteredAttributes }
|
|
||||||
}
|
|
||||||
|
|
||||||
const { primaryAttributes, filteredAttributes } = getDisplayAttributes()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// console.log(template.excelFile),
|
// console.log(template.excelFile),
|
||||||
<Card
|
<Card
|
||||||
@@ -112,227 +54,189 @@ const TemplateCard: React.FC<TemplateCardProps> = ({
|
|||||||
onClick={
|
onClick={
|
||||||
selectionMode && onToggleSelect
|
selectionMode && onToggleSelect
|
||||||
? () => onToggleSelect(template.id)
|
? () => onToggleSelect(template.id)
|
||||||
: undefined
|
: !selectionMode && isConfigured
|
||||||
|
? () => navigate(`/templates/${template.id}/protocols`)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'group relative transition-all duration-200 ease-in-out',
|
'group relative flex min-h-[160px] flex-col transition-all duration-200',
|
||||||
'border-2 ring-2 ring-offset-1',
|
(selectionMode || (!selectionMode && isConfigured)) && 'cursor-pointer',
|
||||||
selectionMode && 'hover:cursor-pointer',
|
selectionMode &&
|
||||||
selectionMode && isSelected
|
isSelected &&
|
||||||
? 'border-primary bg-primary/5 shadow-md shadow-primary/20 ring-primary/30'
|
'border-primary ring-2 ring-primary ring-offset-2',
|
||||||
: 'border-border ring-transparent',
|
|
||||||
!selectionMode &&
|
!selectionMode &&
|
||||||
'hover:border-primary/50 hover:shadow-lg hover:shadow-primary/5',
|
isConfigured &&
|
||||||
'bg-card hover:bg-card/80'
|
'hover:border-primary/50 hover:shadow-md',
|
||||||
|
!selectionMode && !isConfigured && 'cursor-not-allowed opacity-60'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{selectionMode && (
|
{selectionMode && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
tabIndex={0}
|
|
||||||
aria-pressed={isSelected}
|
|
||||||
aria-label={isSelected ? 'Снять выделение' : 'Выбрать'}
|
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onToggleSelect?.(template.id)
|
onToggleSelect?.(template.id)
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'absolute right-2 top-2 z-10 flex h-6 w-6 items-center justify-center rounded-full border-2 bg-background transition-all',
|
'absolute right-2 top-2 h-5 w-5 rounded border border-primary',
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-primary bg-primary text-primary-foreground'
|
? 'bg-primary text-primary-foreground'
|
||||||
: 'border-border bg-background text-muted-foreground hover:border-primary',
|
: 'border-input bg-background hover:bg-accent hover:text-accent-foreground'
|
||||||
'focus-visible:ring-2 focus-visible:ring-primary'
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isSelected ? <Check size={14} /> : null}
|
{isSelected && <Check className="h-3 w-3" />}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{/* Popover-меню с действиями, только если не selectionMode */}
|
|
||||||
{!selectionMode && (
|
{!selectionMode && (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant="ghost"
|
||||||
tabIndex={0}
|
size="sm"
|
||||||
aria-label="Действия"
|
className="absolute right-1 top-1 h-7 w-7 p-0 opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
className={clsx(
|
|
||||||
'absolute right-2 top-2 z-10 flex h-6 w-6 items-center justify-center rounded-full border border-border bg-background text-muted-foreground transition-all',
|
|
||||||
'hover:border-primary hover:text-accent-foreground',
|
|
||||||
'focus-visible:ring-2 focus-visible:ring-primary'
|
|
||||||
)}
|
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<MoreVertical className="h-3.5 w-3.5" />
|
<MoreVertical className="h-3 w-3" />
|
||||||
</button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent align="end" className="w-48 p-1">
|
<PopoverContent align="end" className="w-48 p-1">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
className="flex w-full items-center gap-2 rounded px-2 py-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground"
|
className="w-full justify-start"
|
||||||
>
|
>
|
||||||
<Pencil className="h-4 w-4" />
|
<Pencil className="mr-2 h-4 w-4" />
|
||||||
Редактировать
|
Редактировать
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
className="flex w-full items-center gap-2 rounded px-2 py-2 text-sm transition-colors hover:bg-accent hover:text-accent-foreground"
|
className="w-full justify-start"
|
||||||
>
|
>
|
||||||
<Copy className="h-4 w-4" />
|
<Copy className="mr-2 h-4 w-4" />
|
||||||
Копировать
|
Копировать
|
||||||
</button>
|
</Button>
|
||||||
<div className="mx-1 my-1 h-px bg-border" />
|
<div className="mx-1 my-1 h-px bg-border" />
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
className="flex w-full items-center gap-2 rounded px-2 py-2 text-sm text-destructive transition-colors hover:bg-destructive hover:text-destructive-foreground"
|
className="w-full justify-start text-destructive hover:bg-destructive hover:text-destructive-foreground"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
Удалить
|
Удалить
|
||||||
</button>
|
</Button>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="flex-shrink-0 pb-3">
|
||||||
<CardTitle className="pr-8 text-base font-medium leading-tight">
|
<CardTitle className="line-clamp-3 text-base font-semibold leading-snug">
|
||||||
{template.name}
|
{template.name}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col pt-0">
|
<CardContent className="flex flex-1 flex-col pb-4 pt-0">
|
||||||
<div className="flex-1">
|
<div className="flex-1"></div>
|
||||||
{/* Описание или пустое место для выравнивания */}
|
|
||||||
<div className="mb-3">
|
|
||||||
{template.description ? (
|
|
||||||
<p className="line-clamp-1 text-sm leading-tight text-muted-foreground">
|
|
||||||
{template.description}
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm italic leading-tight text-muted-foreground/40">
|
|
||||||
Описание не указано
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Атрибуты */}
|
<div className="space-y-2">
|
||||||
{(primaryAttributes.length > 0 || filteredAttributes.length > 0) && (
|
{/* Показываем госреестр (если не в фильтрах) */}
|
||||||
<div className="mb-3">
|
{template.attributes &&
|
||||||
<div className="space-y-1">
|
template.attributes
|
||||||
{/* Основные атрибуты с иконками */}
|
.filter(
|
||||||
{primaryAttributes.map(attr => {
|
attr =>
|
||||||
const Icon = getAttributeIcon(attr.attribute_name)
|
attr.value &&
|
||||||
const isFiltered = activeFilters[attr.attribute_id]
|
attr.value.trim() &&
|
||||||
|
(attr.attribute_name.toLowerCase().includes('госреестр') ||
|
||||||
|
attr.attribute_name.toLowerCase().includes('реестр')) &&
|
||||||
|
!activeFilters[attr.attribute_id]
|
||||||
|
)
|
||||||
|
.slice(0, 1)
|
||||||
|
.map(attr => (
|
||||||
|
<div
|
||||||
|
key={attr.attribute_id}
|
||||||
|
className="rounded-md border bg-muted/30 px-3 py-2"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="flex-shrink-0 text-xs font-bold text-muted-foreground/70">
|
||||||
|
ГРСИ
|
||||||
|
</span>
|
||||||
|
<div className="break-all text-right text-sm font-medium leading-tight text-foreground">
|
||||||
|
{attr.value}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Показываем все отфильтрованные атрибуты */}
|
||||||
|
{template.attributes &&
|
||||||
|
template.attributes
|
||||||
|
.filter(
|
||||||
|
attr =>
|
||||||
|
attr.value &&
|
||||||
|
attr.value.trim() &&
|
||||||
|
activeFilters[attr.attribute_id]
|
||||||
|
)
|
||||||
|
.map(attr => {
|
||||||
|
const isRegistry =
|
||||||
|
attr.attribute_name.toLowerCase().includes('госреестр') ||
|
||||||
|
attr.attribute_name.toLowerCase().includes('реестр')
|
||||||
|
|
||||||
|
if (isRegistry) {
|
||||||
|
// Госреестр в фильтрах показываем в специальном стиле
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={attr.attribute_id}
|
key={attr.attribute_id}
|
||||||
className={clsx(
|
className="rounded-md border border-primary/50 bg-primary/10 px-3 py-2"
|
||||||
'flex items-center justify-between rounded px-2 py-0.5 text-xs',
|
|
||||||
isFiltered
|
|
||||||
? 'border border-primary/30 bg-primary/20'
|
|
||||||
: 'bg-muted/30'
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 flex-1 items-center">
|
<div className="flex items-center justify-between gap-2">
|
||||||
{Icon && (
|
<span className="flex-shrink-0 text-xs font-bold text-primary">
|
||||||
<Icon className="mr-1.5 h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
ГРСИ
|
||||||
)}
|
|
||||||
<span className="truncate font-medium text-foreground/80">
|
|
||||||
{attr.attribute_name}
|
|
||||||
</span>
|
</span>
|
||||||
|
<div className="break-all text-right text-sm font-medium leading-tight text-foreground">
|
||||||
|
{attr.value}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="ml-2 max-w-[40%] truncate text-muted-foreground">
|
|
||||||
{attr.value}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
} else {
|
||||||
|
// Обычные атрибуты
|
||||||
|
const displayName =
|
||||||
|
attr.attribute_name.toLowerCase().includes('тип') &&
|
||||||
|
attr.attribute_name.toLowerCase().includes('прибор')
|
||||||
|
? 'ТИП'
|
||||||
|
: attr.attribute_name
|
||||||
|
|
||||||
{/* Отфильтрованные атрибуты (если не входят в основные) */}
|
return (
|
||||||
{filteredAttributes.map(attr => (
|
<div
|
||||||
<div
|
key={attr.attribute_id}
|
||||||
key={attr.attribute_id}
|
className="rounded-md border border-primary/50 bg-primary/10 px-3 py-2"
|
||||||
className="flex items-center justify-between rounded border border-primary/30 bg-primary/20 px-2 py-0.5 text-xs"
|
>
|
||||||
>
|
<div className="flex items-center justify-between gap-2">
|
||||||
<span className="truncate font-medium text-foreground/80">
|
<span className="flex-shrink-0 text-xs font-bold text-primary">
|
||||||
{attr.attribute_name}
|
{displayName}
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-2 max-w-[60%] truncate text-muted-foreground">
|
<div className="break-all text-right text-sm font-medium leading-tight text-foreground">
|
||||||
{attr.value}
|
{attr.value}
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Статус шаблона */}
|
||||||
|
{!isConfigured && (
|
||||||
|
<div className="mt-auto">
|
||||||
|
<div className="rounded-md bg-orange-50 px-3 py-2 text-center">
|
||||||
|
<div className="text-xs font-medium text-orange-600">
|
||||||
|
Требует настройки
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Даты */}
|
|
||||||
<div className="mb-3 grid grid-cols-2 gap-2 text-xs text-muted-foreground/70">
|
|
||||||
<div className="flex flex-col gap-0.5">
|
|
||||||
<span className="font-medium">Создан</span>
|
|
||||||
<span>
|
|
||||||
{new Date(template.createdAt).toLocaleDateString('ru-RU', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: '2-digit',
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-0.5 text-right">
|
|
||||||
<span className="font-medium">Обновлён</span>
|
|
||||||
<span>
|
|
||||||
{new Date(template.updatedAt).toLocaleDateString('ru-RU', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: '2-digit',
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* Кнопки действий */}
|
|
||||||
<div className="flex items-center gap-1.5 border-t border-border/50 pt-2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
className="h-7 flex-1 text-xs"
|
|
||||||
disabled={selectionMode || !isConfigured}
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(`/templates/${template.id}/protocols`)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileText className="mr-1 h-3 w-3" />
|
|
||||||
{isConfigured ? 'Создать' : 'Требует настройки'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
className="h-7 w-7 p-0"
|
|
||||||
disabled={selectionMode}
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(`/templates/${template.id}/edit`)
|
|
||||||
}}
|
|
||||||
aria-label="Редактировать шаблон"
|
|
||||||
>
|
|
||||||
<Settings className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
className="h-7 w-7 p-0"
|
|
||||||
disabled={selectionMode}
|
|
||||||
onClick={e => {
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(`/templates/${template.id}/elements`)
|
|
||||||
}}
|
|
||||||
aria-label="Настроить элементы"
|
|
||||||
>
|
|
||||||
<Wrench className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|||||||
36
yarn.lock
36
yarn.lock
@@ -2186,6 +2186,15 @@ fraction.js@^4.3.7:
|
|||||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
||||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||||
|
|
||||||
|
framer-motion@^11.18.2:
|
||||||
|
version "11.18.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.18.2.tgz#0c6bd05677f4cfd3b3bdead4eb5ecdd5ed245718"
|
||||||
|
integrity sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==
|
||||||
|
dependencies:
|
||||||
|
motion-dom "^11.18.1"
|
||||||
|
motion-utils "^11.18.1"
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
fs.realpath@^1.0.0:
|
fs.realpath@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
@@ -2850,6 +2859,26 @@ minimist@^1.2.0, minimist@^1.2.6:
|
|||||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
||||||
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
||||||
|
|
||||||
|
motion-dom@^11.18.1:
|
||||||
|
version "11.18.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-11.18.1.tgz#e7fed7b7dc6ae1223ef1cce29ee54bec826dc3f2"
|
||||||
|
integrity sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==
|
||||||
|
dependencies:
|
||||||
|
motion-utils "^11.18.1"
|
||||||
|
|
||||||
|
motion-utils@^11.18.1:
|
||||||
|
version "11.18.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-11.18.1.tgz#671227669833e991c55813cf337899f41327db5b"
|
||||||
|
integrity sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==
|
||||||
|
|
||||||
|
motion@^11.16.0:
|
||||||
|
version "11.18.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/motion/-/motion-11.18.2.tgz#17fb372f3ed94fc9ee1384a25a9068e9da1951e7"
|
||||||
|
integrity sha512-JLjvFDuFr42NFtcVoMAyC2sEjnpA8xpy6qWPyzQvCloznAyQ8FIXioxWfHiLtgYhoVpfUqSWpn1h9++skj9+Wg==
|
||||||
|
dependencies:
|
||||||
|
framer-motion "^11.18.2"
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
ms@^2.1.1, ms@^2.1.3:
|
ms@^2.1.1, ms@^2.1.3:
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
@@ -3214,6 +3243,13 @@ re-resizable@6.11.2:
|
|||||||
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.11.2.tgz#2e8f7119ca3881d5b5aea0ffa014a80e5c1252b3"
|
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.11.2.tgz#2e8f7119ca3881d5b5aea0ffa014a80e5c1252b3"
|
||||||
integrity sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==
|
integrity sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==
|
||||||
|
|
||||||
|
react-animated-numbers@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-animated-numbers/-/react-animated-numbers-1.1.1.tgz#ae5aad75f4b561043a0a7ee994041f03bf33f5d8"
|
||||||
|
integrity sha512-Jr2vbDWjo5wW+X8wBYRBACpuKdPkLa4A2ZYfxlAR0oc/gXWVeWyAmAWzZj73276IQxycxrTL7aGn4quWV7+l2Q==
|
||||||
|
dependencies:
|
||||||
|
motion "^11.16.0"
|
||||||
|
|
||||||
react-beautiful-dnd@^13.1.1:
|
react-beautiful-dnd@^13.1.1:
|
||||||
version "13.1.1"
|
version "13.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
|
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
|
||||||
|
|||||||
Reference in New Issue
Block a user