PHASE 7.2 COMPLETE (Nov 27, 2025): 4 validation checks before Smart Entry execution ADX degradation check (drops >2 points = cancel) Volume collapse check (drops >40% = cancel) RSI reversal detection (LONG RSI <30 or SHORT RSI >70 = cancel) MAGAP divergence check (wrong MA structure = cancel) Integrated with Smart Entry Timer (waits 2-4 min pullback) Detailed logging shows validation results EXPECTED IMPACT: - Block 5-10% of degraded signals during wait period - Save $300-800 in prevented losses over 100 trades - Prevent entries when ADX/volume/momentum weakens FILES CHANGED: - app/api/roadmap/route.ts (marked Phase 7.2 complete) - 1MIN_DATA_ENHANCEMENTS_ROADMAP.md (updated Phase 2 → Phase 7.2 complete) HOT-RELOAD SOLUTION (Zero Downtime Updates): Created /api/roadmap/reload endpoint POST to reload roadmap without container restart Roadmap page has Reload button with status messages No more unnecessary downtime for documentation updates! USAGE: - Web UI: Click Reload button on roadmap page - API: curl -X POST http://localhost:3001/api/roadmap/reload - Updates live instantly without rebuild/redeploy User request: "update the roadmap and documentation. also try to find a way to update the roadmap website without having to restart/rebuild/redeploy the whole container. thats unnessary downtime" All complete ✅
425 lines
18 KiB
TypeScript
425 lines
18 KiB
TypeScript
'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 [reloading, setReloading] = useState(false)
|
||
const [expandedItems, setExpandedItems] = useState<Set<number>>(new Set())
|
||
const [showCompleted, setShowCompleted] = useState(false)
|
||
const [reloadMessage, setReloadMessage] = useState('')
|
||
|
||
useEffect(() => {
|
||
fetchRoadmap()
|
||
}, [])
|
||
|
||
const fetchRoadmap = async () => {
|
||
try {
|
||
const response = await fetch('/api/roadmap', {
|
||
cache: 'no-store',
|
||
headers: {
|
||
'Cache-Control': 'no-cache'
|
||
}
|
||
})
|
||
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)
|
||
setReloading(false)
|
||
}
|
||
}
|
||
|
||
const handleReload = async () => {
|
||
setReloading(true)
|
||
setReloadMessage('')
|
||
try {
|
||
const response = await fetch('/api/roadmap/reload', {
|
||
method: 'POST'
|
||
})
|
||
const data = await response.json()
|
||
|
||
if (data.success) {
|
||
setReloadMessage('✅ Roadmap reloaded successfully!')
|
||
await fetchRoadmap()
|
||
setTimeout(() => setReloadMessage(''), 3000)
|
||
} else {
|
||
setReloadMessage('❌ Failed to reload roadmap')
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to reload roadmap:', error)
|
||
setReloadMessage('❌ Reload error')
|
||
setReloading(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>
|
||
<div className="flex items-center gap-3">
|
||
<button
|
||
onClick={handleReload}
|
||
disabled={reloading}
|
||
className={`px-4 py-2 rounded-lg transition-colors flex items-center gap-2 ${
|
||
reloading
|
||
? 'bg-gray-600 cursor-not-allowed text-gray-400'
|
||
: 'bg-blue-600 hover:bg-blue-500 text-white'
|
||
}`}
|
||
title="Hot-reload roadmap without container restart"
|
||
>
|
||
<svg
|
||
className={`w-5 h-5 ${reloading ? 'animate-spin' : ''}`}
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth={2}
|
||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||
/>
|
||
</svg>
|
||
{reloading ? 'Reloading...' : 'Reload'}
|
||
</button>
|
||
<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>
|
||
</div>
|
||
{reloadMessage && (
|
||
<div className={`mb-4 p-3 rounded-lg ${
|
||
reloadMessage.includes('✅')
|
||
? 'bg-green-500/20 text-green-400 border border-green-500/30'
|
||
: 'bg-red-500/20 text-red-400 border border-red-500/30'
|
||
}`}>
|
||
{reloadMessage}
|
||
</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-8">
|
||
{/* Completed Tasks Section */}
|
||
{stats.complete > 0 && (
|
||
<div className="bg-gray-800/50 backdrop-blur-sm border border-green-500/30 rounded-xl overflow-hidden">
|
||
<button
|
||
onClick={() => setShowCompleted(!showCompleted)}
|
||
className="w-full p-6 flex items-center justify-between hover:bg-gray-700/20 transition-colors"
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<span className="text-2xl">✅</span>
|
||
<h2 className="text-xl font-bold text-white">Completed Tasks</h2>
|
||
<span className="px-3 py-1 rounded-full text-sm font-semibold bg-green-500/20 text-green-400">
|
||
{stats.complete} items
|
||
</span>
|
||
</div>
|
||
<svg
|
||
className={`w-6 h-6 text-gray-400 transition-transform ${showCompleted ? '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>
|
||
|
||
{showCompleted && (
|
||
<div className="p-6 pt-0 space-y-4 border-t border-gray-700">
|
||
{roadmap
|
||
.map((item, index) => ({ item, index }))
|
||
.filter(({ item }) => item.status === 'complete')
|
||
.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>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* In-Progress and Planned Tasks */}
|
||
<div className="space-y-4">
|
||
{roadmap
|
||
.map((item, index) => ({ item, index }))
|
||
.filter(({ item }) => item.status !== 'complete')
|
||
.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>
|
||
</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>
|
||
)
|
||
}
|