From 9bff2390354395830175138347c444ddee5d8a75 Mon Sep 17 00:00:00 2001 From: shats Date: Fri, 3 Oct 2025 10:56:56 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=82=D0=B0=20HistoryModal=20=D1=81=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D0=BD=D0=BE=D0=B9=20=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D1=82=D0=BE=D0=B9=20=D0=BF=D0=BE=D0=B4=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=20=D1=81=20=D0=B2=D1=81=D1=82=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=BC=20backdrop.=20=D0=A3=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D1=80=D1=83=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D1=8B=20=D0=B8=20=D1=83=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=BC=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BE?= =?UTF-8?q?=D0=BA=D0=BD=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/HistoryModal.jsx | 217 +++++++++++++---------- frontend/src/components/Modal.jsx | 146 ++++++--------- 2 files changed, 177 insertions(+), 186 deletions(-) diff --git a/frontend/src/components/HistoryModal.jsx b/frontend/src/components/HistoryModal.jsx index aaa4a88..56957bc 100644 --- a/frontend/src/components/HistoryModal.jsx +++ b/frontend/src/components/HistoryModal.jsx @@ -1,4 +1,3 @@ -import Modal from './Modal'; import { useState } from 'react'; import api from '../lib/api.js'; import { useQuery } from '@tanstack/react-query'; @@ -9,7 +8,7 @@ import { formatDateTimeWithRelative } from '../lib/datetime.js'; /** * HistoryModal - модальное окно истории версий - * Рефакторинг: теперь использует базовый Modal компонент + * Простой подход со встроенным backdrop */ export default function HistoryModal({ resource, show, onClose, onRolledBack }) { const [loading, setLoading] = useState(false); @@ -50,105 +49,135 @@ export default function HistoryModal({ resource, show, onClose, onRolledBack }) }); }; - const handleOpen = () => { - if (show) { - refetch().catch(() => notify.error('Не удалось загрузить историю версий')); - } - }; + if (!show) return null; + + if (show && items.length === 0) { + refetch().catch(() => notify.error('Не удалось загрузить историю версий')); + } return ( <> - - - История версий - - } - size="lg" - centered - scrollable - onOpen={handleOpen} +
{ if (e.key === 'Escape') onClose?.(); }} > - {/* Info bar */} -
-
- - Ресурс: {resource} -
- -
+
+
+ + История версий +
+
+ +
+ {/* Info bar */} +
+
+ + Ресурс: {resource} +
+ +
- {/* Table */} -
- - - - - - - - - - - - {items.length === 0 ? ( - - - - ) : ( - items.map((v) => ( - - - - - - - - )) - )} - -
ВерсияДатаРазмерETagДействия
-
-
- -
-

Нет данных

-

- Версионирование бакета может быть отключено -

-
-
- {v.versionId} - {v.isLatest && ( - Текущая - )} - - {v.lastModified ? formatDateTimeWithRelative(v.lastModified) : '—'} - {typeof v.size === 'number' ? `${v.size} байт` : '—'}{v.etag || '—'} - {!v.isLatest && ( - - )} -
+ {/* Table */} +
+ + + + + + + + + + + + {items.length === 0 ? ( + + + + ) : ( + items.map((v) => ( + + + + + + + + )) + )} + +
ВерсияДатаРазмерETagДействия
+
+
+ +
+

Нет данных

+

+ Версионирование бакета может быть отключено +

+
+
+ {v.versionId} + {v.isLatest && ( + Текущая + )} + + {v.lastModified ? formatDateTimeWithRelative(v.lastModified) : '—'} + {typeof v.size === 'number' ? `${v.size} байт` : '—'}{v.etag || '—'} + {!v.isLatest && ( + + )} +
+
+
+ +
+ +
+
-
+ { if (show) { document.body.classList.add('modal-open'); - modalRef.current?.focus(); + setTimeout(() => modalRef.current?.focus(), 100); onOpen?.(); } else { document.body.classList.remove('modal-open'); @@ -64,90 +50,72 @@ function Modal({ if (!show) return null; - const handleBackdropClick = (e) => { - if (backdrop && e.target === backdropRef.current) { - onClose?.(); - } - }; - return ( - <> - {/* Backdrop - гарантированное затемнение */} - {backdrop && ( -
- )} - - {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + >
-
-
- {title && ( -
-
{title}
-
- )} - -
- {children} +
{ + if (e.key === 'Tab') { + const c = e.currentTarget; + const focusable = c.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + if (!focusable || focusable.length === 0) return; + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } + }} + > + {title && ( +
+
{title}
+
- - {footer && ( -
- {footer} -
- )} + )} + +
+ {children}
+ + {footer && ( +
+ {footer} +
+ )}
- +
); } export default Modal; /** - * =================================== - * ГОТОВЫЕ ПРЕСЕТЫ ДЛЯ РАЗНЫХ ЦЕЛЕЙ - * =================================== + * Готовые пресеты для быстрого использования */ -/** - * Компактная модалка для быстрых подтверждений - */ export function SmallModal({ show, onClose, title, children, footer, ...props }) { return (