diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 1e3b51f..bb1bb50 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@tabler/core": "^1.3.2",
"@tabler/icons-react": "^3.34.0",
+ "@tanstack/react-query": "^5.56.2",
"@xyflow/react": "^12.3.4",
"axios": "^1.10.0",
"react": "^19.1.0",
@@ -632,6 +633,32 @@
"react": ">= 16"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.83.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.1.tgz",
+ "integrity": "sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.84.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.84.2.tgz",
+ "integrity": "sha512-cZadySzROlD2+o8zIfbD978p0IphuQzRWiiH3I2ugnTmz4jbjc0+TdibpwqxlzynEen8OulgAg+rzdNF37s7XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.83.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"dev": true,
diff --git a/frontend/package.json b/frontend/package.json
index cd99cba..02a93d1 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,6 +13,7 @@
"@tabler/core": "^1.3.2",
"@tabler/icons-react": "^3.34.0",
"axios": "^1.10.0",
+ "@tanstack/react-query": "^5.56.2",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^6.30.1",
diff --git a/frontend/src/components/ConfirmDialog.jsx b/frontend/src/components/ConfirmDialog.jsx
new file mode 100644
index 0000000..e4ad993
--- /dev/null
+++ b/frontend/src/components/ConfirmDialog.jsx
@@ -0,0 +1,32 @@
+import { useEffect, useRef } from 'react'
+
+export default function ConfirmDialog({ open, title, message, confirmText = 'Подтвердить', cancelText = 'Отмена', onConfirm, onCancel }) {
+ const ref = useRef(null)
+ useEffect(() => {
+ if (open && ref.current) {
+ try { ref.current.querySelector('button[data-primary]')?.focus() } catch {}
+ }
+ }, [open])
+ if (!open) return null
+ return (
+
+
+
+
+
{title || 'Подтверждение'}
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+
diff --git a/frontend/src/components/HistoryModal.jsx b/frontend/src/components/HistoryModal.jsx
index fb4dd88..861cf13 100644
--- a/frontend/src/components/HistoryModal.jsx
+++ b/frontend/src/components/HistoryModal.jsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import api from '../lib/api.js';
+import { useQuery } from '@tanstack/react-query';
import { useNotify } from './NotifyProvider.jsx';
import { IconHistory, IconRefresh, IconDeviceFloppy } from '@tabler/icons-react';
@@ -8,20 +9,18 @@ export default function HistoryModal({ resource, show, onClose, onRolledBack })
const [items, setItems] = useState([]);
const notify = useNotify();
- const fetchHistory = async () => {
- if (!resource) return;
- setLoading(true);
- try {
- const res = await api.get(`/history/${resource}`);
- setItems(Array.isArray(res.data?.items) ? res.data.items : []);
- } catch (e) {
- notify.error('Не удалось загрузить историю версий');
- } finally {
- setLoading(false);
- }
- };
+ const { refetch } = useQuery({
+ queryKey: ['history', resource],
+ enabled: false,
+ queryFn: async () => {
+ const res = await api.get(`/history/${resource}`)
+ const data = Array.isArray(res.data?.items) ? res.data.items : []
+ setItems(data)
+ return data
+ },
+ })
- useEffect(() => { if (show) fetchHistory(); }, [show, resource]);
+ useEffect(() => { if (show) refetch().catch(() => notify.error('Не удалось загрузить историю версий')); }, [show, resource]);
const rollback = async (versionId) => {
if (!versionId) return;
@@ -54,7 +53,7 @@ export default function HistoryModal({ resource, show, onClose, onRolledBack })
Ресурс: {resource}
-