import { useState, useEffect, useRef, useCallback } from 'react'; import api from './lib/api.js'; import { IconPlus, IconSearch, IconEdit, IconTrash, IconCheck, IconX, IconDatabase, IconRefresh, IconArrowsExchange, IconDownload, IconUpload, IconAlertTriangle } from '@/lib/icons'; import { ConfirmDialog } from '@/components/shared/feedback' const API_URL = '/api'; function DataManager({ entityName, entityKey, placeholder }) { const [items, setItems] = useState([]); const [newItem, setNewItem] = useState({ domain: '', type: 'SharkFIN' }); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [editingDomain, setEditingDomain] = useState(null); const [editingValue, setEditingValue] = useState(''); const [bulkFrom, setBulkFrom] = useState('SharkFIN'); const [bulkTo, setBulkTo] = useState('SharkAM'); 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; const [confirmState, setConfirmState] = useState({ open: false, text: '', onConfirm: null }); const fetchItems = useCallback(async () => { setLoading(true); try { const response = await api.get(`/${entityKey}`); setItems(response.data); setError(''); } catch (error) { console.error(`Error fetching ${entityKey}:`, error); setError(`Не удалось загрузить ${entityName}. Проверьте, запущен ли бэкенд.`); } finally { setLoading(false); } }, [entityKey, entityName]); useEffect(() => { fetchItems(); }, [fetchItems]); const handleAddItem = () => { if (newItem.domain.trim() === '') { setError(`Имя ${entityName} не может быть пустым.`); return; } setError(''); setItems([...items, newItem]); setNewItem({ domain: '', type: 'SharkFIN' }); }; const handleEdit = (item) => { setEditingDomain(item.domain); setEditingValue(item.type); }; const handleSaveEdit = (domainName) => { const updatedItems = items.map(i => i.domain === domainName ? { ...i, type: editingValue } : i ); setItems(updatedItems); setEditingDomain(null); }; const handleCancelEdit = () => { setEditingDomain(null); }; const handleDeleteItem = (domainNameToDelete) => { setItems(items.filter(i => i.domain !== domainNameToDelete)); }; const confirmDelete = (item) => { setItemToDelete(item); setShowDeleteModal(true); }; const executeDelete = () => { if (itemToDelete) { handleDeleteItem(itemToDelete.domain); setShowDeleteModal(false); setItemToDelete(null); } }; const handleSaveChanges = async () => { setLoading(true); try { await api.post(`/${entityKey}`, { domains: items }); setSuccess('Изменения успешно сохранены!'); setTimeout(() => setSuccess(''), 3000); } catch (error) { console.error('Error saving changes:', error); setError('Не удалось сохранить изменения.'); } finally { setLoading(false); } }; const handleBulkUpdate = () => { if (!bulkFrom || !bulkTo) { setError('Оба поля для массового обновления должны быть заполнены.'); return; } 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 }); }, }); }; // Сортировка 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 && (
{error}
)} {success && (
{success}
)}
{/* Add New Item Card */}

Добавить новый {entityName}

{ e.preventDefault(); handleAddItem(); }}>
setNewItem({ ...newItem, domain: e.target.value })} />
setNewItem({ ...newItem, type: e.target.value })} />
{/* Bulk Update Card */}

Массовое обновление

setBulkFrom(e.target.value)} />
setBulkTo(e.target.value)} />
{/* Actions Card */}

Действия

Список {entityName === 'домен' ? 'доменов' : entityName}

{/* Фильтр по шлюзу */} {/* Поиск */}
{ setSearchTerm(e.target.value); setCurrentPage(1); }} />
{paginatedItems.map((item) => ( ))}
handleSort('domain')}> Домен {sortField === 'domain' && ( {sortOrder === 'asc' ? '▲' : '▼'} )} handleSort('type')}> Шлюз {sortField === 'type' && ( {sortOrder === 'asc' ? '▲' : '▼'} )}
{item.domain} {item.type} {editingDomain === item.domain ? ( <> setEditingValue(e.target.value)} onKeyDown={e => handleEditKeyDown(e, item.domain)} style={{maxWidth: 120}} /> ) : ( <> )}
{/* Пагинация */} {totalPages > 1 && (
Показано {((currentPage - 1) * pageSize) + 1} - {Math.min(currentPage * pageSize, filteredItems.length)} из {filteredItems.length}
  • {(() => { const pages = []; let start = Math.max(1, currentPage - 2); let end = Math.min(totalPages, currentPage + 2); if (currentPage <= 3) end = Math.min(totalPages, 5); if (currentPage >= totalPages - 2) start = Math.max(1, totalPages - 4); if (start > 1) pages.push('start-ellipsis'); for (let p = start; p <= end; p++) pages.push(p); if (end < totalPages) pages.push('end-ellipsis'); return pages.map((p) => ( p === 'start-ellipsis' || p === 'end-ellipsis' ? (
  • ) : (
  • ) )); })()}
)}
{/* Delete Confirmation Modal */} {showDeleteModal && (

Удалить {entityName}?

Вы уверены, что хотите удалить "{itemToDelete?.domain}"? Это действие необратимо.
)} setConfirmState({ open: false, text: '', onConfirm: null })} onConfirm={() => confirmState.onConfirm?.()} /> ); } export default DataManager;