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
This commit is contained in:
parent
73b241c16f
commit
3e332eceae
@ -284,7 +284,10 @@
|
|||||||
"updated": "Updated:",
|
"updated": "Updated:",
|
||||||
"collapseThreads": "Collapse threads",
|
"collapseThreads": "Collapse threads",
|
||||||
"expandThreads": "Expand threads",
|
"expandThreads": "Expand threads",
|
||||||
"update": "Update"
|
"update": "Update",
|
||||||
|
"searchProjects": "Search projects...",
|
||||||
|
"noProjectsFound": "No projects found",
|
||||||
|
"tryDifferentSearch": "Try a different search term"
|
||||||
},
|
},
|
||||||
"toast": {
|
"toast": {
|
||||||
"allThreadsUnfavorited": {
|
"allThreadsUnfavorited": {
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import {
|
|||||||
IconFolder,
|
IconFolder,
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
|
IconSearch,
|
||||||
|
IconX,
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import AddProjectDialog from '@/containers/dialogs/AddProjectDialog'
|
import AddProjectDialog from '@/containers/dialogs/AddProjectDialog'
|
||||||
import { DeleteProjectDialog } from '@/containers/dialogs/DeleteProjectDialog'
|
import { DeleteProjectDialog } from '@/containers/dialogs/DeleteProjectDialog'
|
||||||
@ -42,6 +44,7 @@ function ProjectContent() {
|
|||||||
const [expandedProjects, setExpandedProjects] = useState<Set<string>>(
|
const [expandedProjects, setExpandedProjects] = useState<Set<string>>(
|
||||||
new Set()
|
new Set()
|
||||||
)
|
)
|
||||||
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
setDeletingId(id)
|
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 (
|
return (
|
||||||
<div className="flex h-full flex-col justify-center">
|
<div className="flex h-full flex-col justify-center">
|
||||||
<HeaderPage>
|
<HeaderPage>
|
||||||
@ -113,6 +126,33 @@ function ProjectContent() {
|
|||||||
</HeaderPage>
|
</HeaderPage>
|
||||||
<div className="h-full overflow-y-auto flex flex-col">
|
<div className="h-full overflow-y-auto flex flex-col">
|
||||||
<div className="p-4 w-full md:w-3/4 mx-auto mt-2">
|
<div className="p-4 w-full md:w-3/4 mx-auto mt-2">
|
||||||
|
{/* Search Bar */}
|
||||||
|
{folders.length > 0 && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="relative">
|
||||||
|
<IconSearch
|
||||||
|
size={18}
|
||||||
|
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-main-view-fg/50"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t('projects.searchProjects')}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => 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 && (
|
||||||
|
<button
|
||||||
|
onClick={() => setSearchQuery('')}
|
||||||
|
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-main-view-fg/50 hover:text-main-view-fg transition-colors"
|
||||||
|
>
|
||||||
|
<IconX size={18} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{folders.length === 0 ? (
|
{folders.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
<IconFolder size={48} className="text-main-view-fg/30 mb-4" />
|
<IconFolder size={48} className="text-main-view-fg/30 mb-4" />
|
||||||
@ -123,9 +163,19 @@ function ProjectContent() {
|
|||||||
{t('projects.noProjectsYetDesc')}
|
{t('projects.noProjectsYetDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
) : filteredProjects.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||||
|
<IconSearch size={48} className="text-main-view-fg/30 mb-4" />
|
||||||
|
<h3 className="text-lg font-medium text-main-view-fg/60 mb-2">
|
||||||
|
{t('projects.noProjectsFound')}
|
||||||
|
</h3>
|
||||||
|
<p className="text-main-view-fg/50 text-sm">
|
||||||
|
{t('projects.tryDifferentSearch')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{folders
|
{filteredProjects
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => b.updated_at - a.updated_at)
|
.sort((a, b) => b.updated_at - a.updated_at)
|
||||||
.map((folder) => {
|
.map((folder) => {
|
||||||
@ -218,7 +268,9 @@ function ProjectContent() {
|
|||||||
|
|
||||||
{/* Thread List */}
|
{/* Thread List */}
|
||||||
{isExpanded && projectThreads.length > 0 && (
|
{isExpanded && projectThreads.length > 0 && (
|
||||||
<div className="mt-3 pl-2">
|
<div
|
||||||
|
className="mt-3 pl-2 pr-2 max-h-[190px] overflow-y-auto overflow-x-hidden [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-main-view-fg/20 [&::-webkit-scrollbar-thumb]:rounded-full hover:[&::-webkit-scrollbar-thumb]:bg-main-view-fg/30"
|
||||||
|
>
|
||||||
<ThreadList
|
<ThreadList
|
||||||
threads={projectThreads}
|
threads={projectThreads}
|
||||||
variant="project"
|
variant="project"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user