feat: Обновить компоненты интерфейса, добавив новый заголовок страницы и состояние пустого контента в менеджерах ASNs, Domains и IPRanges, а также улучшить стили для адаптивного отображения
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m0s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m0s
This commit is contained in:
@@ -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 (
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol className="breadcrumb">
|
||||
{items.map((it, idx) => (
|
||||
<li key={idx} className={`breadcrumb-item${idx === items.length - 1 ? ' active' : ''}`} aria-current={idx === items.length - 1 ? 'page' : undefined}>
|
||||
{it.to && idx !== items.length - 1 ? (
|
||||
<Link to={it.to}>{it.label}</Link>
|
||||
) : (
|
||||
<span>{it.label}</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default Breadcrumbs
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
|
||||
function EmptyState({
|
||||
icon: Icon,
|
||||
title = 'Нет данных',
|
||||
description,
|
||||
action,
|
||||
}) {
|
||||
return (
|
||||
<div className="empty">
|
||||
<div className="empty-icon">
|
||||
{Icon ? <Icon size={48} className="text-muted" /> : null}
|
||||
</div>
|
||||
<p className="empty-title">{title}</p>
|
||||
{description && (
|
||||
<p className="empty-subtitle text-muted">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
{action && (
|
||||
<div className="empty-action">
|
||||
{action}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EmptyState
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
|
||||
function PageHeader({ title, pretitle, actions, meta }) {
|
||||
return (
|
||||
<div className="page-header d-print-none mb-4">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
{pretitle && (
|
||||
<div className="page-pretitle">{pretitle}</div>
|
||||
)}
|
||||
<h2 className="page-title">{title}</h2>
|
||||
{meta && (
|
||||
<div className="text-muted small mt-1">{meta}</div>
|
||||
)}
|
||||
</div>
|
||||
{actions && (
|
||||
<div className="col-auto ms-auto d-print-none">
|
||||
{actions}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageHeader
|
||||
|
||||
|
||||
@@ -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({
|
||||
)}
|
||||
</div>
|
||||
<span className="vr d-none d-lg-inline" />
|
||||
<div className="btn-group">
|
||||
<div className="btn-group d-none d-sm-inline-flex">
|
||||
{onRefresh && (
|
||||
<button className="btn btn-outline-secondary" type="button" onClick={onRefresh} disabled={disableRefresh} title="Обновить данные">
|
||||
<IconRefresh className={loading ? 'spin me-1' : 'me-1'} /> Обновить
|
||||
@@ -159,6 +161,48 @@ function PageHeaderActions({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Компактное меню для маленьких экранов */}
|
||||
{secondaryButtons && (
|
||||
<div className="btn-group d-inline-flex d-sm-none">
|
||||
<PortalDropdown
|
||||
buttonClassName="btn btn-outline-secondary"
|
||||
buttonProps={{ title: 'Ещё действия' }}
|
||||
align="left"
|
||||
buttonContent={<><IconDots className="me-1" /> Ещё</>}
|
||||
>
|
||||
{onRefresh && (
|
||||
<button className="dropdown-item" type="button" onClick={onRefresh} disabled={disableRefresh}>
|
||||
<IconRefresh className="me-1" /> Обновить
|
||||
</button>
|
||||
)}
|
||||
{onImport && (
|
||||
<button className="dropdown-item" type="button" onClick={onImport}>
|
||||
<IconUpload className="me-1" /> Импорт
|
||||
</button>
|
||||
)}
|
||||
{onExport && (
|
||||
<button className="dropdown-item" type="button" onClick={onExport} disabled={disableExport}>
|
||||
<IconDownload className="me-1" /> Экспорт
|
||||
</button>
|
||||
)}
|
||||
{onClear && (
|
||||
<button className="dropdown-item" type="button" onClick={onClear} disabled={disableClear}>
|
||||
Очистить
|
||||
</button>
|
||||
)}
|
||||
{onClearCommunities && (
|
||||
<button className="dropdown-item" type="button" onClick={onClearCommunities} disabled={disableClearCommunities}>
|
||||
<IconEraser className="me-1" /> Очистить community
|
||||
</button>
|
||||
)}
|
||||
{onHistory && (
|
||||
<button className="dropdown-item" type="button" onClick={onHistory}>
|
||||
История
|
||||
</button>
|
||||
)}
|
||||
</PortalDropdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,4 +26,20 @@ export default function TableSkeleton({ rows = 10, cols = 3 }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function TableEmpty({ cols = 3, children }) {
|
||||
return (
|
||||
<div className="table-responsive">
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={cols} className="text-center text-muted py-5">
|
||||
{children}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user