Files
trading_bot_v4/app/roadmap/page.tsx
mindesbunister b2c7551d5b 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
2025-11-25 07:52:10 +01:00

244 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState } from 'react'
interface RoadmapItem {
phase: string
title: string
status: 'complete' | 'in-progress' | 'planned'
description: string
impact: string
completed?: string
items?: string[]
}
export default function RoadmapPage() {
const [roadmap, setRoadmap] = useState<RoadmapItem[]>([])
const [loading, setLoading] = useState(true)
const [expandedItems, setExpandedItems] = useState<Set<number>>(new Set())
useEffect(() => {
fetchRoadmap()
}, [])
const fetchRoadmap = async () => {
try {
const response = await fetch('/api/roadmap')
const data = await response.json()
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) {
console.error('Failed to fetch roadmap:', error)
} finally {
setLoading(false)
}
}
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) => {
switch (status) {
case 'complete':
return 'bg-green-500/20 text-green-400 border-green-500/30'
case 'in-progress':
return 'bg-blue-500/20 text-blue-400 border-blue-500/30'
case 'planned':
return 'bg-gray-500/20 text-gray-400 border-gray-500/30'
default:
return 'bg-gray-500/20 text-gray-400 border-gray-500/30'
}
}
const getStatusIcon = (status: string) => {
switch (status) {
case 'complete':
return '✅'
case 'in-progress':
return '🔄'
case 'planned':
return '📋'
default:
return '❓'
}
}
const stats = {
total: roadmap.length,
complete: roadmap.filter(item => item.status === 'complete').length,
inProgress: roadmap.filter(item => item.status === 'in-progress').length,
planned: roadmap.filter(item => item.status === 'planned').length,
}
const completionRate = stats.total > 0 ? Math.round((stats.complete / stats.total) * 100) : 0
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 p-8">
<div className="max-w-6xl mx-auto">
<div className="text-center text-gray-400">Loading roadmap...</div>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 p-8">
<div className="max-w-6xl mx-auto space-y-8">
{/* Header */}
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-6">
<div className="flex items-center justify-between mb-4">
<h1 className="text-3xl font-bold text-white">🗺 Trading Bot Roadmap</h1>
<a
href="/"
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors flex items-center gap-2"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Back to Home
</a>
</div>
<p className="text-gray-400">Track development progress and upcoming features</p>
</div>
{/* Stats Overview */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-6">
<div className="text-gray-400 text-sm mb-1">Total Items</div>
<div className="text-3xl font-bold text-white">{stats.total}</div>
</div>
<div className="bg-green-500/10 backdrop-blur-sm border border-green-500/30 rounded-xl p-6">
<div className="text-green-400 text-sm mb-1"> Completed</div>
<div className="text-3xl font-bold text-green-400">{stats.complete}</div>
</div>
<div className="bg-blue-500/10 backdrop-blur-sm border border-blue-500/30 rounded-xl p-6">
<div className="text-blue-400 text-sm mb-1">🔄 In Progress</div>
<div className="text-3xl font-bold text-blue-400">{stats.inProgress}</div>
</div>
<div className="bg-gray-500/10 backdrop-blur-sm border border-gray-500/30 rounded-xl p-6">
<div className="text-gray-400 text-sm mb-1">📋 Planned</div>
<div className="text-3xl font-bold text-gray-400">{stats.planned}</div>
</div>
</div>
{/* Progress Bar */}
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-6">
<div className="flex items-center justify-between mb-2">
<div className="text-white font-semibold">Overall Completion</div>
<div className="text-green-400 font-bold">{completionRate}%</div>
</div>
<div className="w-full bg-gray-700 rounded-full h-4 overflow-hidden">
<div
className="bg-gradient-to-r from-green-500 to-emerald-500 h-full transition-all duration-500 rounded-full"
style={{ width: `${completionRate}%` }}
/>
</div>
</div>
{/* Roadmap Items */}
<div className="space-y-4">
{roadmap.map((item, index) => {
const isExpanded = expandedItems.has(index)
return (
<div
key={index}
className={`bg-gray-800/50 backdrop-blur-sm border rounded-xl overflow-hidden transition-all ${getStatusColor(item.status)}`}
>
<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 items-center gap-3 mb-2">
<span className="text-2xl">{getStatusIcon(item.status)}</span>
<h3 className="text-xl font-bold text-white">{item.title}</h3>
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-gray-700/50 text-gray-300">
{item.phase}
</span>
</div>
<p className="text-gray-300">{item.description}</p>
{item.completed && (
<div className="text-xs text-green-400 mt-2">
Completed: {item.completed}
</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>
{item.items && item.items.length > 0 && (
<div>
<div className="text-sm text-gray-400 mb-2">Implementation Details:</div>
<ul className="space-y-1">
{item.items.map((subItem, subIndex) => (
<li key={subIndex} className="text-sm text-gray-300 flex items-start gap-2">
<span className="text-gray-500 mt-1"></span>
<span>{subItem}</span>
</li>
))}
</ul>
</div>
)}
</div>
</div>
)}
</div>
)
})}
</div>
{/* Footer */}
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-6 text-center">
<p className="text-gray-400 text-sm">
Last updated: {new Date().toLocaleDateString()} Trading Bot v4
</p>
</div>
</div>
</div>
)
}