diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bb1bb50..b66285c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ "@tanstack/react-query": "^5.56.2", "@xyflow/react": "^12.3.4", "axios": "^1.10.0", + "html-to-image": "^1.11.11", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^6.30.1", @@ -1688,6 +1689,12 @@ "node": ">= 0.4" } }, + "node_modules/html-to-image": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz", + "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "dev": true, diff --git a/frontend/package.json b/frontend/package.json index 02a93d1..ef0f31a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "@tabler/icons-react": "^3.34.0", "axios": "^1.10.0", "@tanstack/react-query": "^5.56.2", + "html-to-image": "^1.11.11", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^6.30.1", diff --git a/frontend/src/DataManager.jsx b/frontend/src/DataManager.jsx index fab7d5e..1dc7398 100644 --- a/frontend/src/DataManager.jsx +++ b/frontend/src/DataManager.jsx @@ -14,6 +14,7 @@ import { IconUpload, IconAlertTriangle } from '@tabler/icons-react'; +import ConfirmDialog from './components/ConfirmDialog.jsx' const API_URL = '/api'; @@ -36,6 +37,7 @@ function DataManager({ entityName, entityKey, placeholder }) { const [filterType, setFilterType] = useState(''); const editInputRef = useRef(null); const pageSize = 10; + const [confirmState, setConfirmState] = useState({ open: false, text: '', onConfirm: null }); useEffect(() => { fetchItems(); @@ -118,14 +120,16 @@ function DataManager({ entityName, entityKey, placeholder }) { setError('Оба поля для массового обновления должны быть заполнены.'); return; } - const confirm = window.confirm(`Вы уверены, что хотите заменить все шлюзы "${bulkFrom}" на "${bulkTo}"? Это действие необратимо.`); - if (confirm) { - const updatedItems = items.map(i => - i.type === bulkFrom ? { ...i, type: bulkTo } : i - ); - setItems(updatedItems); - setSuccess(`Шлюзы "${bulkFrom}" были успешно заменены на "${bulkTo}". Не забудьте сохранить изменения.`); - } + setConfirmState({ + open: true, + text: `Вы уверены, что хотите заменить все шлюзы "${bulkFrom}" на "${bulkTo}"? Это действие необратимо.`, + onConfirm: () => { + const updatedItems = items.map(i => i.type === bulkFrom ? { ...i, type: bulkTo } : i); + setItems(updatedItems); + setSuccess(`Шлюзы "${bulkFrom}" были успешно заменены на "${bulkTo}". Не забудьте сохранить изменения.`); + setConfirmState({ open: false, text: '', onConfirm: null }); + }, + }); }; // Сортировка @@ -500,6 +504,14 @@ function DataManager({ entityName, entityKey, placeholder }) { )} + + setConfirmState({ open: false, text: '', onConfirm: null })} + onConfirm={() => confirmState.onConfirm?.()} + /> ); } diff --git a/frontend/src/FilterManager.jsx b/frontend/src/FilterManager.jsx index 77e17fe..92945b2 100644 --- a/frontend/src/FilterManager.jsx +++ b/frontend/src/FilterManager.jsx @@ -1,5 +1,6 @@ import { useState, useEffect, useRef } from 'react'; import api from './lib/api.js'; +import ConfirmDialog from './components/ConfirmDialog.jsx'; import { IconPlus, IconSearch, @@ -75,6 +76,7 @@ function FilterManager() { const [editingKey, setEditingKey] = useState(null); const [editingDraft, setEditingDraft] = useState({ community: '', gateway: '', description: '' }); const [selectedFilterKeys, setSelectedFilterKeys] = useState(new Set()); + const [confirmState, setConfirmState] = useState({ open: false, text: '', onConfirm: null }); // Communities directory for autocomplete const [communities, setCommunities] = useState([]); @@ -239,12 +241,15 @@ function FilterManager() { return; } - const confirm = window.confirm( - `Скопировать конфигурацию и фильтры с сервера "${sourceServer.name}" на ${targetServers.length} сервер(ов)?\n\n` + - `Целевые серверы: ${targetServers.map(s => s.name).join(', ')}` - ); - - if (!confirm) return; + let proceed = false; + await new Promise((resolve) => { + setConfirmState({ + open: true, + text: `Скопировать конфигурацию и фильтры с сервера "${sourceServer.name}" на ${targetServers.length} сервер(ов)?\n\nЦелевые серверы: ${targetServers.map(s => s.name).join(', ')}`, + onConfirm: () => { proceed = true; setConfirmState({ open: false, text: '', onConfirm: null }); resolve(); }, + }); + }); + if (!proceed) return; setLoading(true); try { @@ -334,12 +339,15 @@ function FilterManager() { return; } - const confirm = window.confirm( - `Развернуть фильтры и конфигурацию с сервера "${selectedServer.name}" на ${enabledServers.length} сервер(ов)?\n\n` + - `Целевые серверы: ${enabledServers.map(s => s.name).join(', ')}` - ); - - if (!confirm) return; + let proceed = false; + await new Promise((resolve) => { + setConfirmState({ + open: true, + text: `Развернуть фильтры и конфигурацию с сервера "${selectedServer.name}" на ${enabledServers.length} сервер(ов)?\n\nЦелевые серверы: ${enabledServers.map(s => s.name).join(', ')}`, + onConfirm: () => { proceed = true; setConfirmState({ open: false, text: '', onConfirm: null }); resolve(); }, + }); + }); + if (!proceed) return; setLoading(true); try { @@ -690,8 +698,15 @@ function FilterManager() { const deleteSelectedFilters = async () => { if (selectedFilterKeys.size === 0) return; - const confirmed = window.confirm(`Удалить выбранные фильтры (${selectedFilterKeys.size})?`); - if (!confirmed) return; + let proceed = false; + await new Promise((resolve) => { + setConfirmState({ + open: true, + text: `Удалить выбранные фильтры (${selectedFilterKeys.size})?`, + onConfirm: () => { proceed = true; setConfirmState({ open: false, text: '', onConfirm: null }); resolve(); }, + }); + }); + if (!proceed) return; const updated = serverFilters.filter((f) => !selectedFilterKeys.has(filterKey(f))); setServerFilters(updated); setSelectedFilterKeys(new Set()); @@ -1679,7 +1694,15 @@ function FilterManager() {