diff --git a/frontend/src/components/NotifyProvider.jsx b/frontend/src/components/NotifyProvider.jsx index e470e37..975d860 100644 --- a/frontend/src/components/NotifyProvider.jsx +++ b/frontend/src/components/NotifyProvider.jsx @@ -121,31 +121,50 @@ function iconByType(type) { function NotifyViewport({ items, onClose }) { return ( -
-
- {items.map((n) => ( -
-
-
{iconByType(n.type)}
-
+
+ {items.map((n) => ( +
+
+
+ {iconByType(n.type)} + + {n.type === 'success' && 'Успех'} + {n.type === 'error' && 'Ошибка'} + {n.type === 'warning' && 'Внимание'} + {n.type === 'info' && 'Информация'} + +
+
+
{n.message} - {n.details && ( -
- Подробнее -
{typeof n.details === 'string' ? n.details : JSON.stringify(n.details, null, 2)}
-
- )} - {n.count > 1 && ( - ×{n.count} - )} + {n.count > 1 && ×{n.count}}
- + {n.details && ( +
+ Подробнее +
+                    {typeof n.details === 'string' ? n.details : JSON.stringify(n.details, null, 2)}
+                  
+
+ )}
- ))} -
+
+ ))}
); } diff --git a/frontend/src/components/ToastContainer.jsx b/frontend/src/components/ToastContainer.jsx index 0986adf..7bc1adf 100644 --- a/frontend/src/components/ToastContainer.jsx +++ b/frontend/src/components/ToastContainer.jsx @@ -3,12 +3,12 @@ import { IconCheck, IconX, IconAlertTriangle, - IconInfoCircle, - IconRefresh + IconInfoCircle } from '@tabler/icons-react' /** - * Контекст для глобального управления toast уведомлениями + * Контекст для глобального управления toast уведомлениями. + * Тосты в стиле Tabler Alerts, всплывают справа как Tabler Toasts. */ const ToastContext = createContext({ success: () => {}, @@ -19,24 +19,29 @@ const ToastContext = createContext({ export const useToast = () => useContext(ToastContext) +const DEFAULT_TITLES = { + success: 'Выполнено успешно', + error: 'Произошла ошибка', + warning: 'Внимание', + info: 'Информация', +} + /** - * Toast уведомление (отдельный элемент) + * Один toast: разметка Tabler (toast + toast-header + toast-body), стиль Tabler Alerts. */ function Toast({ toast, onClose }) { const [isLeaving, setIsLeaving] = useState(false) useEffect(() => { if (toast.duration && toast.duration > 0) { - const timer = setTimeout(() => { - handleClose() - }, toast.duration) + const timer = setTimeout(() => handleClose(), toast.duration) return () => clearTimeout(timer) } }, [toast.duration]) const handleClose = () => { setIsLeaving(true) - setTimeout(() => onClose(toast.id), 300) // анимация 300ms + setTimeout(() => onClose(toast.id), 300) } const getIcon = () => { @@ -45,47 +50,53 @@ function Toast({ toast, onClose }) { case 'error': return case 'warning': return case 'info': return - default: return null + default: return } } - const getColorClass = () => { - switch (toast.type) { - case 'success': return 'alert-success' - case 'error': return 'alert-danger' - case 'warning': return 'alert-warning' - case 'info': return 'alert-info' - default: return 'alert-info' - } - } + const alertClass = { + success: 'alert-success', + error: 'alert-danger', + warning: 'alert-warning', + info: 'alert-info', + }[toast.type] || 'alert-info' + + const title = toast.title ?? DEFAULT_TITLES[toast.type] return (
-
-
- {getIcon()} +
+
+ {getIcon()} + {title} +
-
- {toast.title && ( -

{toast.title}

- )} -
{toast.message}
- - {/* Действия */} +
+
{toast.message}
{toast.actions && toast.actions.length > 0 && (
{toast.actions.map((action, idx) => (
)}
-
) @@ -152,46 +157,73 @@ export function ToastContainer({ children }) { return ( {children} - - {/* Контейнер с toast'ами */} -
- {toasts.map(toast => ( - + + {/* Контейнер тостов справа (как в Tabler) */} +
+ {toasts.map((t) => ( + ))}
- {/* CSS анимации */} + {/* Стили: тосты в стиле Tabler Alerts, всплывают справа */}