feat: Add collapsible roadmap items with completed collapsed by default

- Added expandedItems state to track which items are expanded
- Auto-expands only non-complete items on load (in-progress, planned)
- Complete items collapsed by default for better overview
- Click anywhere on item header or chevron to toggle
- Smooth transitions and hover effects
- Improves readability when many items are complete
This commit is contained in:
mindesbunister
2025-11-25 07:52:10 +01:00
parent 5677d8d1b4
commit b2c7551d5b

View File

@@ -15,6 +15,7 @@ interface RoadmapItem {
export default function RoadmapPage() { export default function RoadmapPage() {
const [roadmap, setRoadmap] = useState<RoadmapItem[]>([]) const [roadmap, setRoadmap] = useState<RoadmapItem[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [expandedItems, setExpandedItems] = useState<Set<number>>(new Set())
useEffect(() => { useEffect(() => {
fetchRoadmap() fetchRoadmap()
@@ -25,6 +26,15 @@ export default function RoadmapPage() {
const response = await fetch('/api/roadmap') const response = await fetch('/api/roadmap')
const data = await response.json() const data = await response.json()
setRoadmap(data.roadmap || []) setRoadmap(data.roadmap || [])
// Auto-expand only non-complete items by default
const autoExpand = new Set<number>()
data.roadmap?.forEach((item: RoadmapItem, index: number) => {
if (item.status !== 'complete') {
autoExpand.add(index)
}
})
setExpandedItems(autoExpand)
} catch (error) { } catch (error) {
console.error('Failed to fetch roadmap:', error) console.error('Failed to fetch roadmap:', error)
} finally { } finally {
@@ -32,6 +42,16 @@ export default function RoadmapPage() {
} }
} }
const toggleItem = (index: number) => {
const newExpanded = new Set(expandedItems)
if (newExpanded.has(index)) {
newExpanded.delete(index)
} else {
newExpanded.add(index)
}
setExpandedItems(newExpanded)
}
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status) { switch (status) {
case 'complete': case 'complete':
@@ -136,12 +156,18 @@ export default function RoadmapPage() {
{/* Roadmap Items */} {/* Roadmap Items */}
<div className="space-y-4"> <div className="space-y-4">
{roadmap.map((item, index) => ( {roadmap.map((item, index) => {
const isExpanded = expandedItems.has(index)
return (
<div <div
key={index} key={index}
className={`bg-gray-800/50 backdrop-blur-sm border rounded-xl p-6 ${getStatusColor(item.status)}`} className={`bg-gray-800/50 backdrop-blur-sm border rounded-xl overflow-hidden transition-all ${getStatusColor(item.status)}`}
> >
<div className="flex items-start justify-between mb-4"> <div
className="p-6 cursor-pointer hover:bg-gray-700/20 transition-colors"
onClick={() => toggleItem(index)}
>
<div className="flex items-start justify-between">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<span className="text-2xl">{getStatusIcon(item.status)}</span> <span className="text-2xl">{getStatusIcon(item.status)}</span>
@@ -150,21 +176,42 @@ export default function RoadmapPage() {
{item.phase} {item.phase}
</span> </span>
</div> </div>
<p className="text-gray-300 mb-3">{item.description}</p> <p className="text-gray-300">{item.description}</p>
<div className="bg-gray-900/50 rounded-lg p-3 mb-3">
<div className="text-xs text-gray-400 mb-1">Expected Impact</div>
<div className="text-sm text-white font-semibold">{item.impact}</div>
</div>
{item.completed && ( {item.completed && (
<div className="text-xs text-green-400"> <div className="text-xs text-green-400 mt-2">
Completed: {item.completed} Completed: {item.completed}
</div> </div>
)} )}
</div> </div>
<button
className="ml-4 text-gray-400 hover:text-white transition-colors"
onClick={(e) => {
e.stopPropagation()
toggleItem(index)
}}
>
<svg
className={`w-6 h-6 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
</div>
{isExpanded && (
<div className="px-6 pb-6 border-t border-gray-700">
<div className="pt-4">
<div className="bg-gray-900/50 rounded-lg p-3 mb-3">
<div className="text-xs text-gray-400 mb-1">Expected Impact</div>
<div className="text-sm text-white font-semibold">{item.impact}</div>
</div> </div>
{item.items && item.items.length > 0 && ( {item.items && item.items.length > 0 && (
<div className="border-t border-gray-700 pt-4 mt-4"> <div>
<div className="text-sm text-gray-400 mb-2">Implementation Details:</div> <div className="text-sm text-gray-400 mb-2">Implementation Details:</div>
<ul className="space-y-1"> <ul className="space-y-1">
{item.items.map((subItem, subIndex) => ( {item.items.map((subItem, subIndex) => (
@@ -177,7 +224,11 @@ export default function RoadmapPage() {
</div> </div>
)} )}
</div> </div>
))} </div>
)}
</div>
)
})}
</div> </div>
{/* Footer */} {/* Footer */}