Skip to content

Commit 5a59bcd

Browse files
committed
refactor: blog & roadmap.
1 parent 70aa405 commit 5a59bcd

File tree

9 files changed

+424
-461
lines changed

9 files changed

+424
-461
lines changed

src/components/roadmap/RoadmapCard.js

Lines changed: 3 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,9 @@ import React from 'react';
22
import { Link } from 'react-router-dom';
33
import { motion } from 'framer-motion';
44
import { KanbanIcon } from '@phosphor-icons/react'; // Using KanbanIcon as a default/watermark icon
5+
import { getStatusClasses, getPriorityClasses, statusTextColor } from '../../utils/roadmapHelpers';
56

67
const RoadmapCard = ({ app, index }) => {
7-
const getStatusClasses = (status) => {
8-
let bgColor = '';
9-
let borderColor = '';
10-
switch (status) {
11-
case 'Planned':
12-
bgColor = 'bg-blue-500';
13-
borderColor = 'border-blue-700'; // Darker shade for border
14-
break;
15-
case 'In Progress':
16-
bgColor = 'bg-orange-500';
17-
borderColor = 'border-orange-700';
18-
break;
19-
case 'Completed':
20-
bgColor = 'bg-green-500';
21-
borderColor = 'border-green-700';
22-
break;
23-
case 'On Hold':
24-
bgColor = 'bg-red-500';
25-
borderColor = 'border-red-700';
26-
break;
27-
default:
28-
bgColor = 'bg-gray-500';
29-
borderColor = 'border-gray-700';
30-
}
31-
return `${bgColor} ${borderColor}`;
32-
};
33-
34-
const getPriorityClasses = (priority) => {
35-
let textColor = '';
36-
let borderColor = '';
37-
switch (priority) {
38-
case 'High':
39-
textColor = 'text-red-400';
40-
borderColor = 'border-red-700';
41-
break;
42-
case 'Medium':
43-
textColor = 'text-yellow-400';
44-
borderColor = 'border-yellow-700';
45-
break;
46-
case 'Low':
47-
textColor = 'text-green-400';
48-
borderColor = 'border-green-700';
49-
break;
50-
default:
51-
textColor = 'text-gray-400';
52-
borderColor = 'border-gray-700';
53-
}
54-
return `${textColor} ${borderColor}`;
55-
};
56-
57-
const statusTextColor = (status) => {
58-
if (status === 'Planned') return 'text-white';
59-
return 'text-black';
60-
};
61-
628
return (
639
<motion.div
6410
initial={{ opacity: 0, y: 20 }}
@@ -67,7 +13,7 @@ const RoadmapCard = ({ app, index }) => {
6713
>
6814
<Link to={`/roadmap/${app.id}`} className="block group relative h-full">
6915
{/* Main Card Container */}
70-
<div className="relative flex flex-col h-full bg-gray-900/80 backdrop-blur-xl border border-gray-800 rounded-xl p-4 overflow-hidden group-hover:border-purple-600 transition-all duration-300 shadow-xl">
16+
<div className="relative flex flex-col h-full bg-gray-900/80 backdrop-blur-xl border border-gray-800 rounded-xl p-4 overflow-hidden group-hover:border-primary-500 transition-all duration-300 shadow-xl">
7117
{/* Subtle Grid Background */}
7218
<div
7319
className="absolute inset-0 opacity-[0.03] pointer-events-none z-0"
@@ -96,7 +42,7 @@ const RoadmapCard = ({ app, index }) => {
9642
{app.priority || 'Low'}
9743
</span>
9844
</div>
99-
<h4 className="text-xl font-bold font-mono text-white mb-2 tracking-tight group-hover:text-purple-400 transition-colors">
45+
<h4 className="text-xl font-bold font-mono text-white mb-2 tracking-tight group-hover:text-primary-400 transition-colors">
10046
{app.title}
10147
</h4>
10248
<p className="text-gray-400 font-mono text-sm leading-relaxed line-clamp-3">

src/components/roadmap/TableView.js

Lines changed: 161 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
import React, { useState } from 'react';
2-
import { Link } from 'react-router-dom';
3-
2+
import { Link, useNavigate } from 'react-router-dom';
3+
import { MagnifyingGlass, Funnel, CaretUp, CaretDown, Check } from '@phosphor-icons/react';
44
import { getStatusClasses, getPriorityClasses, statusTextColor } from '../../utils/roadmapHelpers';
55

66
const TableView = ({ issuesData = [] }) => {
7+
const navigate = useNavigate();
78
const [sortBy, setSortBy] = useState('title');
89
const [sortOrder, setSortOrder] = useState('asc'); // 'asc' or 'desc'
9-
const [activeFilters, setActiveFilters] = useState(['Planned', 'In Progress', 'On Hold', 'Completed']); // Changed filterStatus to activeFilters
10+
const [activeFilters, setActiveFilters] = useState(['Planned', 'In Progress', 'On Hold', 'Completed']);
11+
const [searchQuery, setSearchQuery] = useState('');
1012

1113
const handleFilterChange = (status) => {
1214
if (activeFilters.includes(status)) {
13-
setActiveFilters(activeFilters.filter((s) => s !== status)); // Remove filter
15+
setActiveFilters(activeFilters.filter((s) => s !== status));
1416
} else {
15-
setActiveFilters([...activeFilters, status]); // Add filter
17+
setActiveFilters([...activeFilters, status]);
1618
}
1719
};
1820

1921
const filteredApps = issuesData.filter((app) => {
20-
if (activeFilters.length === 0) return false; // Show none if no filters active
21-
return activeFilters.includes(app.status || 'Planned');
22+
const matchesFilter = activeFilters.length === 0 || activeFilters.includes(app.status || 'Planned');
23+
const matchesSearch = (app.title?.toLowerCase() || '').includes(searchQuery.toLowerCase()) ||
24+
(app.description?.toLowerCase() || '').includes(searchQuery.toLowerCase());
25+
return matchesFilter && matchesSearch;
2226
});
2327

2428
const sortedApps = [...filteredApps].sort((a, b) => {
@@ -51,110 +55,160 @@ const TableView = ({ issuesData = [] }) => {
5155
}
5256
};
5357

54-
const renderSortArrow = (column) => {
55-
if (sortBy === column) {
56-
return sortOrder === 'asc' ? ' ↑' : ' ↓';
57-
}
58-
return '';
58+
const SortIcon = ({ column }) => {
59+
if (sortBy !== column) return <div className="w-4 h-4" />; // Placeholder to prevent layout shift
60+
return sortOrder === 'asc' ? <CaretUp weight="bold" size={14} /> : <CaretDown weight="bold" size={14} />;
5961
};
6062

6163
return (
62-
<div className="overflow-x-auto rounded-xl shadow-lg bg-gray-900/70 backdrop-blur-sm border border-gray-800">
63-
<div className="mb-4 mt-4 flex justify-center items-center flex-wrap gap-2">
64-
{['Planned', 'In Progress', 'Completed', 'On Hold'].map((status) => (
65-
<button
66-
key={status}
67-
onClick={() => handleFilterChange(status)}
68-
className={`px-4 py-2 rounded-md text-sm font-mono transition-colors border ${
69-
activeFilters.includes(status)
70-
? `${getStatusClasses(status).split(' ')[0]} ${getStatusClasses(status).split(' ')[1]} ${statusTextColor(status)}`
71-
: 'bg-gray-800/60 border-gray-700 text-gray-300 hover:border-indigo-500 hover:text-white'
72-
}`}
73-
>
74-
{status}
75-
</button>
76-
))}
77-
</div>
78-
<table className="min-w-full divide-y divide-gray-700 text-white">
79-
<thead className="bg-gray-800/60 border-b border-gray-700">
80-
<tr>
81-
<th
82-
scope="col"
83-
className="px-6 py-3 text-left text-sm font-mono font-bold text-gray-400 uppercase tracking-wide cursor-pointer hover:text-white transition-colors"
84-
onClick={() => handleSort('title')}
85-
>
86-
Title {renderSortArrow('title')}
87-
</th>
88-
<th
89-
scope="col"
90-
className="px-6 py-3 text-left text-sm font-mono font-bold text-gray-400 uppercase tracking-wide"
91-
>
92-
Description
93-
</th>
94-
<th
95-
scope="col"
96-
className="px-6 py-3 text-left text-sm font-mono font-bold text-gray-400 uppercase tracking-wide cursor-pointer hover:text-white transition-colors"
97-
onClick={() => handleSort('status')}
98-
>
99-
Status {renderSortArrow('status')}
100-
</th>
101-
<th
102-
scope="col"
103-
className="px-6 py-3 text-left text-sm font-mono font-bold text-gray-400 uppercase tracking-wide cursor-pointer hover:text-white transition-colors"
104-
onClick={() => handleSort('priority')}
105-
>
106-
Priority {renderSortArrow('priority')}
107-
</th>
108-
<th
109-
scope="col"
110-
className="px-6 py-3 text-left text-sm font-mono font-bold text-gray-400 uppercase tracking-wide cursor-pointer hover:text-white transition-colors"
111-
onClick={() => handleSort('created_at')}
112-
>
113-
Created At {renderSortArrow('created_at')}
114-
</th>
115-
<th
116-
scope="col"
117-
className="px-6 py-3 text-left text-sm font-mono font-bold text-gray-400 uppercase tracking-wide"
118-
>
119-
Notes
120-
</th>
121-
</tr>
122-
</thead>
123-
<tbody className="divide-y divide-gray-700">
124-
{sortedApps.map((app, index) => (
125-
<tr key={app.id} className={`group hover:bg-indigo-500/20 transition-colors ${index % 2 === 0 ? 'bg-gray-900/40' : 'bg-gray-800/40'}`}>
126-
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono font-medium text-white">
127-
<Link to={`/roadmap/${app.id}`} className="hover:underline text-purple-400">
128-
{app.title}
129-
</Link>
130-
</td>
131-
<td className="px-6 py-4 text-sm font-mono text-gray-400">
132-
{app.description}
133-
</td>
134-
<td className="px-6 py-4 whitespace-nowrap">
135-
<span
136-
className={`px-2 py-0 inline-flex text-xs font-mono font-semibold rounded-md shadow-sm border ${getStatusClasses(app.status)} ${statusTextColor(app.status)} `}
137-
>
138-
{app.status || 'Planned'}
139-
</span>
140-
</td>
141-
<td className="px-6 py-4 whitespace-nowrap">
142-
<span
143-
className={`px-2 py-0 inline-flex text-xs font-mono font-semibold rounded-md shadow-sm border ${getPriorityClasses(app.priority)}`}
144-
>
145-
{app.priority || 'Low'}
64+
<div className="space-y-6">
65+
{/* Toolbar: Search and Filters */}
66+
<div className="bg-gray-900/70 backdrop-blur-md rounded-2xl border border-gray-800 shadow-xl p-4 md:p-5 flex flex-col lg:flex-row gap-6 justify-between items-center">
67+
{/* Search */}
68+
<div className="relative w-full lg:max-w-md group">
69+
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
70+
<MagnifyingGlass className="text-gray-500 group-focus-within:text-primary-400 transition-colors" size={20} />
71+
</div>
72+
<input
73+
type="text"
74+
placeholder="Search issues by title or description..."
75+
value={searchQuery}
76+
onChange={(e) => setSearchQuery(e.target.value)}
77+
className="block w-full pl-11 pr-4 py-3 border border-gray-700 rounded-xl leading-5 bg-gray-800/50 text-gray-300 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500/50 focus:border-primary-500 focus:bg-gray-800 transition-all duration-300 text-sm font-mono"
78+
/>
79+
</div>
80+
81+
{/* Filters */}
82+
<div className="flex flex-col sm:flex-row items-center gap-3 w-full lg:w-auto justify-center lg:justify-end">
83+
<div className="flex items-center text-gray-400 font-mono text-xs uppercase tracking-wider">
84+
<Funnel size={16} className="mr-2" /> Filter Status:
85+
</div>
86+
<div className="flex flex-wrap justify-center gap-2">
87+
{['Planned', 'In Progress', 'Completed', 'On Hold'].map((status) => (
88+
<button
89+
key={status}
90+
onClick={() => handleFilterChange(status)}
91+
className={`
92+
group relative px-3 py-1.5 rounded-lg text-xs font-mono font-bold transition-all duration-200 border select-none
93+
${activeFilters.includes(status)
94+
? `${getStatusClasses(status).split(' ')[0]} ${getStatusClasses(status).split(' ')[1]} ${statusTextColor(status)} shadow-md ring-1 ring-white/10`
95+
: 'bg-gray-800/40 border-gray-700 text-gray-500 hover:border-gray-600 hover:bg-gray-800 hover:text-gray-300'
96+
}
97+
`}
98+
>
99+
<span className="flex items-center gap-1.5">
100+
{activeFilters.includes(status) && <Check weight="bold" />}
101+
{status}
146102
</span>
147-
</td>
148-
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-400">
149-
{new Date(app.created_at).toLocaleDateString()}
150-
</td>
151-
<td className="px-6 py-4 text-sm font-mono text-gray-500">
152-
{app.notes || '-'}
153-
</td>
154-
</tr>
155-
))}
156-
</tbody>
157-
</table>
103+
</button>
104+
))}
105+
</div>
106+
</div>
107+
</div>
108+
109+
{/* Table */}
110+
<div className="overflow-hidden rounded-2xl shadow-2xl bg-gray-900/70 backdrop-blur-md border border-gray-800">
111+
<div className="overflow-x-auto">
112+
<table className="min-w-full divide-y divide-gray-800">
113+
<thead>
114+
<tr className="bg-gray-800/60">
115+
{[
116+
{ key: 'title', label: 'Title' },
117+
{ key: 'description', label: 'Description', noSort: true },
118+
{ key: 'status', label: 'Status' },
119+
{ key: 'priority', label: 'Priority' },
120+
{ key: 'created_at', label: 'Created' },
121+
{ key: 'notes', label: 'Notes', noSort: true },
122+
].map((col) => (
123+
<th
124+
key={col.key}
125+
scope="col"
126+
onClick={() => !col.noSort && handleSort(col.key)}
127+
className={`
128+
px-6 py-4 text-left text-xs font-mono font-bold text-gray-400 uppercase tracking-wider
129+
${!col.noSort ? 'cursor-pointer hover:text-primary-400 hover:bg-gray-800/50 transition-colors select-none' : ''}
130+
`}
131+
>
132+
<div className="flex items-center gap-2">
133+
{col.label}
134+
{!col.noSort && <SortIcon column={col.key} />}
135+
</div>
136+
</th>
137+
))}
138+
</tr>
139+
</thead>
140+
<tbody className="divide-y divide-gray-800/50">
141+
{sortedApps.length > 0 ? (
142+
sortedApps.map((app, index) => (
143+
<tr
144+
key={app.id}
145+
onClick={(e) => {
146+
// Navigate if the click didn't originate from a link
147+
if (!e.target.closest('a')) {
148+
navigate(`/roadmap/${app.id}`);
149+
}
150+
}}
151+
className={`
152+
group transition-colors duration-200 cursor-pointer
153+
${index % 2 === 0 ? 'bg-gray-900/20' : 'bg-transparent'}
154+
hover:!bg-gray-800/60
155+
`}
156+
>
157+
<td className="px-6 py-4 whitespace-nowrap">
158+
<Link to={`/roadmap/${app.id}`} className="text-sm font-mono font-bold text-white group-hover:text-primary-400 transition-colors">
159+
{app.title}
160+
</Link>
161+
</td>
162+
<td className="px-6 py-4 text-sm text-gray-400 font-mono max-w-xs truncate" title={app.description}>
163+
{app.description}
164+
</td>
165+
<td className="px-6 py-4 whitespace-nowrap">
166+
<span className={`px-2.5 py-1 inline-flex items-center text-[10px] font-mono font-bold uppercase tracking-wide rounded-full border ${getStatusClasses(app.status)} ${statusTextColor(app.status)} shadow-sm`}>
167+
{app.status || 'Planned'}
168+
</span>
169+
</td>
170+
<td className="px-6 py-4 whitespace-nowrap">
171+
<span className={`flex items-center gap-2 text-xs font-mono font-bold ${getPriorityClasses(app.priority).split(' ')[0]}`}>
172+
<span className={`w-2 h-2 rounded-full ${
173+
app.priority === 'High' ? 'bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.6)]' :
174+
app.priority === 'Medium' ? 'bg-yellow-500 shadow-[0_0_8px_rgba(234,179,8,0.6)]' :
175+
'bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.6)]'
176+
}`}></span>
177+
{app.priority || 'Low'}
178+
</span>
179+
</td>
180+
<td className="px-6 py-4 whitespace-nowrap text-xs font-mono text-gray-500">
181+
{new Date(app.created_at).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })}
182+
</td>
183+
<td className="px-6 py-4 text-xs text-gray-500 font-mono italic max-w-xs truncate">
184+
{app.notes || '-'}
185+
</td>
186+
</tr>
187+
))
188+
) : (
189+
<tr>
190+
<td colSpan="6" className="px-6 py-16 text-center text-gray-500 font-mono">
191+
<div className="flex flex-col items-center justify-center gap-4">
192+
<div className="p-4 rounded-full bg-gray-800/50">
193+
<MagnifyingGlass size={32} className="opacity-50" />
194+
</div>
195+
<p className="text-lg font-medium text-gray-400">No issues found</p>
196+
<p className="text-sm">Try adjusting your search or filters.</p>
197+
</div>
198+
</td>
199+
</tr>
200+
)}
201+
</tbody>
202+
</table>
203+
</div>
204+
</div>
205+
206+
{/* Footer info */}
207+
<div className="flex justify-end items-center px-2">
208+
<span className="text-xs font-mono text-gray-600 bg-gray-900/50 px-3 py-1 rounded-full border border-gray-800">
209+
Showing <span className="text-primary-400 font-bold">{sortedApps.length}</span> of {issuesData.length} issues
210+
</span>
211+
</div>
158212
</div>
159213
);
160214
};

0 commit comments

Comments
 (0)