11'use client'
22
3- import SearchPosts from '@/components/SearchPosts'
43import { prefix } from '@/utils/prefix'
5- import Image from 'next/image'
64import Link from 'next/link'
75import { useEffect , useState } from 'react'
6+ import { Search , Filter , X } from 'iconoir-react'
7+ import Heading from '@/components/heading'
8+
9+ const SearchBar = ( {
10+ searchQuery,
11+ setSearchQuery,
12+ onFilterChange,
13+ yearFilter,
14+ availableYears,
15+ } : {
16+ searchQuery : string
17+ setSearchQuery : ( query : string ) => void
18+ onFilterChange : ( year : string ) => void
19+ yearFilter : string
20+ availableYears : string [ ]
21+ } ) => {
22+ const [ showFilters , setShowFilters ] = useState ( false )
823
9- const MinuteFile = ( { slug, intro } : { slug : string ; intro : string } ) => {
1024 return (
11- < Link href = { `${ prefix } /minutes/${ slug } ` } >
12- < div className = "border border-border p-4 rounded-sm bg-foreground" >
13- < h2 className = "text-xl font-space-mono" > { slug } </ h2 >
14- < p
15- style = { {
16- display : '-webkit-box' ,
17- WebkitLineClamp : 5 ,
18- WebkitBoxOrient : 'vertical' ,
19- overflow : 'hidden' ,
20- position : 'relative' ,
21- } }
22- >
23- < span className = "opacity-70" > { intro } </ span >
24- < span
25- style = { {
26- position : 'absolute' ,
27- bottom : 0 ,
28- left : 0 ,
29- right : 0 ,
30- height : '10em' ,
31- background :
32- 'linear-gradient(to bottom, rgba(255,255,255,0) 0%, #353535 100%)' ,
33- } }
25+ < div className = "mb-8" >
26+ < div className = "relative flex flex-col sm:flex-row gap-4 items-center" >
27+ < div className = "relative flex-1 max-w-md" >
28+ < Search className = "absolute left-3 top-1/2 transform -translate-y-1/2 text-zinc-400 w-5 h-5" />
29+ < input
30+ type = "text"
31+ placeholder = "Search minutes, topics, or dates..."
32+ className = "w-full pl-10 pr-4 py-3 bg-foreground border border-border focus:outline-none focus:ring-2 focus:ring-csred/50 focus:border-csred transition-all duration-200 font-space-mono text-sm"
33+ value = { searchQuery }
34+ onChange = { ( e ) => setSearchQuery ( e . target . value ) }
3435 />
35- </ p >
36+ { searchQuery && (
37+ < button
38+ onClick = { ( ) => setSearchQuery ( '' ) }
39+ className = "absolute right-3 top-1/2 transform -translate-y-1/2 text-zinc-400 hover:text-white transition-colors"
40+ >
41+ < X className = "w-4 h-4" />
42+ </ button >
43+ ) }
44+ </ div >
45+
46+ < button
47+ onClick = { ( ) => setShowFilters ( ! showFilters ) }
48+ className = "flex items-center gap-2 px-4 py-3 bg-foreground border border-border hover:bg-border/50 transition-colors font-space-mono text-sm"
49+ >
50+ < Filter className = "w-4 h-4" />
51+ Filters
52+ { yearFilter && (
53+ < span className = "bg-csred text-white px-2 py-1 rounded-full text-xs" >
54+ { yearFilter }
55+ </ span >
56+ ) }
57+ </ button >
3658 </ div >
37- </ Link >
59+
60+ { showFilters && (
61+ < div className = "mt-4 p-4 bg-foreground border border-border " >
62+ < div className = "flex flex-wrap gap-2" >
63+ < button
64+ onClick = { ( ) => onFilterChange ( '' ) }
65+ className = { `px-3 py-2 text-sm font-space-mono transition-colors ${
66+ ! yearFilter
67+ ? 'bg-csred text-white'
68+ : 'bg-border text-zinc-300 hover:bg-border/70'
69+ } `}
70+ >
71+ All Years
72+ </ button >
73+ { availableYears . map ( ( year ) => (
74+ < button
75+ key = { year }
76+ onClick = { ( ) => onFilterChange ( year ) }
77+ className = { `px-3 py-2 text-sm font-space-mono transition-colors ${
78+ yearFilter === year
79+ ? 'bg-csred text-white'
80+ : 'bg-border text-zinc-300 hover:bg-border/70'
81+ } `}
82+ >
83+ { year }
84+ </ button >
85+ ) ) }
86+ </ div >
87+ </ div >
88+ ) }
89+ </ div >
90+ )
91+ }
92+
93+ const ListView = ( {
94+ filteredPosts,
95+ initialPostsToShow,
96+ setInitialPostsToShow,
97+ } : {
98+ filteredPosts : {
99+ slug : string
100+ content : string
101+ date : string
102+ title : string
103+ } [ ]
104+ initialPostsToShow : number
105+ setInitialPostsToShow : ( count : number ) => void
106+ } ) => {
107+ return (
108+ < div >
109+ { filteredPosts . slice ( 0 , initialPostsToShow ) . map ( ( post , index ) => (
110+ < Link key = { post . slug } href = { `${ prefix } /minutes/${ post . slug } ` } >
111+ < div className = "group mt-4 border border-border p-4 bg-foreground hover:bg-border/30 transition-all duration-200 hover:scale-[1.01]" >
112+ < div className = "flex items-center justify-between" >
113+ < div className = "flex-1" >
114+ < h3 className = "text-lg font-tomorrow text-white group-hover:text-csred transition-colors" >
115+ { new Date ( post . date ) . toLocaleDateString ( 'en-GB' , {
116+ day : 'numeric' ,
117+ month : 'short' ,
118+ year : 'numeric' ,
119+ } ) }
120+ </ h3 >
121+ < span className = "text-sm text-zinc-400 font-space-mono" >
122+ { new Date ( post . date ) . toLocaleDateString ( 'en-GB' , {
123+ weekday : 'long' ,
124+ } ) }
125+ </ span >
126+ </ div >
127+ </ div >
128+ < p
129+ className = "text-sm opacity-70 leading-relaxed mt-2"
130+ style = { {
131+ display : '-webkit-box' ,
132+ WebkitLineClamp : 2 ,
133+ WebkitBoxOrient : 'vertical' ,
134+ overflow : 'hidden' ,
135+ } }
136+ >
137+ { post . content . slice ( 0 , 180 ) } ...
138+ </ p >
139+ </ div >
140+ </ Link >
141+ ) ) }
142+
143+ { initialPostsToShow < filteredPosts . length && (
144+ < div className = "text-center mt-6" >
145+ < button
146+ onClick = { ( ) => setInitialPostsToShow ( Infinity ) }
147+ className = "bg-csred hover:bg-csred/80 text-white font-space-mono px-6 py-2 transition-all duration-200 hover:scale-105"
148+ >
149+ Show all { filteredPosts . length - initialPostsToShow } more minutes
150+ </ button >
151+ </ div >
152+ ) }
153+ </ div >
38154 )
39155}
40156
@@ -44,64 +160,114 @@ const MinutesList = ({
44160 posts : {
45161 slug : string
46162 content : string
163+ date : string
164+ title : string
47165 } [ ]
48166} ) => {
49167 const [ searchQuery , setSearchQuery ] = useState ( '' )
50168 const [ filteredPosts , setFilteredPosts ] = useState ( posts )
51- const [ initialPostsToShow , setInitialPostsToShow ] = useState ( 16 )
169+ const [ initialPostsToShow , setInitialPostsToShow ] = useState ( 20 )
170+ const [ yearFilter , setYearFilter ] = useState ( '' )
171+
172+ const availableYears = Array . from (
173+ new Set ( posts . map ( ( post ) => post . date . split ( '-' ) [ 0 ] ) )
174+ )
175+ . sort ( )
176+ . reverse ( )
52177
53178 useEffect ( ( ) => {
54- setFilteredPosts (
55- posts . filter ( ( post ) =>
56- post . content . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) )
179+ let filtered = posts
180+
181+ if ( searchQuery ) {
182+ filtered = filtered . filter (
183+ ( post ) =>
184+ post . content . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ||
185+ post . slug . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) ) ||
186+ post . date . includes ( searchQuery )
57187 )
58- )
59- } , [ searchQuery ] )
188+ }
189+
190+ if ( yearFilter ) {
191+ filtered = filtered . filter ( ( post ) => post . date . startsWith ( yearFilter ) )
192+ }
193+
194+ setFilteredPosts ( filtered )
195+ } , [ searchQuery , yearFilter , posts ] )
60196
61197 return (
62- < >
63- < div className = "max-w-5xl mx-auto" >
64- < div className = "mb-10 mt-4" >
65- < h1 className = "font-tomorrow text-3xl" > Minutes</ h1 >
66- </ div >
67- < p className = "text-md mb-10" >
68- Welcome to the CompSoc Meeting Minutes Archive. Here, you will find
69- detailed records of all committee meetings dating back to 2016.
70- Explore these documents to gain insights into the decisions,
71- discussions, and developments that have shaped our society.
72- </ p >
73-
74- < SearchPosts setSearchQuery = { setSearchQuery } />
75- < div className = "grid grid-cols-1 sm:grid-cols-2 gap-4 lg:grid-cols-4" >
76- { filteredPosts . slice ( 0 , initialPostsToShow ) . map ( ( post ) => (
77- < MinuteFile key = { post . slug } slug = { post . slug } intro = { post . content } />
78- ) ) }
198+ < div className = "max-w-5xl mx-auto" >
199+ < div className = "mb-10 mt-4" >
200+ < Heading title = "Meeting Minutes" />
201+ </ div >
202+ < p className = "mt-6 font-space-mono mb-10" >
203+ Explore our comprehensive archive of committee meetings dating back to
204+ 2016. Discover the decisions, discussions, and developments that shaped
205+ CompSoc.
206+ </ p >
207+
208+ < SearchBar
209+ searchQuery = { searchQuery }
210+ setSearchQuery = { setSearchQuery }
211+ onFilterChange = { setYearFilter }
212+ yearFilter = { yearFilter }
213+ availableYears = { availableYears }
214+ />
215+
216+ { searchQuery || yearFilter ? (
217+ < div className = "mt-8" >
218+ < div className = "flex items-center justify-between mb-6" >
219+ < h3 className = "text-xl font-tomorrow" >
220+ { searchQuery ? 'Search Results' : `${ yearFilter } Minutes` }
221+ < span className = "ml-2 text-sm font-space-mono text-zinc-400" >
222+ ({ filteredPosts . length } found)
223+ </ span >
224+ </ h3 >
225+ { ( searchQuery || yearFilter ) && (
226+ < button
227+ onClick = { ( ) => {
228+ setSearchQuery ( '' )
229+ setYearFilter ( '' )
230+ } }
231+ className = "text-sm text-csred hover:text-csred/80 font-space-mono"
232+ >
233+ Clear filters
234+ </ button >
235+ ) }
236+ </ div >
237+ < ListView
238+ filteredPosts = { filteredPosts }
239+ initialPostsToShow = { initialPostsToShow }
240+ setInitialPostsToShow = { setInitialPostsToShow }
241+ />
79242 </ div >
80- { initialPostsToShow < filteredPosts . length && (
243+ ) : (
244+ < ListView
245+ filteredPosts = { filteredPosts }
246+ initialPostsToShow = { initialPostsToShow }
247+ setInitialPostsToShow = { setInitialPostsToShow }
248+ />
249+ ) }
250+
251+ { filteredPosts . length === 0 && ( searchQuery || yearFilter ) && (
252+ < div className = "flex flex-col items-center justify-center py-16" >
253+ < div className = "text-6xl mb-4" > 🔍</ div >
254+ < h3 className = "text-xl font-tomorrow mb-2" > No minutes found</ h3 >
255+ < p className = "text-zinc-400 text-center max-w-md" >
256+ Try adjusting your search terms or filters to find what you're
257+ looking for.
258+ </ p >
81259 < button
82- onClick = { ( ) => setInitialPostsToShow ( Infinity ) }
83- className = "text-primary font-space-mono bg-foreground px-3 py-2 rounded-sm m-auto block border border-border my-4"
260+ onClick = { ( ) => {
261+ setSearchQuery ( '' )
262+ setYearFilter ( '' )
263+ } }
264+ className = "mt-4 px-6 py-2 bg-csred text-white hover:bg-csred/80 transition-colors font-space-mono"
84265 >
85- Show all
266+ Clear all filters
86267 </ button >
87- ) }
88- { filteredPosts . length === 0 && (
89- < div className = "flex flex-col items-center justify-center space-y-4 mt-8 h-32" >
90- < Image
91- src = { `${ prefix } /sad-mug.png` }
92- alt = "No results"
93- width = { 150 }
94- height = { 150 }
95- className = "m-auto my-0"
96- />
97-
98- < p className = "text-center font-space-mono text-lg mt-4" >
99- Nothing to see here { ':(' }
100- </ p >
101- </ div >
102- ) }
103- </ div >
104- </ >
268+ </ div >
269+ ) }
270+ </ div >
105271 )
106272}
107273
0 commit comments