11"use client"
22
3+ import React , { useState , useMemo } from "react"
34import Header from "@/components/header"
45import Footer from "@/components/footer"
56import { Card , CardContent , CardHeader , CardTitle , CardDescription } from "@/components/ui/card"
67import { Button } from "@/components/ui/button"
78import { Badge } from "@/components/ui/badge"
9+ import { Input } from "@/components/ui/input"
10+ import {
11+ DropdownMenu ,
12+ DropdownMenuTrigger ,
13+ DropdownMenuContent ,
14+ DropdownMenuCheckboxItem ,
15+ DropdownMenuLabel ,
16+ DropdownMenuSeparator ,
17+ } from "@/components/ui/dropdown-menu"
818import Link from "next/link"
9- import { Github , Linkedin , Code2 , Sparkles , Briefcase , ExternalLink } from "lucide-react"
19+ import { Github , Linkedin , Code2 , Sparkles , Briefcase , ExternalLink , Search , ChevronDown } from "lucide-react"
1020import { cn } from "@/lib/utils"
1121
1222// Import projects data from JSON file
@@ -29,14 +39,39 @@ const projectsWithDetails = projectsData.projects.map((project, index) => {
2939 }
3040} )
3141
32- const projects = projectsWithDetails
42+ const allTags = [ ... new Set ( projectsWithDetails . flatMap ( ( p ) => p . tags ) ) ] . sort ( )
3343
3444export default function ProjectsPage ( ) {
45+ const [ searchTerm , setSearchTerm ] = useState ( "" )
46+ const [ selectedTags , setSelectedTags ] = useState < string [ ] > ( [ ] )
47+
48+ const handleTagToggle = ( tag : string ) => {
49+ setSelectedTags ( ( prev ) =>
50+ prev . includes ( tag ) ? prev . filter ( ( t ) => t !== tag ) : [ ...prev , tag ]
51+ )
52+ }
53+
54+ const filteredProjects = useMemo ( ( ) => {
55+ return projectsWithDetails . filter ( ( project ) => {
56+ const searchLower = searchTerm . toLowerCase ( )
57+ const matchesSearch =
58+ project . project_name . toLowerCase ( ) . includes ( searchLower ) ||
59+ project . description . toLowerCase ( ) . includes ( searchLower ) ||
60+ project . tags . some ( ( tag ) => tag . toLowerCase ( ) . includes ( searchLower ) )
61+
62+ const matchesTags =
63+ selectedTags . length === 0 ||
64+ selectedTags . every ( ( tag ) => project . tags . includes ( tag ) )
65+
66+ return matchesSearch && matchesTags
67+ } )
68+ } , [ searchTerm , selectedTags ] )
69+
3570 return (
3671 < div className = "flex flex-col min-h-screen bg-gradient-to-br from-background via-background to-muted/5" >
3772 < Header />
3873
39- { /* Hero Section */ }
74+ { /* Hero Section */ }
4075 < section className = "py-20 md:py-32 relative overflow-hidden" >
4176 < div
4277 className = { cn (
@@ -80,8 +115,46 @@ export default function ProjectsPage() {
80115
81116 < section className = "relative py-20 bg-gradient-to-b from-muted/20 to-background" >
82117 < div className = "container relative z-10 mx-auto px-4" >
118+ { /* Search and Filter Controls */ }
119+ < div className = "mb-12 flex justify-center items-center gap-4 max-w-4xl mx-auto" >
120+ < div className = "relative flex-grow" >
121+ < Search className = "absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
122+ < Input
123+ type = "text"
124+ placeholder = "Search projects..."
125+ className = "w-full pl-12 pr-4 py-3 text-lg bg-background/70 backdrop-blur-sm rounded-full focus:ring-2 focus:ring-primary/50"
126+ value = { searchTerm }
127+ onChange = { ( e ) => setSearchTerm ( e . target . value ) }
128+ />
129+ </ div >
130+ < DropdownMenu >
131+ < DropdownMenuTrigger asChild >
132+ < Button variant = "outline" className = "flex items-center gap-2 rounded-full text-lg py-3 bg-background/70 backdrop-blur-sm" >
133+ < span > Tags</ span >
134+ { selectedTags . length > 0 && (
135+ < Badge variant = "secondary" className = "rounded-full" > { selectedTags . length } </ Badge >
136+ ) }
137+ < ChevronDown className = "h-5 w-5 text-muted-foreground" />
138+ </ Button >
139+ </ DropdownMenuTrigger >
140+ < DropdownMenuContent className = "w-64" >
141+ < DropdownMenuLabel > Filter by Tags</ DropdownMenuLabel >
142+ < DropdownMenuSeparator />
143+ { allTags . map ( ( tag ) => (
144+ < DropdownMenuCheckboxItem
145+ key = { tag }
146+ checked = { selectedTags . includes ( tag ) }
147+ onCheckedChange = { ( ) => handleTagToggle ( tag ) }
148+ >
149+ { tag }
150+ </ DropdownMenuCheckboxItem >
151+ ) ) }
152+ </ DropdownMenuContent >
153+ </ DropdownMenu >
154+ </ div >
155+
83156 < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 lg:gap-10" >
84- { projects . map ( ( project , index ) => (
157+ { filteredProjects . map ( ( project , index ) => (
85158 < div key = { project . project_name } className = "group" style = { { animationDelay : `${ index * 100 } ms` } } >
86159 < Card className = "relative h-full overflow-hidden border-0 bg-background/60 backdrop-blur-sm shadow-lg transition-all duration-500 hover:shadow-2xl hover:shadow-primary/10 hover:-translate-y-2" >
87160 < div
@@ -181,4 +254,4 @@ export default function ProjectsPage() {
181254 < Footer />
182255 </ div >
183256 )
184- }
257+ }
0 commit comments