1'use client'
2
3import { useState } from 'react'
4import Link from 'next/link'
5import { ArrowUpDown, ExternalLink, Search, X } from 'lucide-react'
6import { PieChart, Pie, ResponsiveContainer } from 'recharts'
7import { Project, SortConfig } from '@/lib/types'
8import { formatCurrency } from '@/lib/data'
9
10interface ProjectTableProps {
11 projects: Project[]
12}
13
14export default function ProjectTable({ projects }: ProjectTableProps) {
15 const [sortConfig, setSortConfig] = useState<SortConfig>({
16 key: 'fundingReceived',
17 direction: 'desc'
18 })
19 const [searchQuery, setSearchQuery] = useState('')
20
21 // Filter projects based on search query
22 const filteredProjects = projects.filter(project => {
23 if (!searchQuery) return true
24
25 const query = searchQuery.toLowerCase()
26 return (
27 project.name.toLowerCase().includes(query) ||
28 project.description.toLowerCase().includes(query) ||
29 project.categories.some(category => category.toLowerCase().includes(query))
30 )
31 })
32
33 // Sort filtered projects
34 const sortedProjects = [...filteredProjects].sort((a, b) => {
35 const aValue = a[sortConfig.key]
36 const bValue = b[sortConfig.key]
37
38 if (typeof aValue === 'number' && typeof bValue === 'number') {
39 return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue
40 }
41
42 const aString = String(aValue).toLowerCase()
43 const bString = String(bValue).toLowerCase()
44
45 if (sortConfig.direction === 'asc') {
46 return aString.localeCompare(bString)
47 } else {
48 return bString.localeCompare(aString)
49 }
50 })
51
52 const handleSort = (key: keyof Project) => {
53 setSortConfig({
54 key,
55 direction: sortConfig.key === key && sortConfig.direction === 'desc' ? 'asc' : 'desc'
56 })
57 }
58
59
60 const SortButton = ({ column, label }: { column: keyof Project; label: string }) => (
61 <button
62 onClick={() => handleSort(column)}
63 className="group flex items-center space-x-1 text-xs font-medium text-secondary uppercase tracking-wider hover:text-primary transition-colors"
64 >
65 <span>{label}</span>
66 <ArrowUpDown className="h-3 w-3 opacity-60 group-hover:opacity-100" />
67 </button>
68 )
69
70 return (
71 <div className="space-y-4">
72 {/* Search Bar */}
73 <div className="relative w-full sm:max-w-sm mb-4 sm:mb-0">
74 <input
75 type="text"
76 placeholder="Search projects..."
77 value={searchQuery}
78 onChange={(e) => setSearchQuery(e.target.value)}
79 className="w-full px-4 py-2 bg-transparent border-0 border-b border-border text-primary placeholder-muted focus:outline-none focus:border-accent transition-elegant"
80 />
81 {searchQuery && (
82 <button
83 onClick={() => setSearchQuery('')}
84 className="absolute right-1 top-1/2 transform -translate-y-1/2 p-1 text-muted hover:text-accent transition-colors"
85 >
86 <X className="h-3 w-3" />
87 </button>
88 )}
89 </div>
90
91 {/* Results count */}
92 {searchQuery && (
93 <div className="text-sm text-secondary mb-4">
94 {sortedProjects.length} of {projects.length} projects found
95 </div>
96 )}
97
98 <div className="overflow-x-auto">
99 <table className="w-full min-w-[800px]">
100 <thead>
101 <tr className="bg-accent-light border-b border-border">
102 <th className="text-left py-3 px-2 sm:px-4">
103 <SortButton column="name" label="Project" />
104 </th>
105 <th className="text-left py-3 px-2 sm:px-4 hidden sm:table-cell">
106 <SortButton column="fundingReceived" label="Funding" />
107 </th>
108 <th className="text-left py-3 px-2 sm:px-4 hidden md:table-cell">
109 <SortButton column="sustainableRevenuePercent" label="Sustainability" />
110 </th>
111 <th className="text-left py-3 px-2 sm:px-4 hidden lg:table-cell">
112 <SortButton column="annualBudget" label="Budget" />
113 </th>
114 <th className="text-left py-3 px-2 sm:px-4 hidden lg:table-cell">
115 <SortButton column="teamSize" label="Team" />
116 </th>
117 <th className="text-left py-3 px-2 sm:px-4 hidden md:table-cell">
118 <SortButton column="fundingGivenOut" label="Distributed" />
119 </th>
120 <th className="text-left py-3 px-2 sm:px-4 hidden sm:table-cell">
121 <span className="text-xs font-medium text-secondary uppercase tracking-wider">Categories</span>
122 </th>
123 </tr>
124 </thead>
125 <tbody>
126 {sortedProjects.map((project) => (
127 <tr
128 key={project.id}
129 className="hover:bg-surface-hover transition-colors group border-b border-border/30"
130 >
131 <td className="py-2 px-2 sm:px-4">
132 <div className="flex items-center space-x-3 sm:space-x-4">
133 <div className="flex-shrink-0">
134 <div className="w-8 h-8 bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
135 <span className="text-xs font-semibold text-gray-700 dark:text-gray-300">
136 {project.name.substring(0, 2).toUpperCase()}
137 </span>
138 </div>
139 </div>
140 <div className="min-w-0 flex-1">
141 <div className="flex items-center space-x-2">
142 <Link
143 href={`/project/${project.id}`}
144 className="text-sm font-semibold text-primary hover:text-accent transition-colors"
145 >
146 {project.name}
147 </Link>
148 <a
149 href={project.website}
150 target="_blank"
151 rel="noopener noreferrer"
152 className="opacity-0 group-hover:opacity-100 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 transition-all"
153 >
154 <ExternalLink className="h-3 w-3" />
155 </a>
156 </div>
157 <p className="text-xs text-secondary mt-1 max-w-xs truncate">
158 {project.description}
159 </p>
160 {/* Mobile-only funding info */}
161 <div className="sm:hidden mt-2 flex flex-wrap gap-2 text-xs">
162 <span className="font-medium text-primary">{formatCurrency(project.fundingReceived)}</span>
163 <span className="text-muted">•</span>
164 <span className="text-secondary">{project.sustainableRevenuePercent}% sustainable</span>
165 </div>
166 </div>
167 </div>
168 </td>
169 <td className="py-2 px-2 sm:px-4 hidden sm:table-cell">
170 <div className="text-xs font-semibold text-primary">
171 {formatCurrency(project.fundingReceived)}
172 </div>
173 <div className="text-xs text-muted">
174 Total raised
175 </div>
176 </td>
177 <td className="py-2 px-2 sm:px-4 hidden md:table-cell">
178 <div className="flex items-center space-x-3">
179 <div className="w-8 h-8">
180 <ResponsiveContainer width="100%" height="100%">
181 <PieChart>
182 <Pie
183 data={[
184 { value: project.sustainableRevenuePercent, fill: 'var(--accent)' },
185 { value: 100 - project.sustainableRevenuePercent, fill: 'var(--border)' }
186 ]}
187 cx="50%"
188 cy="50%"
189 innerRadius={8}
190 outerRadius={16}
191 startAngle={90}
192 endAngle={450}
193 dataKey="value"
194 >
195 </Pie>
196 </PieChart>
197 </ResponsiveContainer>
198 </div>
199 <div>
200 <div className="text-xs font-semibold text-primary">
201 {project.sustainableRevenuePercent}%
202 </div>
203 <div className="text-xs text-muted">
204 Sustainable
205 </div>
206 </div>
207 </div>
208 </td>
209 <td className="py-2 px-2 sm:px-4 hidden lg:table-cell">
210 <div className="text-xs font-semibold text-primary">
211 {formatCurrency(project.annualBudget)}
212 </div>
213 <div className="text-xs text-muted">
214 Annual
215 </div>
216 </td>
217 <td className="py-2 px-2 sm:px-4 hidden lg:table-cell">
218 <div className="text-xs font-semibold text-primary">
219 {project.teamSize}
220 </div>
221 <div className="text-xs text-muted">
222 {project.teamSize === 1 ? 'person' : 'people'}
223 </div>
224 </td>
225 <td className="py-2 px-2 sm:px-4 hidden md:table-cell">
226 <div className="text-xs font-semibold text-primary">
227 {formatCurrency(project.fundingGivenOut)}
228 </div>
229 <div className="text-xs text-muted">
230 {((project.fundingGivenOut / project.fundingReceived) * 100).toFixed(0)}% of received
231 </div>
232 </td>
233 <td className="py-2 px-2 sm:px-4 hidden sm:table-cell">
234 <div className="flex flex-wrap gap-0.5 max-w-32">
235 {project.categories.slice(0, 2).map((category) => (
236 <span
237 key={category}
238 className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600"
239 >
240 {category}
241 </span>
242 ))}
243 {project.categories.length > 2 && (
244 <span className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-600">
245 +{project.categories.length - 2}
246 </span>
247 )}
248 </div>
249 </td>
250 </tr>
251 ))}
252 </tbody>
253 </table>
254 </div>
255 </div>
256 )
257}