"use client"; import { useState, useEffect, useRef } from "react"; import { ArrowRight, Link, Zap } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; interface TimelineItem { id: number; title: string; date: string; content: string; category: string; icon: React.ElementType; relatedIds: number[]; status: "completed" | "in-progress" | "pending"; energy: number; } interface RadialOrbitalTimelineProps { timelineData: TimelineItem[]; } export default function RadialOrbitalTimeline({ timelineData, }: RadialOrbitalTimelineProps) { const [expandedItems, setExpandedItems] = useState>( {} ); const [viewMode, setViewMode] = useState<"orbital">("orbital"); const [rotationAngle, setRotationAngle] = useState(0); const [autoRotate, setAutoRotate] = useState(true); const [pulseEffect, setPulseEffect] = useState>({}); const [centerOffset, setCenterOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0, }); const [activeNodeId, setActiveNodeId] = useState(null); const containerRef = useRef(null); const orbitRef = useRef(null); const nodeRefs = useRef>({}); const handleContainerClick = (e: React.MouseEvent) => { if (e.target === containerRef.current || e.target === orbitRef.current) { setExpandedItems({}); setActiveNodeId(null); setPulseEffect({}); setAutoRotate(true); } }; const toggleItem = (id: number) => { setExpandedItems((prev) => { const newState = { ...prev }; Object.keys(newState).forEach((key) => { if (parseInt(key) !== id) { newState[parseInt(key)] = false; } }); newState[id] = !prev[id]; if (!prev[id]) { setActiveNodeId(id); setAutoRotate(false); const relatedItems = getRelatedItems(id); const newPulseEffect: Record = {}; relatedItems.forEach((relId) => { newPulseEffect[relId] = true; }); setPulseEffect(newPulseEffect); centerViewOnNode(id); } else { setActiveNodeId(null); setAutoRotate(true); setPulseEffect({}); } return newState; }); }; useEffect(() => { let rotationTimer: NodeJS.Timeout; if (autoRotate && viewMode === "orbital") { rotationTimer = setInterval(() => { setRotationAngle((prev) => (prev + 0.3) % 360); }, 40); } return () => { if (rotationTimer) { clearInterval(rotationTimer); } }; }, [autoRotate, viewMode]); const centerViewOnNode = (nodeId: number) => { if (viewMode !== "orbital" || !nodeRefs.current[nodeId]) return; const nodeIndex = timelineData.findIndex((item) => item.id === nodeId); const totalNodes = timelineData.length; const targetAngle = (nodeIndex / totalNodes) * 360; setRotationAngle(270 - targetAngle); }; const calculateNodePosition = (index: number, total: number) => { const angle = ((index / total) * 360 + rotationAngle) % 360; const radius = 200; const radian = angle * (Math.PI / 180); const x = Number((radius * Math.cos(radian) + centerOffset.x).toFixed(3)); const y = Number((radius * Math.sin(radian) + centerOffset.y).toFixed(3)); const zIndex = Math.round(100 - 50 * Math.sin(radian)); const opacity = Number( (0.4 + 0.6 * ((1 + Math.sin(radian)) / 2)).toFixed(3) ); return { x, y, angle, zIndex, opacity }; }; const getRelatedItems = (itemId: number): number[] => { const currentItem = timelineData.find((item) => item.id === itemId); return currentItem ? currentItem.relatedIds : []; }; const isRelatedToActive = (itemId: number): boolean => { if (!activeNodeId) return false; const relatedItems = getRelatedItems(activeNodeId); return relatedItems.includes(itemId); }; const getStatusStyles = (status: TimelineItem["status"]): string => { switch (status) { case "completed": return "text-white bg-black border-white"; case "in-progress": return "text-black bg-white border-black"; case "pending": return "text-white bg-black/40 border-white/50"; default: return "text-white bg-black/40 border-white/50"; } }; return (
{timelineData.map((item, index) => { const position = calculateNodePosition(index, timelineData.length); const isExpanded = expandedItems[item.id]; const isRelated = isRelatedToActive(item.id); const isPulsing = pulseEffect[item.id]; const Icon = item.icon; const nodeStyle = { transform: `translate(${position.x}px, ${position.y}px)`, zIndex: isExpanded ? 200 : position.zIndex, opacity: isExpanded ? 1 : position.opacity, }; return (
(nodeRefs.current[item.id] = el)} className="absolute transition-all duration-700 cursor-pointer" style={nodeStyle} onClick={(e) => { e.stopPropagation(); toggleItem(item.id); }} >
{item.title}
{isExpanded && (
{item.status === "completed" ? "COMPLETE" : item.status === "in-progress" ? "IN PROGRESS" : "PENDING"} {item.date}
{item.title}

{item.content}

Energy Level {item.energy}%
{item.relatedIds.length > 0 && (

Connected Nodes

{item.relatedIds.map((relatedId) => { const relatedItem = timelineData.find( (i) => i.id === relatedId ); return ( ); })}
)}
)}
); })}
); }