feat: Add search and filter UI components

- Created SearchBar component with debounced input
- Built FilterPanel component with controls for priority, completion, tags, and due date
- Added sort controls (field and direction)
- Implemented filter clear functionality
- Display search results with highlighted text
- Added GlobalSearch component for home page
- Integrated search and filter into list detail view
- Added CSS styles for search highlighting

Requirements: 6.1, 6.2, 6.3, 6.4, 8.1, 8.2, 8.3
This commit is contained in:
2025-12-01 19:25:09 +00:00
parent 477e378d08
commit 0143097e69
9 changed files with 781 additions and 43 deletions

View File

@@ -1,3 +1,12 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind utilities;
/* Search highlight styles */
mark {
background-color: #fef08a;
color: inherit;
padding: 0 2px;
border-radius: 2px;
font-weight: 500;
}

View File

@@ -1,9 +1,9 @@
import { getListWithItems } from '@/lib/actions'
import { notFound } from 'next/navigation'
import Link from 'next/link'
import { ArrowLeft, Plus } from 'lucide-react'
import ItemCard from '@/components/ItemCard'
import { ArrowLeft } from 'lucide-react'
import InlineItemCreate from '@/components/InlineItemCreate'
import ListDetailView from '@/components/ListDetailView'
export default async function ListDetailPage({ params }: { params: { id: string } }) {
const list = await getListWithItems(params.id)
@@ -13,7 +13,6 @@ export default async function ListDetailPage({ params }: { params: { id: string
}
const completedItems = list.items.filter(item => item.completed)
const incompleteItems = list.items.filter(item => !item.completed)
return (
<main className="container mx-auto px-4 py-8 max-w-4xl">
@@ -45,44 +44,8 @@ export default async function ListDetailPage({ params }: { params: { id: string
<InlineItemCreate listId={list.id} />
</div>
{/* Items List */}
<div className="space-y-6">
{/* Incomplete Items */}
{incompleteItems.length > 0 && (
<div>
<h2 className="text-lg font-semibold text-gray-900 mb-3">
To Do ({incompleteItems.length})
</h2>
<div className="space-y-2">
{incompleteItems.map((item) => (
<ItemCard key={item.id} item={item} listId={list.id} />
))}
</div>
</div>
)}
{/* Completed Items */}
{completedItems.length > 0 && (
<div>
<h2 className="text-lg font-semibold text-gray-500 mb-3">
Completed ({completedItems.length})
</h2>
<div className="space-y-2">
{completedItems.map((item) => (
<ItemCard key={item.id} item={item} listId={list.id} />
))}
</div>
</div>
)}
{/* Empty State */}
{list.items.length === 0 && (
<div className="text-center py-12 bg-gray-50 rounded-lg">
<p className="text-gray-500 text-lg mb-2">No tasks yet</p>
<p className="text-gray-400">Add your first task to get started</p>
</div>
)}
</div>
{/* Search, Filter, and Items List */}
<ListDetailView listId={list.id} initialItems={list.items} />
</main>
)
}

View File

@@ -1,6 +1,7 @@
import { getLists } from '@/lib/actions'
import ListCard from '@/components/ListCard'
import NewListButton from '@/components/NewListButton'
import GlobalSearch from '@/components/GlobalSearch'
export default async function Home() {
const lists = await getLists()
@@ -12,6 +13,11 @@ export default async function Home() {
<NewListButton />
</div>
{/* Global Search */}
<div className="mb-8">
<GlobalSearch />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{lists.map((list) => (
<ListCard key={list.id} list={list} />