diff --git a/frontend/src/CommunitiesManager.jsx b/frontend/src/CommunitiesManager.jsx index 4c937d3..1c05a97 100644 --- a/frontend/src/CommunitiesManager.jsx +++ b/frontend/src/CommunitiesManager.jsx @@ -11,6 +11,8 @@ import { IconFilter, IconChartBar, IconList, + IconLink, + IconRefresh, IconSortAscending, IconSortDescending } from '@tabler/icons-react'; @@ -44,6 +46,10 @@ 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 [filterUsage, setFilterUsage] = useState({}); + const [filterUsageLoading, setFilterUsageLoading] = useState(false); + const [filterUsageError, setFilterUsageError] = useState(''); + const [showOnlyUsedInFilters, setShowOnlyUsedInFilters] = useState(false); // Модальные окна const [addModalOpen, setAddModalOpen] = useState(false); @@ -51,7 +57,24 @@ function CommunitiesManager() { const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [itemToDelete, setItemToDelete] = useState(null); - useEffect(() => { fetchItems(); }, []); + useEffect(() => { + fetchItems(); + fetchFilterUsage(); + }, []); + + useEffect(() => { + if (!filterUsage) return; + setItems((prev) => { + const map = new Map(prev.map((i) => [i.value, i])); + Object.keys(filterUsage).forEach((value) => { + if (!value) return; + if (!map.has(value)) { + map.set(value, { value, name: '', description: '', tags: [], gatewayDefault: '', color: '', _external: true }); + } + }); + return Array.from(map.values()); + }); + }, [filterUsage]); const fetchItems = async () => { setLoading(true); @@ -115,6 +138,42 @@ function CommunitiesManager() { } }; + const fetchFilterUsage = async () => { + setFilterUsageLoading(true); + setFilterUsageError(''); + try { + const serversRes = await api.get(`/server-configs`); + const serversList = Array.isArray(serversRes.data) ? serversRes.data : []; + if (!serversList.length) { + setFilterUsage({}); + return; + } + + const responses = await Promise.all( + serversList.map((server) => + api.get(`/server-filters/${server.id}`).catch(() => ({ data: [] })) + ) + ); + + const usageMap = new Map(); + responses.forEach((res) => { + const arr = Array.isArray(res.data) ? res.data : []; + arr.forEach((f) => { + const value = String(f?.community || '').trim(); + if (!value) return; + usageMap.set(value, (usageMap.get(value) || 0) + 1); + }); + }); + + setFilterUsage(Object.fromEntries(usageMap)); + } catch (e) { + console.error('Error fetching filter usage:', e); + setFilterUsageError('Не удалось загрузить использование в фильтрах.'); + } finally { + setFilterUsageLoading(false); + } + }; + const saveAll = async (data) => { setLoading(true); try { @@ -245,14 +304,16 @@ function CommunitiesManager() { const filtered = useMemo(() => { const q = searchTerm.trim().toLowerCase(); - if (!q) return sorted; - return sorted.filter(i => - (i.value || '').toLowerCase().includes(q) || - (i.name || '').toLowerCase().includes(q) || - (i.description || '').toLowerCase().includes(q) || - (i.tags || []).some(t => (t || '').toLowerCase().includes(q)) - ); - }, [sorted, searchTerm]); + const searchFiltered = q + ? sorted.filter(i => + (i.value || '').toLowerCase().includes(q) || + (i.name || '').toLowerCase().includes(q) || + (i.description || '').toLowerCase().includes(q) || + (i.tags || []).some(t => (t || '').toLowerCase().includes(q)) + ) + : sorted; + return searchFiltered.filter((i) => !showOnlyUsedInFilters || (filterUsage[i.value] || 0) > 0); + }, [sorted, searchTerm, showOnlyUsedInFilters, filterUsage]); const totalPages = Math.ceil(filtered.length / pageSize) || 1; const page = Math.min(currentPage, totalPages); @@ -375,7 +436,7 @@ function CommunitiesManager() {
-
+
)}
+
+ + + {filterUsageError && ( + {filterUsageError} + )} +
@@ -437,14 +525,15 @@ function CommunitiesManager() { + {loading ? ( - + ) : paginated.length === 0 ? ( - - ) : paginated.map((item) => ( - - - - - - - - - ))} + ) : paginated.map((item) => { + const usageCount = filterUsage[item.value] || 0; + return ( + + + + + + + + + + ); + })}
Описание Теги GatewayИсп. в фильтрах Действия
Загрузка...
Загрузка...
+
- {item.value} - {item._external && ( - не в справ. - )} - {item.name || } - {item.description || '—'} - - {(item.tags || []).map(t => ( - {t} - ))} - - {item.gatewayDefault || '—'} - -
- - -
-
+ {item.value} + {item._external && ( + не в справ. + )} + {item.name || } + {item.description || '—'} + + {(item.tags || []).map(t => ( + {t} + ))} + + {item.gatewayDefault || '—'} + + {usageCount > 0 ? ( + {usageCount} в фильтрах + ) : ( + 0 + )} + +
+ + + + + +
+
diff --git a/frontend/src/FilterManager.jsx b/frontend/src/FilterManager.jsx index fd9e955..d5f0c5c 100644 --- a/frontend/src/FilterManager.jsx +++ b/frontend/src/FilterManager.jsx @@ -69,6 +69,15 @@ function FilterManager() { const [filterSearch, setFilterSearch] = useState(''); const [sortBy, setSortBy] = useState('community'); // community | gateway | description const [sortDir, setSortDir] = useState('asc'); // asc | desc + + // Префилл поиска из query-параметра ?community=... + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const communityFromQuery = params.get('community'); + if (communityFromQuery) { + setFilterSearch(communityFromQuery); + } + }, []); // Состояние управления серверами const [serverListModalOpen, setServerListModalOpen] = useState(false);