diff --git a/frontend/src/DataManager.jsx b/frontend/src/DataManager.jsx
index 10b0558..e45d134 100644
--- a/frontend/src/DataManager.jsx
+++ b/frontend/src/DataManager.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useRef } from 'react';
import axios from 'axios';
import {
IconPlus,
@@ -30,6 +30,12 @@ function DataManager({ entityName, entityKey, placeholder }) {
const [loading, setLoading] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [sortField, setSortField] = useState('domain');
+ const [sortOrder, setSortOrder] = useState('asc');
+ const [filterType, setFilterType] = useState('');
+ const editInputRef = useRef(null);
+ const pageSize = 10;
useEffect(() => {
fetchItems();
@@ -122,10 +128,56 @@ function DataManager({ entityName, entityKey, placeholder }) {
}
};
- const filteredItems = items.filter(i =>
+ // Сортировка
+ const sortedItems = [...items].sort((a, b) => {
+ let valA = a[sortField] || '';
+ let valB = b[sortField] || '';
+ if (typeof valA === 'string') valA = valA.toLowerCase();
+ if (typeof valB === 'string') valB = valB.toLowerCase();
+ if (valA < valB) return sortOrder === 'asc' ? -1 : 1;
+ if (valA > valB) return sortOrder === 'asc' ? 1 : -1;
+ return 0;
+ });
+
+ // Фильтрация по шлюзу
+ const filteredByType = filterType
+ ? sortedItems.filter(i => i.type === filterType)
+ : sortedItems;
+
+ // Поиск
+ const filteredItems = filteredByType.filter(i =>
i.domain.toLowerCase().includes(searchTerm.toLowerCase())
);
+ // Пагинация
+ const totalPages = Math.ceil(filteredItems.length / pageSize);
+ const paginatedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
+
+ // Для фильтра - список всех уникальных шлюзов
+ const allTypes = Array.from(new Set(items.map(i => i.type)));
+
+ // Сортировка по клику
+ const handleSort = (field) => {
+ if (sortField === field) {
+ setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
+ } else {
+ setSortField(field);
+ setSortOrder('asc');
+ }
+ };
+
+ // Улучшенный инлайн-редакт
+ useEffect(() => {
+ if (editingDomain && editInputRef.current) {
+ editInputRef.current.focus();
+ }
+ }, [editingDomain]);
+
+ const handleEditKeyDown = (e, domainName) => {
+ if (e.key === 'Enter') handleSaveEdit(domainName);
+ if (e.key === 'Escape') handleCancelEdit();
+ };
+
return (
<>
{error && (
@@ -278,110 +330,105 @@ function DataManager({ entityName, entityKey, placeholder }) {
- {/* Search and Table Card */}
-
-
Список {entityName}
-
-
-
+
+
Список {entityName === 'домен' ? 'доменов' : entityName}
+
+ {/* Фильтр по шлюзу */}
+
+ {/* Поиск */}
+
+
+
+
setSearchTerm(e.target.value)}
+ onChange={e => { setSearchTerm(e.target.value); setCurrentPage(1); }}
/>
-
-
-
-
-
- | {entityName} |
- Шлюз |
- |
-
-
-
- {filteredItems.map((item, index) => (
-
-
- {editingDomain === item.domain ? (
+
- {filteredItems.length === 0 && (
-
-
-
-
- Нет {entityName} для отображения
-
- Добавьте первый {entityName} используя форму слева.
-
-
- )}
+
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+ |
+
+ ))}
+
+
+ {/* Пагинация */}
+ {totalPages > 1 && (
+
+
+ -
+
+
+ {Array.from({length: totalPages}, (_, i) => i + 1).map(page => (
+ -
+
+
+ ))}
+ -
+
+
+
+
+ )}