From aa882bad20d4bb1445a143c9ca2296353cfacea5 Mon Sep 17 00:00:00 2001 From: shats Date: Sun, 30 Nov 2025 22:36:15 +0700 Subject: [PATCH] feat: Refactor CommunitiesManager component to enhance UI and functionality with modals for adding, editing, and deleting communities. Improved validation and error handling, and updated layout for better user experience. --- frontend/src/CommunitiesManager.jsx | 701 ++++++++++++++++++---------- 1 file changed, 453 insertions(+), 248 deletions(-) diff --git a/frontend/src/CommunitiesManager.jsx b/frontend/src/CommunitiesManager.jsx index 812eb80..943d9eb 100644 --- a/frontend/src/CommunitiesManager.jsx +++ b/frontend/src/CommunitiesManager.jsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import api from './lib/api.js'; import { IconPlus, @@ -7,25 +7,23 @@ import { IconTrash, IconCheck, IconX, - IconDatabase, - IconRefresh, - IconUpload, - IconDownload, - IconDeviceFloppy, IconHash, IconFilter, IconChartBar, IconList, - IconArrowUp, - IconArrowDown + IconSortAscending, + IconSortDescending } from '@tabler/icons-react'; import PageHeaderActions from './components/PageHeaderActions.jsx'; -import PageHeader from './components/PageHeader.jsx'; import EmptyState from './components/EmptyState.jsx'; -import { TableEmpty } from './components/TableSkeleton.jsx'; import Breadcrumbs from './components/Breadcrumbs.jsx'; import CommunityStats from './components/CommunityStats.jsx'; import SavedFilters from './components/SavedFilters.jsx'; +import FormModal from './components/FormModal.jsx'; +import ConfirmModal from './components/ConfirmModal.jsx'; +import FormField from './components/FormField.jsx'; +import ErrorAlert from './components/ErrorAlert.jsx'; +import Pagination from './components/Pagination.jsx'; const API_URL = '/api'; @@ -46,7 +44,12 @@ function CommunitiesManager() { const [newItem, setNewItem] = useState({ value: '', name: '', description: '', tags: '', gatewayDefault: '', color: '', category: '', priority: 0, enabled: true }); const [editingValue, setEditingValue] = useState(null); const [editingDraft, setEditingDraft] = useState({ value: '', name: '', description: '', tags: '', gatewayDefault: '', color: '', category: '', priority: 0, enabled: true }); - const editRef = useRef(null); + + // Модальные окна + const [addModalOpen, setAddModalOpen] = useState(false); + const [editModalOpen, setEditModalOpen] = useState(false); + const [deleteModalOpen, setDeleteModalOpen] = useState(false); + const [itemToDelete, setItemToDelete] = useState(null); useEffect(() => { fetchItems(); }, []); @@ -135,6 +138,7 @@ function CommunitiesManager() { const value = String(newItem.value || '').trim(); if (!value) { setError('Поле value обязательно.'); return; } if (items.some(i => i.value === value)) { setError('Такое value уже существует.'); return; } + if (!/^(\d+|\d+:\d+)$/.test(value)) { setError('Формат: число или N:N'); return; } setError(''); setItems([...items, { value, @@ -145,6 +149,9 @@ function CommunitiesManager() { color: String(newItem.color || ''), }]); setNewItem({ value: '', name: '', description: '', tags: '', gatewayDefault: '', color: '' }); + setAddModalOpen(false); + setSuccess('Community успешно добавлен!'); + setTimeout(() => setSuccess(''), 3000); }; const startEdit = (item) => { @@ -157,11 +164,13 @@ function CommunitiesManager() { gatewayDefault: item.gatewayDefault || '', color: item.color || '', }); + setEditModalOpen(true); }; const saveEdit = () => { const value = String(editingDraft.value || '').trim(); if (!value) { setError('Поле value обязательно.'); return; } + if (!/^(\d+|\d+:\d+)$/.test(value)) { setError('Формат: число или N:N'); return; } if (value !== editingValue && items.some(i => i.value === value)) { setError('Такое value уже существует.'); return; } const next = items.map(i => i.value === editingValue ? { value, @@ -173,11 +182,30 @@ function CommunitiesManager() { } : i); setItems(next); setEditingValue(null); + setEditModalOpen(false); + setSuccess('Community успешно обновлён!'); + setTimeout(() => setSuccess(''), 3000); }; - const cancelEdit = () => setEditingValue(null); + const cancelEdit = () => { + setEditingValue(null); + setEditModalOpen(false); + }; - const deleteItem = (value) => setItems(items.filter(i => i.value !== value)); + const handleDeleteClick = (item) => { + setItemToDelete(item); + setDeleteModalOpen(true); + }; + + const confirmDelete = () => { + if (itemToDelete) { + setItems(items.filter(i => i.value !== itemToDelete.value)); + setDeleteModalOpen(false); + setItemToDelete(null); + setSuccess('Community успешно удалён!'); + setTimeout(() => setSuccess(''), 3000); + } + }; const handleImport = () => { const text = window.prompt('Вставьте JSON-массив community (value, name, description, tags, gatewayDefault, color)'); @@ -236,122 +264,118 @@ function CommunitiesManager() { }; return ( -
- Справочник Community)} - pretitle={( - - )} - actions={( - - )} - /> - - {/* Вкладки: Список / Статистика */} -
-
-
- - +
+
+
+
+
+

+ + Справочник Community +

+
+ +
+
+
+ +
- {error && ( -
- {error} - -
- )} - {success && ( -
- {success} - -
- )} +
+
+ {/* Уведомления */} + {error && ( + setError('')} onRetry={fetchItems} /> + )} + {success && ( +
+
+
+ +
+
+ {success} +
+
+ +
+ )} - {/* Отображение списка communities */} - {activeTab === 'list' && ( -
-
-
-

Добавить

-
-
- -
- - setNewItem({...newItem, value:e.target.value})} placeholder="65001:200 или 100" /> -
- {newItem.value && !/^(\d+|\d+:\d+)$/.test(String(newItem.value).trim()) && (
Формат: число или N:N
)} - {newItem.value && /^(\d+|\d+:\d+)$/.test(String(newItem.value).trim()) && (
Корректно
)} -
-
- -
- - setNewItem({...newItem, name:e.target.value})} placeholder="Напр. Social" /> -
-
-
- -