diff --git a/package.json b/package.json index fd8ef7a..a612682 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "lodash.isequal": "^4.5.0", "lucide-react": "^0.525.0", "react": "^18.2.0", + "react-animated-numbers": "^1.1.1", "react-beautiful-dnd": "^13.1.1", "react-day-picker": "^9.8.0", "react-dom": "^18.2.0", diff --git a/src/component/TemplateManager/CategoryFieldsSelector.tsx b/src/component/TemplateManager/CategoryFieldsSelector.tsx index aa57fa8..4aa9549 100644 --- a/src/component/TemplateManager/CategoryFieldsSelector.tsx +++ b/src/component/TemplateManager/CategoryFieldsSelector.tsx @@ -23,15 +23,7 @@ export const CategoryFieldsSelector: React.FC = ({ disabled = false, compact = false, // По умолчанию обычный режим }) => { - const { - attributes, - getRequiredAttributes, - getOptionalAttributes, - isLoading, - } = useCategoryContext() - - const requiredAttributes = getRequiredAttributes() - const optionalAttributes = getOptionalAttributes() + const { attributes, isLoading } = useCategoryContext() const getAttributeValue = (attributeId: string): string => { const value = selectedValues.find(v => v.attributeId === attributeId)?.value @@ -92,29 +84,6 @@ export const CategoryFieldsSelector: React.FC = ({ ) } - const renderAttributeSection = ( - attributesList: Attribute[], - title: string, - titleColor: string = '' - ) => { - if (attributesList.length === 0) return null - - return ( -
-
- -
-
- {attributesList.map(renderAttribute)} -
-
- ) - } - if (isLoading) { return (
@@ -127,17 +96,11 @@ export const CategoryFieldsSelector: React.FC = ({ return (
- {/* Обязательные атрибуты */} - {renderAttributeSection( - requiredAttributes, - 'Обязательные атрибуты', - 'text-red-600' - )} - - {/* Необязательные атрибуты */} - {renderAttributeSection(optionalAttributes, 'Дополнительные атрибуты')} - - {attributes.length === 0 && ( + {attributes.length > 0 ? ( +
+ {attributes.map(renderAttribute)} +
+ ) : (

Атрибуты не настроены

diff --git a/src/component/TemplateManager/TemplateFilterSidebar.tsx b/src/component/TemplateManager/TemplateFilterSidebar.tsx index d77b179..bf7c0c0 100644 --- a/src/component/TemplateManager/TemplateFilterSidebar.tsx +++ b/src/component/TemplateManager/TemplateFilterSidebar.tsx @@ -118,21 +118,6 @@ export const TemplateFilterSidebar: React.FC = ({ - {/* Фильтр по описанию */} - - - - updateFilters({ description: value })} - suggestions={descriptionSuggestions} - /> - - - - - {/* Активные фильтры по атрибутам */} {Object.keys(filters.attributes).length > 0 && ( <> diff --git a/src/entitiy/element/model/implementations/TextElement.tsx b/src/entitiy/element/model/implementations/TextElement.tsx index 5642c91..111cfa8 100644 --- a/src/entitiy/element/model/implementations/TextElement.tsx +++ b/src/entitiy/element/model/implementations/TextElement.tsx @@ -114,7 +114,9 @@ export const textDefinition: ElementDefinition = { const [localStorageEnabled, setLocalStorageEnabled] = useState(false) const [currentValue, setCurrentValue] = useState(value || '') const [isAnimating, setIsAnimating] = useState(false) - const [rollingDigits, setRollingDigits] = useState([]) + const [animatingDigits, setAnimatingDigits] = useState<(number | null)[]>( + [] + ) const [flyingDigit, setFlyingDigit] = useState<{ show: boolean x: number @@ -178,8 +180,6 @@ export const textDefinition: ElementDefinition = { const currentNum = parseFloat(currentValue) || 0 const newNum = currentNum + 1 - const currentStr = currentNum.toString() - const newStr = newNum.toString() setIsAnimating(true) @@ -189,164 +189,92 @@ export const textDefinition: ElementDefinition = { ) as HTMLElement if (buttonElement) { const rect = buttonElement.getBoundingClientRect() - const inputElement = buttonElement - .closest('.relative') - ?.querySelector('input') - const inputRect = inputElement?.getBoundingClientRect() - if (inputRect) { - setFlyingDigit({ - show: true, - x: rect.left + rect.width / 2, - y: rect.top + rect.height / 2, - }) + setFlyingDigit({ + show: true, + x: rect.left + rect.width / 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()) } } - // Проверяем, увеличивается ли количество разрядов - 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) - } + requestAnimationFrame(animateDigits) } - // Определяем отображаемое значение - const displayValue = - isAnimating && rollingDigits.length > 0 - ? rollingDigits.join('').replace(/^0+/, '') || '0' - : currentValue + // Проверяем, является ли текущее значение числом + const isNumericValue = + !isNaN(parseFloat(currentValue)) && isFinite(parseFloat(currentValue)) + + // Функция для отображения значения с анимированными цифрами + const getDisplayValue = () => { + if (!isAnimating || !isNumericValue || animatingDigits.length === 0) { + return currentValue + } + + // Собираем число из анимированных цифр и убираем ведущие нули + const animatedStr = animatingDigits.join('') + return animatedStr.replace(/^0+/, '') || '0' + } return (
@@ -431,7 +359,7 @@ export const textDefinition: ElementDefinition = {
{ if (!isAnimating) { // Автоматически заменяем запятые на точки @@ -441,10 +369,10 @@ export const textDefinition: ElementDefinition = { }} required={config.required} className={`${config.showIncrementButton ? 'pr-10' : ''} ${ - isAnimating - ? 'font-mono text-lg font-bold tracking-wider text-blue-600' + isAnimating && isNumericValue + ? 'font-mono text-lg font-bold tracking-wider text-blue-600 transition-all duration-300' : '' - } transition-all duration-300`} + }`} readOnly={isAnimating} /> diff --git a/src/page/TemplatesOverviewPage.tsx b/src/page/TemplatesOverviewPage.tsx index cdc156f..7a7a76e 100644 --- a/src/page/TemplatesOverviewPage.tsx +++ b/src/page/TemplatesOverviewPage.tsx @@ -15,14 +15,12 @@ import { DialogTrigger, } from '@/component/ui/dialog' import { Input } from '@/component/ui/input' -import { Label } from '@/component/ui/label' import { Separator } from '@/component/ui/separator' import { SidebarInset, SidebarProvider, SidebarTrigger, } from '@/component/ui/sidebar' -import { Textarea } from '@/component/ui/textarea' import { apiTemplateWithAttributesToTemplate, getTemplateWithAttributesApi, @@ -36,9 +34,7 @@ import { CheckSquare, Copy, Edit3, - FileText, Plus, - Settings2, Square, Tag, Trash2, @@ -162,16 +158,12 @@ const TemplateForm: React.FC = ({
- - Основная информация + + {nameLabel}
- = ({ className="h-9" />
- -
- -