From 3e332eceae2fb47b248ee425037bf06f5666cce9 Mon Sep 17 00:00:00 2001 From: Roushan Singh Date: Fri, 3 Oct 2025 16:20:51 +0530 Subject: [PATCH] feat: add project search and scrollable thread lists - Add search bar to filter projects by name in real-time - Implement scrollable thread container with max 4 visible threads - Add empty state for no search results - Add clear button (X) to reset search query --- web-app/src/locales/en/common.json | 5 ++- web-app/src/routes/project/index.tsx | 56 +++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/web-app/src/locales/en/common.json b/web-app/src/locales/en/common.json index 2eca7a699..690381188 100644 --- a/web-app/src/locales/en/common.json +++ b/web-app/src/locales/en/common.json @@ -284,7 +284,10 @@ "updated": "Updated:", "collapseThreads": "Collapse threads", "expandThreads": "Expand threads", - "update": "Update" + "update": "Update", + "searchProjects": "Search projects...", + "noProjectsFound": "No projects found", + "tryDifferentSearch": "Try a different search term" }, "toast": { "allThreadsUnfavorited": { diff --git a/web-app/src/routes/project/index.tsx b/web-app/src/routes/project/index.tsx index deb4cb2a6..5d3a30315 100644 --- a/web-app/src/routes/project/index.tsx +++ b/web-app/src/routes/project/index.tsx @@ -14,6 +14,8 @@ import { IconFolder, IconChevronDown, IconChevronRight, + IconSearch, + IconX, } from '@tabler/icons-react' import AddProjectDialog from '@/containers/dialogs/AddProjectDialog' import { DeleteProjectDialog } from '@/containers/dialogs/DeleteProjectDialog' @@ -42,6 +44,7 @@ function ProjectContent() { const [expandedProjects, setExpandedProjects] = useState>( new Set() ) + const [searchQuery, setSearchQuery] = useState('') const handleDelete = (id: string) => { setDeletingId(id) @@ -93,6 +96,16 @@ function ProjectContent() { }) } + // Filter projects based on search query + const filteredProjects = useMemo(() => { + if (!searchQuery.trim()) { + return folders + } + return folders.filter((folder) => + folder.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) + }, [folders, searchQuery]) + return (
@@ -113,6 +126,33 @@ function ProjectContent() {
+ {/* Search Bar */} + {folders.length > 0 && ( +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2.5 bg-main-view-fg/5 border border-main-view-fg/10 rounded-lg text-main-view-fg placeholder:text-main-view-fg/50 focus:outline-none focus:ring-2 focus:ring-main-view-fg/20 focus:border-main-view-fg/20 transition-all" + /> + {searchQuery && ( + + )} +
+
+ )} + {folders.length === 0 ? (
@@ -123,9 +163,19 @@ function ProjectContent() { {t('projects.noProjectsYetDesc')}

+ ) : filteredProjects.length === 0 ? ( +
+ +

+ {t('projects.noProjectsFound')} +

+

+ {t('projects.tryDifferentSearch')} +

+
) : (
- {folders + {filteredProjects .slice() .sort((a, b) => b.updated_at - a.updated_at) .map((folder) => { @@ -218,7 +268,9 @@ function ProjectContent() { {/* Thread List */} {isExpanded && projectThreads.length > 0 && ( -
+