From f2b1db209279380441abde4df66ac03ce4ce823e Mon Sep 17 00:00:00 2001 From: shats Date: Fri, 3 Oct 2025 13:09:14 +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=BC=D0=BE=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BE=D0=BA=D0=BE=D0=BD=20=D1=81=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=87=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=20=D0=BA=D0=BB=D0=B8=D0=BA=D0=B0=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=84=D0=BE=D0=BD=D1=83=20=D0=B4=D0=BB=D1=8F=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D1=8F.=20=D0=9E=D0=B1?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20Confir?= =?UTF-8?q?mDialog,=20ConfirmDiffModal,=20ConfirmModal,=20FormModal,=20His?= =?UTF-8?q?toryModal,=20ImportModal,=20SettingsModal=20=D0=B8=20WsUpdateMo?= =?UTF-8?q?dal=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20=D1=81=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=BC=20=D0=B8=20=D1=83=D0=BD=D0=B8=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84?= =?UTF-8?q?=D0=B5=D0=B9=D1=81=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ConfirmDialog.jsx | 138 ++++++++-------- frontend/src/components/ConfirmDiffModal.jsx | 160 ++++++++++--------- frontend/src/components/ConfirmModal.jsx | 134 +++++++++------- frontend/src/components/FormModal.jsx | 100 +++++++----- frontend/src/components/HistoryModal.jsx | 14 +- frontend/src/components/ImportModal.jsx | 43 +++-- frontend/src/components/Modal.jsx | 108 +++++++------ frontend/src/components/SettingsModal.jsx | 43 +++-- frontend/src/components/WsUpdateModal.jsx | 45 ++++-- 9 files changed, 444 insertions(+), 341 deletions(-) diff --git a/frontend/src/components/ConfirmDialog.jsx b/frontend/src/components/ConfirmDialog.jsx index f46cf10..1dff978 100644 --- a/frontend/src/components/ConfirmDialog.jsx +++ b/frontend/src/components/ConfirmDialog.jsx @@ -18,76 +18,88 @@ export default function ConfirmDialog({ }) { if (!open) return null; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onCancel?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onCancel?.(); }} - > -
-
{ - 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(); + <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onCancel?.(); }} + > +
+
{ + 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}
-
- -
-
- {destructive && ( -
- + }} + > +
+
{title}
+
+ +
+
+ {destructive && ( +
+ +
+ )} +
+

{message}

- )} -
-

{message}

-
- -
- - + +
+ + +
-
+ ); } diff --git a/frontend/src/components/ConfirmDiffModal.jsx b/frontend/src/components/ConfirmDiffModal.jsx index 9bee46d..72823c6 100644 --- a/frontend/src/components/ConfirmDiffModal.jsx +++ b/frontend/src/components/ConfirmDiffModal.jsx @@ -11,93 +11,105 @@ function ConfirmDiffModal({ show, diff, onConfirm, onClose, loading = false }) { if (!show) return null; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
{ - 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(); + <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
{ + 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(); + } } - } - }} - > -
-
Подтвердить сохранение
-
- -
-
-
-
-
- Добавлено -
{added}
+ }} + > +
+
Подтвердить сохранение
+
+ +
+
+
+
+
+ Добавлено +
{added}
+
-
-
-
-
- Удалено -
{removed}
+
+
+
+ Удалено +
{removed}
+
-
-
-
-
- Изменено -
{changed}
+
+
+
+ Изменено +
{changed}
+
-
- -
- - + +
+ + +
-
+ ); } diff --git a/frontend/src/components/ConfirmModal.jsx b/frontend/src/components/ConfirmModal.jsx index 71f450c..c763ef4 100644 --- a/frontend/src/components/ConfirmModal.jsx +++ b/frontend/src/components/ConfirmModal.jsx @@ -23,75 +23,87 @@ function ConfirmModal({ onConfirm?.(); }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
{ - 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(); + <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
{ + 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}
-
- -
-
- {showIcon && Icon && ( -
- + }} + > +
+
{title}
+
+ +
+
+ {showIcon && Icon && ( +
+ +
+ )} +
+ {typeof message === 'string' ?

{message}

: message}
- )} -
- {typeof message === 'string' ?

{message}

: message}
-
- -
- - + +
+ + +
-
+ ); } diff --git a/frontend/src/components/FormModal.jsx b/frontend/src/components/FormModal.jsx index c4c5dc4..2f02420 100644 --- a/frontend/src/components/FormModal.jsx +++ b/frontend/src/components/FormModal.jsx @@ -36,55 +36,67 @@ function FormModal({ } }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
+ <> + {/* Modal Backdrop */} +
+ + {/* Modal */}
-
-
-
-
{title}
-
- -
- {children} -
- -
- - -
-
+
+
+
+
+
{title}
+
+ +
+ {children} +
+ +
+ + +
+
+
-
+ ); } diff --git a/frontend/src/components/HistoryModal.jsx b/frontend/src/components/HistoryModal.jsx index 56957bc..3bb20e5 100644 --- a/frontend/src/components/HistoryModal.jsx +++ b/frontend/src/components/HistoryModal.jsx @@ -55,13 +55,23 @@ export default function HistoryModal({ resource, show, onClose, onRolledBack }) refetch().catch(() => notify.error('Не удалось загрузить историю версий')); } + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( <> + {/* Modal Backdrop */} +
+ + {/* Modal */}
{ if (e.key === 'Escape') onClose?.(); }} >
@@ -90,7 +100,7 @@ export default function HistoryModal({ resource, show, onClose, onRolledBack }) История версий -
diff --git a/frontend/src/components/ImportModal.jsx b/frontend/src/components/ImportModal.jsx index d8eec5f..ea70faa 100644 --- a/frontend/src/components/ImportModal.jsx +++ b/frontend/src/components/ImportModal.jsx @@ -86,23 +86,34 @@ function ImportModal({ URL.revokeObjectURL(url); }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
-
-
- - {title} -
-
+ <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
+
+
+ + {title} +
+
{/* Description */} diff --git a/frontend/src/components/Modal.jsx b/frontend/src/components/Modal.jsx index 2698b50..489e9de 100644 --- a/frontend/src/components/Modal.jsx +++ b/frontend/src/components/Modal.jsx @@ -50,63 +50,75 @@ function Modal({ if (!show) return null; + const handleBackdropClick = (e) => { + if (backdrop && e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > + <> + {/* Modal Backdrop */} +
+ + {/* Modal */}
{ if (e.key === 'Escape') onClose?.(); }} >
{ - 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(); - } - } - }} + className={`modal-dialog${size !== 'md' ? ` modal-${size}` : ''}${scrollable ? ' modal-dialog-scrollable' : ''}${centered ? ' modal-dialog-centered' : ''}`} + role="document" > - {title && ( -
-
{title}
-
+ )} + +
+ {children}
- )} - -
- {children} + + {footer && ( +
+ {footer} +
+ )}
- - {footer && ( -
- {footer} -
- )}
-
+ ); } diff --git a/frontend/src/components/SettingsModal.jsx b/frontend/src/components/SettingsModal.jsx index cf9ffe8..1cddd3f 100644 --- a/frontend/src/components/SettingsModal.jsx +++ b/frontend/src/components/SettingsModal.jsx @@ -103,23 +103,34 @@ export default function SettingsModal({ open, onClose }) { } }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
-
-
- - Настройки интерфейса -
-
+ <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
+
+
+ + Настройки интерфейса +
+
{error && setError('')} />} diff --git a/frontend/src/components/WsUpdateModal.jsx b/frontend/src/components/WsUpdateModal.jsx index 8e48ed9..f44bfdf 100644 --- a/frontend/src/components/WsUpdateModal.jsx +++ b/frontend/src/components/WsUpdateModal.jsx @@ -117,24 +117,35 @@ function WsUpdateModal({ show, url, onClose }) { ); }; + const handleBackdropClick = (e) => { + if (e.target === e.currentTarget) { + onClose?.(); + } + }; + return ( -
{ if (e.key === 'Escape') onClose?.(); }} - > -
-
-
-
- - Логи запуска - {getStatusBadge()} -
-
+ <> + {/* Modal Backdrop */} +
+ + {/* Modal */} +
{ if (e.key === 'Escape') onClose?.(); }} + > +
+
+
+
+ + Логи запуска + {getStatusBadge()} +
+
{/* Controls */}