From 2ec8a9e7f73be9ae79797706d1ad8c9e4f0e7248 Mon Sep 17 00:00:00 2001 From: d0zingcat Date: Sat, 17 Jan 2026 14:20:02 +0800 Subject: [PATCH] feat: add group name search Signed-off-by: d0zingcat --- apps/server/src/api.ts | 11 +- .../web/src/components/GroupBindingsModal.tsx | 242 +++++++++++++----- 2 files changed, 195 insertions(+), 58 deletions(-) diff --git a/apps/server/src/api.ts b/apps/server/src/api.ts index 3d1a7c4..bbc85d3 100644 --- a/apps/server/src/api.ts +++ b/apps/server/src/api.ts @@ -270,12 +270,21 @@ api.delete("/topics/:topicId/subscribe/:userId", requireAuth, async (c) => { // Get list of known groups (for selection) api.get("/groups", requireAuth, async (c) => { + const query = c.req.query("q")?.trim(); + const limit = Math.min(Number(c.req.query("limit") || 100), 200); + + let whereClause = undefined; + if (query) { + whereClause = sql`${knownGroupChats.name} ilike ${`%${query}%`}`; + } + // Return recent active groups const groups = await db .select() .from(knownGroupChats) + .where(whereClause) .orderBy(desc(knownGroupChats.lastActiveAt)) - .limit(50); + .limit(limit); return c.json(groups); }); diff --git a/apps/web/src/components/GroupBindingsModal.tsx b/apps/web/src/components/GroupBindingsModal.tsx index 4fe1c6f..18c6bf7 100644 --- a/apps/web/src/components/GroupBindingsModal.tsx +++ b/apps/web/src/components/GroupBindingsModal.tsx @@ -1,5 +1,5 @@ -import { MessageCircle, Plus, Trash2 } from "lucide-react"; -import { useCallback, useEffect, useState } from "react"; +import { MessageCircle, Plus, Search, Trash2, X } from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { client } from "../lib/client"; import Modal from "./Modal"; @@ -29,16 +29,21 @@ export default function GroupBindingsModal({ topicId, topicName, }: GroupBindingsModalProps) { - // const { user } = useAuth(); // Unused const [bindings, setBindings] = useState([]); const [knownGroups, setKnownGroups] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); const [selectedChatId, setSelectedChatId] = useState(""); const [loading, setLoading] = useState(false); + const [isSearching, setIsSearching] = useState(false); + const [showDropdown, setShowDropdown] = useState(false); const [status, setStatus] = useState<{ type: "success" | "error"; message: string; } | null>(null); + const dropdownRef = useRef(null); + const searchTimeoutRef = useRef | null>(null); + const fetchBindings = useCallback(async () => { try { const res = await client.api.topics[":id"].groups.$get( @@ -58,17 +63,25 @@ export default function GroupBindingsModal({ } }, [topicId]); - const fetchKnownGroups = useCallback(async () => { + const fetchKnownGroups = useCallback(async (q?: string) => { + setIsSearching(true); try { - const res = await client.api.groups.$get(undefined, { - init: { credentials: "include" }, - }); + const res = await client.api.groups.$get( + { + query: q ? { q } : undefined, + }, + { + init: { credentials: "include" }, + }, + ); const data = (await res.json()) as KnownGroup[]; if (Array.isArray(data)) { setKnownGroups(data); } } catch (err) { console.error(err); + } finally { + setIsSearching(false); } }, []); @@ -78,24 +91,60 @@ export default function GroupBindingsModal({ fetchKnownGroups(); setStatus(null); setSelectedChatId(""); + setSearchQuery(""); + setShowDropdown(false); } }, [isOpen, topicId, fetchBindings, fetchKnownGroups]); + // Handle click outside to close dropdown + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setShowDropdown(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const handleSearchChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setSearchQuery(value); + setSelectedChatId(""); + setShowDropdown(true); + + if (searchTimeoutRef.current) { + clearTimeout(searchTimeoutRef.current); + } + + searchTimeoutRef.current = setTimeout(() => { + fetchKnownGroups(value); + }, 300); + }; + + const handleSelectGroup = (group: KnownGroup) => { + setSelectedChatId(group.chatId); + setSearchQuery(group.name); + setShowDropdown(false); + }; + const handleBind = async () => { if (!selectedChatId) return; setLoading(true); setStatus(null); - const group = knownGroups.find((g) => g.chatId === selectedChatId); - if (!group) return; + const groupName = searchQuery; try { const res = await client.api.topics[":id"].groups.$post( { param: { id: topicId }, json: { - chatId: group.chatId, - name: group.name, + chatId: selectedChatId, + name: groupName, }, }, { @@ -114,12 +163,12 @@ export default function GroupBindingsModal({ }); fetchBindings(); setSelectedChatId(""); + setSearchQuery(""); } else { - await res.json(); // Consume body + await res.json(); setStatus({ type: "error", message: "Failed to bind group" }); } } catch (_) { - // Ignore error setStatus({ type: "error", message: "An error occurred" }); } finally { setLoading(false); @@ -147,7 +196,6 @@ export default function GroupBindingsModal({ } }; - // Filter out groups that are already bound const availableGroups = knownGroups.filter( (kg) => !bindings.some((b) => b.chatId === kg.chatId), ); @@ -160,31 +208,41 @@ export default function GroupBindingsModal({ >
-

+

+ Bound Groups

{bindings.length === 0 ? ( -

- No groups bound to this topic yet. -

+
+

+ No groups bound to this topic yet. +

+
) : ( -
    +
      {bindings.map((binding) => (
    • -
      - - - {binding.name} - +
      +
      + +
      +
      + + {binding.name} + + + {binding.chatId} + +
      {binding.status} @@ -193,10 +251,10 @@ export default function GroupBindingsModal({
    • ))} @@ -204,45 +262,115 @@ export default function GroupBindingsModal({ )}
-
-

+
+

+ Add Group Binding

-

- Select a group where the Feishu Bot has been added. If your group is - not listed, try removing and re-adding the bot to the group. +

+ Search and select a group where the Alert Messenger bot + has been added.

-
- +
+
+
+
+ {isSearching ? ( +
+ ) : ( + + )} +
+ knownGroups.length > 0 && setShowDropdown(true)} + disabled={loading} + /> + {searchQuery && ( + + )} +
+ + {showDropdown && ( +
+ {availableGroups.length > 0 ? ( + availableGroups.map((group) => ( + + )) + ) : ( +
+

No results found

+

+ Try a different search term or check if the bot is in + the group. +

+
+ )} +
+ )} +
+
+ {status && ( -

- {status.message} -

+
{status.message}
+
)}