File size: 8,210 Bytes
5e52bd7 f353f19 5e52bd7 6314c8a 5e52bd7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | "use client";
import { translations, Language } from "../lib/translations";
import { avatarBase64 } from "../lib/avatar";
import { userPhotoBase64 } from "../lib/user_photo";
import { useState, type Dispatch, type SetStateAction } from "react";
type SidebarTab =
| "Dashboard"
| "Tender Search"
| "My Portfolio"
| "Market Monitor"
| "Company Profile"
| "Agent Analysis"
| "Proposal Draft"
| "History"
| "Documentation"
| "Database"
| "About";
type Props = {
tabs: readonly SidebarTab[];
activeTab: SidebarTab;
onTabSelect: Dispatch<SetStateAction<SidebarTab>>;
status: string;
lang: Language;
forceExpanded?: boolean;
};
export default function Sidebar({ tabs, activeTab, onTabSelect, status, lang, forceExpanded = false }: Props) {
const t = translations[lang];
const [isHovered, setIsHovered] = useState(false);
const isExpanded = forceExpanded || isHovered;
const getTabLabel = (tab: SidebarTab) => {
switch(tab) {
case "Dashboard": return { label: t.dashboard, icon: "📊" };
case "Tender Search": return { label: t.tenderSearch, icon: "📡" };
case "My Portfolio": return { label: t.myPortfolio, icon: "★" };
case "Market Monitor": return { label: "Market Monitor", icon: "🛒" };
case "Company Profile": return { label: t.companyProfile, icon: "🏢" };
case "Agent Analysis": return { label: t.agentAnalysis, icon: "🤖" };
case "Proposal Draft": return { label: t.proposalDraft, icon: "✍️" };
case "History": return { label: t.history, icon: "🕒" };
case "Documentation": return { label: t.documentation, icon: "📚" };
case "Database": return { label: "Local DB", icon: "🗄️" };
case "About": return { label: t.about, icon: "ℹ️" };
default: return { label: tab, icon: "•" };
}
};
return (
<aside
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={`glass-card rounded-2xl md:rounded-3xl h-full md:h-[calc(100vh-3rem)] md:sticky md:top-6 p-3 md:p-4 flex flex-col gap-4 md:gap-8 transition-all duration-500 ease-in-out z-50 border-white/10 ${isExpanded ? 'w-full md:w-72 shadow-2xl shadow-purple-500/10 bg-black/60' : 'w-[84px] shadow-none border-white/5 bg-white/[0.02]'}`}
>
<div className={`flex items-center gap-3 px-2 transition-all duration-300 ${isExpanded ? 'justify-start' : 'justify-center'}`}>
<div className="w-10 h-10 premium-gradient rounded-xl flex-shrink-0 flex items-center justify-center shadow-lg shadow-purple-500/20">
<span className="text-white font-bold text-xl">A</span>
</div>
{isExpanded && (
<div className="animate-in fade-in duration-500">
<h1 className="text-xl font-bold tracking-tight text-white whitespace-nowrap">AndesOps</h1>
<p className="text-[8px] font-black uppercase tracking-[0.3em] text-cyan opacity-50">v1.2.7 • AMD Hackathon Final</p>
</div>
)}
</div>
<nav className="flex-1 flex flex-col gap-2 overflow-y-auto overflow-x-hidden custom-scrollbar pr-1">
{tabs.map((tab) => {
const isActive = activeTab === tab;
const { label, icon } = getTabLabel(tab);
const tabSlug = tab.toLowerCase().replace(/ /g, "_");
return (
<button
key={tab}
onClick={() => {
onTabSelect(tab);
window.history.pushState({}, '', `?tab=${tabSlug}`);
}}
className={`flex items-center rounded-2xl transition-all duration-300 active:scale-90 group relative ${
isActive
? "bg-white/10 text-white shadow-[inset_0_0_20px_rgba(255,255,255,0.05)] border border-white/10"
: "text-slate-400 hover:bg-white/5 hover:text-white"
} ${isExpanded ? 'px-5 py-4 gap-4' : 'px-0 py-4 w-full justify-center'}`}
>
<span className={`text-xl transition-all duration-300 ${isActive ? 'scale-110' : 'group-hover:scale-110 opacity-70 group-hover:opacity-100'}`}>
{icon}
</span>
{isExpanded && (
<span className="font-medium text-sm whitespace-nowrap animate-in slide-in-from-left-2 duration-300">
{label}
</span>
)}
{!isExpanded && isActive && (
<div className="absolute right-0 top-1/2 -translate-y-1/2 w-1 h-6 bg-purple-500 rounded-l-full shadow-[0_0_12px_rgba(168,85,247,0.8)]" />
)}
{/* Tooltip for collapsed mode */}
{!isExpanded && (
<div className="absolute left-full ml-4 px-3 py-2 bg-slate-900 text-white text-[10px] font-bold rounded-lg opacity-0 pointer-events-none group-hover:opacity-100 transition-opacity border border-white/10 whitespace-nowrap z-50">
{label}
</div>
)}
</button>
);
})}
</nav>
<div className="mt-auto space-y-4 pt-6 border-t border-white/5">
{/* User Profile Section */}
<div className={`flex items-center gap-3 transition-all duration-300 ${isExpanded ? 'px-2' : 'justify-center'}`}>
<div className="relative group">
<div className="absolute -inset-0.5 bg-gradient-to-r from-purple-600 to-cyan-600 rounded-xl blur opacity-20 group-hover:opacity-40 transition"></div>
<div className="relative w-10 h-10 rounded-xl overflow-hidden border border-white/10 bg-slate-900">
<img
src={avatarBase64}
alt="Profile"
className="w-full h-full object-cover"
/>
</div>
</div>
{isExpanded && (
<div className="flex-1 min-w-0 animate-in fade-in slide-in-from-left-2 duration-500 text-left">
<p className="text-white font-bold text-sm tracking-tight truncate">Álvaro Valenzuela</p>
<p className="text-[9px] font-black uppercase tracking-widest text-cyan opacity-60">Architect & CEO @ <a href="https://www.rew.cl" target="_blank" rel="noopener noreferrer" className="hover:text-white transition-colors underline decoration-cyan/30">REW.cl</a></p>
</div>
)}
</div>
<div className={`rounded-xl transition-all duration-500 bg-gradient-to-br from-indigo-500/10 to-purple-500/10 border border-indigo-500/20 ${isExpanded ? 'px-4 py-3' : 'p-2 flex justify-center'}`}>
{isExpanded ? (
<>
<p className="text-[10px] uppercase tracking-widest text-indigo-300 font-bold mb-1">Status</p>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${
status === "connected" ? "bg-green-500 animate-pulse" :
status === "listening" ? "bg-blue-500 animate-bounce" :
status === "warning" ? "bg-yellow-500 animate-pulse" :
"bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.5)]"
}`} />
<span className={`text-xs font-medium ${
status === "connected" ? "text-green-300" :
status === "listening" ? "text-blue-300" :
status === "warning" ? "text-yellow-300" :
"text-red-300"
}`}>
{status === "connected" ? "Systems Nominal" :
status === "listening" ? "Synchronizing..." :
status === "warning" ? "Limited Connectivity" :
"Connection Offline"}
</span>
</div>
</>
) : (
<div className={`w-3 h-3 rounded-full ${
status === "connected" ? "bg-green-500 animate-pulse" :
status === "listening" ? "bg-blue-500 animate-bounce" :
status === "warning" ? "bg-yellow-500 animate-pulse" :
"bg-red-500 shadow-[0_0_8px_rgba(239,68,68,0.5)]"
}`} />
)}
</div>
</div>
</aside>
);
}
|