From 260985573cd598b7963382617898ab192fff4d71 Mon Sep 17 00:00:00 2001 From: shats Date: Sun, 7 Dec 2025 19:46:09 +0700 Subject: [PATCH] feat: Group communities by tags in EasySwitchManager for improved organization and visibility, enhancing user experience with clear categorization and display of communities without tags. --- frontend/src/EasySwitchManager.jsx | 202 +++++++++++++++++++++++++++-- 1 file changed, 192 insertions(+), 10 deletions(-) diff --git a/frontend/src/EasySwitchManager.jsx b/frontend/src/EasySwitchManager.jsx index 71a7574..6f4e3a1 100644 --- a/frontend/src/EasySwitchManager.jsx +++ b/frontend/src/EasySwitchManager.jsx @@ -622,15 +622,57 @@ function EasySwitchManager() { {/* Контент (сворачиваемый) */} {isExpanded && (
- {/* Сетка Communities для этого сервера */} -
- {filteredCommunitiesForServer.map((comm) => { - const activeKey = `${server.id}:${comm.value}`; - const activeGw = activeGateways[activeKey]; - const hasFilterForComm = server.filtersMap.has(comm.value); + {/* Группируем communities по тегам */} + {(() => { + // Группируем по тегам + const groupedByTags = new Map(); + const noTagCommunities = []; + + filteredCommunitiesForServer.forEach(comm => { + const tags = Array.isArray(comm.tags) ? comm.tags : + (comm.tags ? String(comm.tags).split(',').map(t => t.trim()).filter(Boolean) : []); - return ( -
+ if (tags.length === 0) { + noTagCommunities.push(comm); + } else { + tags.forEach(tag => { + if (!groupedByTags.has(tag)) { + groupedByTags.set(tag, []); + } + groupedByTags.get(tag).push(comm); + }); + } + }); + + // Сортируем теги + const sortedTags = Array.from(groupedByTags.keys()).sort(); + + return ( + <> + {/* Communities с тегами */} + {sortedTags.map(tag => { + const tagCommunities = groupedByTags.get(tag); + return ( +
+ {/* Заголовок группы тегов */} +
+ + {tag} + + + {tagCommunities.length} {tagCommunities.length === 1 ? 'community' : 'communities'} + +
+ + {/* Сетка Communities для этого тега */} +
+ {tagCommunities.map((comm) => { + const activeKey = `${server.id}:${comm.value}`; + const activeGw = activeGateways[activeKey]; + const hasFilterForComm = server.filtersMap.has(comm.value); + + return ( +
{/* Заголовок community */}
@@ -738,8 +780,148 @@ function EasySwitchManager() {
); - })} -
+ })} +
+
+ ); + })} + + {/* Communities без тегов */} + {noTagCommunities.length > 0 && ( +
+ {/* Заголовок группы без тегов */} +
+ + Без тегов + + + {noTagCommunities.length} {noTagCommunities.length === 1 ? 'community' : 'communities'} + +
+ + {/* Сетка Communities без тегов */} +
+ {noTagCommunities.map((comm) => { + const activeKey = `${server.id}:${comm.value}`; + const activeGw = activeGateways[activeKey]; + const hasFilterForComm = server.filtersMap.has(comm.value); + + return ( +
+
+ {/* Заголовок community */} +
+ + {comm.value} + + {comm.name || comm.description || ''} + {hasFilterForComm && ( + + настроен + + )} + + ({server.gateways.length}) + +
+ + {/* Сетка карточек шлюзов для этого community */} +
+ {server.gateways.map((gw, idx) => { + const isActive = activeGw === gw.name; + const isFastest = idx === 0; + const ping = 20 + idx * 10 + Math.floor(Math.random() * 15); + + return ( +
+
handleGatewaySelect(server.id, comm.value, gw.name)} + onMouseEnter={(e) => { + if (!isActive) { + e.currentTarget.style.boxShadow = '0 2px 6px rgba(0,0,0,0.1)'; + e.currentTarget.style.transform = 'translateY(-1px)'; + } + }} + onMouseLeave={(e) => { + if (!isActive) { + e.currentTarget.style.boxShadow = 'none'; + e.currentTarget.style.transform = 'translateY(0)'; + } + }} + > +
+ {/* Галочка (абсолютное позиционирование) */} + {isActive && ( +
+ +
+ )} + + {/* Метка Fastest */} +
+ {isFastest && !isActive && ( + + + Fast + + )} +
+ + {/* Название шлюза */} +
+ {gw.name} +
+ + {/* Описание шлюза */} + {gw.comment && ( +
+ {gw.comment} +
+ )} + + {/* Протокол */} +
+ {serverMeta?.provider || 'vless'} +
+ + {/* Метрика (пинг) */} +
+ {ping} +
+
+
+
+ ); + })} +
+
+
+ ); + })} +
+
+ )} + + ); + })()}
)}