1+ 'use client'
2+
13import { useState , useEffect } from 'react'
24import ArticleGrid from '@/components/ArticleGrid'
3- import ArticleSearchBox from '@/components/ArticleSearchBox '
5+ import { ArticleStatus } from '@prisma/client '
46import { ArticleWithRelations } from '@/lib/agents/researcher/researcher'
57import { searchArticles } from '@/app/researcher/researcherActions'
8+ import {
9+ Select ,
10+ SelectContent ,
11+ SelectItem ,
12+ SelectTrigger ,
13+ SelectValue ,
14+ } from "@/components/ui/select"
15+ import { Button } from '@/components/ui/button'
16+ import { Search , SortAsc , Loader2 , ChevronLeft , ChevronRight } from "lucide-react"
17+ import { Input } from "@/components/ui/input"
18+ import { Badge } from "@/components/ui/badge"
19+ import { cn } from "@/lib/utils"
20+
21+ type SortOption = {
22+ label : string
23+ field : 'createdAt' | 'updatedAt' | 'publishedAt' | 'title'
24+ order : 'asc' | 'desc'
25+ }
626
27+ const sortOptions : SortOption [ ] = [
28+ { label : 'Newest First' , field : 'createdAt' , order : 'desc' } ,
29+ { label : 'Oldest First' , field : 'publishedAt' , order : 'asc' } ,
30+ { label : 'Recently Updated' , field : 'updatedAt' , order : 'desc' } ,
31+ { label : 'Title A-Z' , field : 'title' , order : 'asc' } ,
32+ { label : 'Title Z-A' , field : 'title' , order : 'desc' } ,
33+ ]
734
835type ArticleSearchAndGridProps = {
936 categorySlug ?: string
1037 tagSlug ?: string
38+ authorUsername ?: string
39+ status ?: ArticleStatus
1140}
1241
13- export default function ArticleSearchAndGrid ( { categorySlug = '' , tagSlug = '' } : ArticleSearchAndGridProps ) {
42+ export default function ArticleSearchAndGrid ( {
43+ categorySlug = '' ,
44+ tagSlug = '' ,
45+ authorUsername = '' ,
46+ status = ArticleStatus . PUBLISH
47+ } : ArticleSearchAndGridProps ) {
1448 const [ articles , setArticles ] = useState < ArticleWithRelations [ ] > ( [ ] )
1549 const [ isLoading , setIsLoading ] = useState ( true )
1650 const [ searchQuery , setSearchQuery ] = useState ( '' )
51+ const [ selectedSort , setSelectedSort ] = useState < SortOption > ( sortOptions [ 0 ] )
52+ const [ currentPage , setCurrentPage ] = useState ( 1 )
53+ const [ totalPages , setTotalPages ] = useState ( 1 )
54+ const pageSize = 12
1755
1856 useEffect ( ( ) => {
1957 const fetchArticles = async ( ) => {
20- setIsLoading ( true )
21- try {
22- const results = await searchArticles ( searchQuery , categorySlug , tagSlug )
23- setArticles ( results as ArticleWithRelations [ ] )
24- } catch ( error ) {
25- console . error ( 'Error fetching articles:' , error )
26- } finally {
27- setIsLoading ( false )
28- }
58+ setIsLoading ( true )
59+ try {
60+ const results = await searchArticles (
61+ searchQuery ,
62+ categorySlug ,
63+ tagSlug ,
64+ authorUsername ,
65+ status ,
66+ selectedSort . field ,
67+ selectedSort . order ,
68+ currentPage ,
69+ pageSize
70+ )
71+ setArticles ( results . articles as ArticleWithRelations [ ] )
72+ setTotalPages ( results . totalPages )
73+ } catch ( error ) {
74+ console . error ( 'Error fetching articles:' , error )
75+ } finally {
76+ setIsLoading ( false )
77+ }
2978 }
3079 fetchArticles ( )
31- } , [ categorySlug , tagSlug , searchQuery ] )
80+ } , [ categorySlug , tagSlug , authorUsername , searchQuery , status , selectedSort , currentPage ] )
3281
3382 const handleSearch = ( query : string ) => {
3483 setSearchQuery ( query )
84+ setCurrentPage ( 1 ) // Reset to first page on new search
85+ }
86+
87+ const handleSortChange = ( value : string ) => {
88+ const option = sortOptions . find ( opt => `${ opt . field } -${ opt . order } ` === value )
89+ if ( option ) {
90+ setSelectedSort ( option )
91+ setCurrentPage ( 1 ) // Reset to first page on sort change
92+ }
3593 }
3694
3795 return (
38- < >
39- < ArticleSearchBox
40- searchQuery = { searchQuery }
41- setSearchQuery = { handleSearch }
42- isLoading = { isLoading }
43- />
96+ < div className = "space-y-6" >
97+ < div className = "rounded-lg border bg-card shadow-sm" >
98+ < div className = "flex flex-col sm:flex-row gap-4 items-center p-4" >
99+ < div className = "relative flex-1" >
100+ < Search className = "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
101+ < Input
102+ type = "search"
103+ placeholder = "Search articles..."
104+ value = { searchQuery }
105+ onChange = { ( e ) => handleSearch ( e . target . value ) }
106+ className = { cn (
107+ "w-full pl-9 pr-4" ,
108+ isLoading && "pr-12"
109+ ) }
110+ />
111+ { isLoading && (
112+ < div className = "absolute right-3 top-1/2 -translate-y-1/2" >
113+ < Loader2 className = "h-4 w-4 animate-spin text-muted-foreground" />
114+ </ div >
115+ ) }
116+ </ div >
117+
118+ < div className = "flex items-center gap-2" >
119+ < SortAsc className = "h-4 w-4 text-muted-foreground" />
120+ < Select
121+ value = { `${ selectedSort . field } -${ selectedSort . order } ` }
122+ onValueChange = { handleSortChange }
123+ >
124+ < SelectTrigger className = "w-[180px] bg-background" >
125+ < SelectValue placeholder = "Sort by..." />
126+ </ SelectTrigger >
127+ < SelectContent >
128+ { sortOptions . map ( ( option ) => (
129+ < SelectItem
130+ key = { `${ option . field } -${ option . order } ` }
131+ value = { `${ option . field } -${ option . order } ` }
132+ >
133+ { option . label }
134+ </ SelectItem >
135+ ) ) }
136+ </ SelectContent >
137+ </ Select >
138+ </ div >
139+ </ div >
140+
141+ { ( categorySlug || tagSlug || authorUsername ) && (
142+ < div className = "px-4 py-2 border-t flex gap-2 flex-wrap" >
143+ { categorySlug && (
144+ < Badge variant = "secondary" >
145+ Category: { categorySlug }
146+ </ Badge >
147+ ) }
148+ { tagSlug && (
149+ < Badge variant = "secondary" >
150+ Tag: { tagSlug }
151+ </ Badge >
152+ ) }
153+ { authorUsername && (
154+ < Badge variant = "secondary" >
155+ Author: { authorUsername }
156+ </ Badge >
157+ ) }
158+ </ div >
159+ ) }
160+ </ div >
161+
44162 < ArticleGrid articles = { articles } />
163+
45164 { articles . length === 0 && ! isLoading && (
46- < p className = "text-center text-muted-foreground mt-6" > No articles found.</ p >
165+ < div className = "text-center py-12" >
166+ < p className = "text-muted-foreground" > No articles found.</ p >
167+ < p className = "text-sm text-muted-foreground mt-1" >
168+ Try adjusting your search or filters
169+ </ p >
170+ </ div >
171+ ) }
172+
173+ { totalPages > 1 && (
174+ < div className = "flex justify-center gap-2 mt-6" >
175+ < Button
176+ variant = "outline"
177+ onClick = { ( ) => setCurrentPage ( p => Math . max ( 1 , p - 1 ) ) }
178+ disabled = { currentPage === 1 }
179+ className = "gap-2"
180+ >
181+ < ChevronLeft className = "h-4 w-4" />
182+ Previous
183+ </ Button >
184+ < div className = "flex items-center px-4 rounded-md border bg-background" >
185+ < span className = "text-sm" >
186+ Page { currentPage } of { totalPages }
187+ </ span >
188+ </ div >
189+ < Button
190+ variant = "outline"
191+ onClick = { ( ) => setCurrentPage ( p => Math . min ( totalPages , p + 1 ) ) }
192+ disabled = { currentPage === totalPages }
193+ className = "gap-2"
194+ >
195+ Next
196+ < ChevronRight className = "h-4 w-4" />
197+ </ Button >
198+ </ div >
47199 ) }
48- </ >
200+ </ div >
49201 )
50202}
0 commit comments