diff --git a/frontend/src/ASNsNewManager.jsx b/frontend/src/ASNsNewManager.jsx index d951a90..ccf9840 100644 --- a/frontend/src/ASNsNewManager.jsx +++ b/frontend/src/ASNsNewManager.jsx @@ -17,9 +17,12 @@ import { IconClock, IconFileText } from '@tabler/icons-react'; -import TableSkeleton from './components/TableSkeleton.jsx'; +import TableSkeleton, { TableEmpty } from './components/TableSkeleton.jsx'; import S3MetaBar from './components/S3MetaBar.jsx'; import PageHeaderActions from './components/PageHeaderActions.jsx'; +import PageHeader from './components/PageHeader.jsx'; +import EmptyState from './components/EmptyState.jsx'; +import Breadcrumbs from './components/Breadcrumbs.jsx'; import ConfirmDialog from './components/ConfirmDialog.jsx'; import WsUpdateModal from './components/WsUpdateModal.jsx'; import ConfirmDiffModal from './components/ConfirmDiffModal.jsx'; @@ -409,14 +412,17 @@ function ASNsNewManager() { )} -
-
-
-

ASNs

-
Главная / Данные / ASNs
-
-
- + )} + actions={( + -
-
-
+ /> + )} + />
{/* Add New ASN Card */} @@ -551,6 +556,10 @@ function ASNsNewManager() { {/* Мета-информация будет показана внизу карточки */} {loading ? ( + ) : paginatedItems.length === 0 ? ( + + Импорт} /> + ) : (
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5af0209..2d89807 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -211,7 +211,7 @@ function MainLayout() { const activeTab = getActiveTab(); return ( -
+
{/* Верхнее меню Tabler Admin */}
@@ -322,7 +322,7 @@ function MainLayout() {
-
+
} /> } /> @@ -336,7 +336,7 @@ function MainLayout() { } />
-
+
Версия UI: {import.meta?.env?.VITE_APP_VERSION || 'dev'}
diff --git a/frontend/src/CommunitiesManager.jsx b/frontend/src/CommunitiesManager.jsx index d23c5fc..d1c36e1 100644 --- a/frontend/src/CommunitiesManager.jsx +++ b/frontend/src/CommunitiesManager.jsx @@ -16,6 +16,10 @@ import { IconFilter } from '@tabler/icons-react'; import PageHeaderActions from './components/PageHeaderActions.jsx'; +import PageHeader from './components/PageHeader.jsx'; +import EmptyState from './components/EmptyState.jsx'; +import { TableEmpty } from './components/TableSkeleton.jsx'; +import Breadcrumbs from './components/Breadcrumbs.jsx'; const API_URL = '/api'; @@ -224,14 +228,17 @@ function CommunitiesManager() { return (
-
-
-
-

Справочник Community

-
Главная / Данные / Community
-
-
- Справочник Community)} + pretitle={( + + )} + actions={( + -
-
-
+ /> + )} + /> {error && (
@@ -319,7 +325,9 @@ function CommunitiesManager() { {loading ? (
) : paginated.length === 0 ? ( - + ) : paginated.map((item) => (
Загрузка...
Нет записей
+ Добавить} /> +
diff --git a/frontend/src/Dashboard.jsx b/frontend/src/Dashboard.jsx index 1b03118..92bb712 100644 --- a/frontend/src/Dashboard.jsx +++ b/frontend/src/Dashboard.jsx @@ -16,6 +16,8 @@ import { IconCreditCard, IconSearch } from '@tabler/icons-react'; +import PageHeader from './components/PageHeader.jsx'; +import Breadcrumbs from './components/Breadcrumbs.jsx'; function StatCard({ icon: Icon, color, value, title, subtitle, to }) { return ( @@ -172,19 +174,15 @@ function Dashboard() {
{/* Глобальный поиск удалён */} {/* Заголовок страницы */} -
-
-
-

Панель

-
Краткая сводка по данным
-
-
- -
-
-
+ + )} + actions={()} + /> {/* Основные метрики */}
diff --git a/frontend/src/DomainsNewManager.jsx b/frontend/src/DomainsNewManager.jsx index 5f4d823..6cbbbe9 100644 --- a/frontend/src/DomainsNewManager.jsx +++ b/frontend/src/DomainsNewManager.jsx @@ -17,7 +17,10 @@ import { IconClock, IconFileText } from '@tabler/icons-react'; -import TableSkeleton from './components/TableSkeleton.jsx'; +import TableSkeleton, { TableEmpty } from './components/TableSkeleton.jsx'; +import PageHeader from './components/PageHeader.jsx'; +import EmptyState from './components/EmptyState.jsx'; +import Breadcrumbs from './components/Breadcrumbs.jsx'; import S3MetaBar from './components/S3MetaBar.jsx'; import PageHeaderActions from './components/PageHeaderActions.jsx'; import ConfirmDialog from './components/ConfirmDialog.jsx'; @@ -414,14 +417,17 @@ function DomainsNewManager() {
)} -
-
-
-

Домены

-
Главная / Данные / Домены
-
-
- + )} + actions={( + -
-
-
+ /> + )} + />
{/* Add New Item Card */} @@ -557,6 +562,10 @@ function DomainsNewManager() { {/* Мета-информация будет показана внизу карточки */} {loading ? ( + ) : paginatedItems.length === 0 ? ( + + Импорт} /> + ) : (
diff --git a/frontend/src/IPRangesManager.jsx b/frontend/src/IPRangesManager.jsx index d0c911d..d8cdb71 100644 --- a/frontend/src/IPRangesManager.jsx +++ b/frontend/src/IPRangesManager.jsx @@ -17,9 +17,12 @@ import { IconClock, IconFileText } from '@tabler/icons-react'; -import TableSkeleton from './components/TableSkeleton.jsx'; +import TableSkeleton, { TableEmpty } from './components/TableSkeleton.jsx'; import S3MetaBar from './components/S3MetaBar.jsx'; import PageHeaderActions from './components/PageHeaderActions.jsx'; +import PageHeader from './components/PageHeader.jsx'; +import EmptyState from './components/EmptyState.jsx'; +import Breadcrumbs from './components/Breadcrumbs.jsx'; import ConfirmDialog from './components/ConfirmDialog.jsx'; import WsUpdateModal from './components/WsUpdateModal.jsx'; import ConfirmDiffModal from './components/ConfirmDiffModal.jsx'; @@ -668,14 +671,17 @@ function IPRangesManager() { )} -
-
-
-

IP-диапазоны

-
Главная / Данные / IP ranges
-
-
- + )} + actions={( + -
-
-
+ /> + )} + />
{/* Add New Item Card */} @@ -814,6 +819,10 @@ function IPRangesManager() { {/* Мета-информация будет показана внизу карточки */} {loading ? ( + ) : paginatedItems.length === 0 ? ( + + Импорт} /> + ) : (
diff --git a/frontend/src/components/Breadcrumbs.jsx b/frontend/src/components/Breadcrumbs.jsx new file mode 100644 index 0000000..6e89d59 --- /dev/null +++ b/frontend/src/components/Breadcrumbs.jsx @@ -0,0 +1,25 @@ +import React from 'react' +import { Link } from 'react-router-dom' + +function Breadcrumbs({ items = [] }) { + if (!Array.isArray(items) || items.length === 0) return null + return ( + + ) +} + +export default Breadcrumbs + + diff --git a/frontend/src/components/EmptyState.jsx b/frontend/src/components/EmptyState.jsx new file mode 100644 index 0000000..a88e604 --- /dev/null +++ b/frontend/src/components/EmptyState.jsx @@ -0,0 +1,31 @@ +import React from 'react' + +function EmptyState({ + icon: Icon, + title = 'Нет данных', + description, + action, +}) { + return ( +
+
+ {Icon ? : null} +
+

{title}

+ {description && ( +

+ {description} +

+ )} + {action && ( +
+ {action} +
+ )} +
+ ) +} + +export default EmptyState + + diff --git a/frontend/src/components/NotifyProvider.jsx b/frontend/src/components/NotifyProvider.jsx index 714baff..e470e37 100644 --- a/frontend/src/components/NotifyProvider.jsx +++ b/frontend/src/components/NotifyProvider.jsx @@ -90,9 +90,12 @@ export function useNotify() { // Единый тост-успех для мутаций (POST/DELETE/PUT/PATCH) export function notifyMutationSuccess(message, details) { + // Тихий режим: по умолчанию успех показываем не тостом, а через inline-баннер на страницах. + // Оставляем возможность вручную вызвать тост при необходимости, если передан details?.forceToast === true try { const text = message || 'Операция выполнена'; - if (typeof window !== 'undefined' && window.notify?.success) { + const forceToast = Boolean(details && details.forceToast); + if (forceToast && typeof window !== 'undefined' && window.notify?.success) { window.notify.success(text, details); } } catch {} diff --git a/frontend/src/components/PageHeader.jsx b/frontend/src/components/PageHeader.jsx new file mode 100644 index 0000000..0affaa7 --- /dev/null +++ b/frontend/src/components/PageHeader.jsx @@ -0,0 +1,28 @@ +import React from 'react' + +function PageHeader({ title, pretitle, actions, meta }) { + return ( +
+
+
+ {pretitle && ( +
{pretitle}
+ )} +

{title}

+ {meta && ( +
{meta}
+ )} +
+ {actions && ( +
+ {actions} +
+ )} +
+
+ ) +} + +export default PageHeader + + diff --git a/frontend/src/components/PageHeaderActions.jsx b/frontend/src/components/PageHeaderActions.jsx index 00343b5..5993cbc 100644 --- a/frontend/src/components/PageHeaderActions.jsx +++ b/frontend/src/components/PageHeaderActions.jsx @@ -5,7 +5,8 @@ import { IconDeviceFloppy, IconPlayerPlay, IconBolt, - IconEraser + IconEraser, + IconDots } from '@tabler/icons-react'; import PortalDropdown from './PortalDropdown.jsx'; import { IconClock } from '@tabler/icons-react'; @@ -31,6 +32,7 @@ function PageHeaderActions({ onOnlineUpdate, onBackgroundUpdate, }) { + const secondaryButtons = Boolean(onImport || onExport || onClear || onClearCommunities || onHistory); // Рендер плейсхолдеров во время загрузки/перезагрузки страницы if (loading) { const showUpdateGroup = Boolean(onBackgroundUpdate || onOnlineUpdate); @@ -95,7 +97,7 @@ function PageHeaderActions({ )} -
+
{onRefresh && (
)}
+ {/* Компактное меню для маленьких экранов */} + {secondaryButtons && ( +
+ Ещё} + > + {onRefresh && ( + + )} + {onImport && ( + + )} + {onExport && ( + + )} + {onClear && ( + + )} + {onClearCommunities && ( + + )} + {onHistory && ( + + )} + +
+ )} ); } diff --git a/frontend/src/components/TableSkeleton.jsx b/frontend/src/components/TableSkeleton.jsx index 2b193a4..115a085 100644 --- a/frontend/src/components/TableSkeleton.jsx +++ b/frontend/src/components/TableSkeleton.jsx @@ -26,4 +26,20 @@ export default function TableSkeleton({ rows = 10, cols = 3 }) { ); } +export function TableEmpty({ cols = 3, children }) { + return ( +
+
+ + + + + +
+ {children} +
+
+ ); +} + diff --git a/frontend/src/index.css b/frontend/src/index.css index 216b671..4fca928 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1 +1,5 @@ /* Using Bootstrap styles, this file can be empty or used for custom global styles. */ + +/* Sticky footer helpers */ +.page { min-height: 100vh; display: flex; flex-direction: column; } +.footer { margin-top: auto; } \ No newline at end of file