feat: implementar sidebar colapsavel profissional

Substitui navegacao por header/menu mobile por sidebar lateral colapsavel
com toggle, persistencia localStorage e responsividade automatica.

- Novo componente Layout.tsx com sidebar, tooltips e overlay mobile
- Estado colapsado persistido em localStorage (desktop)
- Colapsada por defeito em mobile com drawer animado
- Animacoes suaves via framer-motion (spring)
- Removida navegacao duplicada de App.tsx, Monitor.tsx e Financial.tsx
- Rotas envolvidas pelo Layout via React Router Outlet

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 14:58:06 +00:00
parent 8148eb47fe
commit a4271fd06a
5 changed files with 340 additions and 226 deletions
+20 -103
View File
@@ -22,11 +22,8 @@ import {
CheckCircle2,
Timer,
Sparkles,
LayoutDashboard,
Activity,
Target,
Menu,
X,
} from 'lucide-react'
// Types
@@ -422,7 +419,6 @@ function App() {
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const [error, setError] = useState<string | null>(null)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const fetchData = useCallback(async () => {
setRefreshing(true)
@@ -459,7 +455,7 @@ function App() {
if (loading) {
return (
<div className="min-h-screen bg-mesh flex items-center justify-center">
<div className="flex items-center justify-center py-20">
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
@@ -481,7 +477,7 @@ function App() {
// Error state (Vulnerabilidade 3.2)
if (error) {
return (
<div className="min-h-screen bg-mesh flex items-center justify-center">
<div className="flex items-center justify-center py-20">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
@@ -510,93 +506,20 @@ function App() {
if (!data) return null
return (
<div className="min-h-screen bg-mesh">
<div className="bg-grid min-h-screen">
{/* Header */}
<header className="sticky top-0 z-50 border-b border-white/5 bg-[#0a0a0f]/90 backdrop-blur-2xl">
<div className="max-w-[1800px] mx-auto px-6 lg:px-8 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<motion.div
whileHover={{ scale: 1.05, rotate: 5 }}
className="w-11 h-11 rounded-xl bg-gradient-to-br from-brand-500 to-violet-600 flex items-center justify-center shadow-lg shadow-brand-500/30"
>
<Zap className="w-6 h-6 text-white" />
</motion.div>
<div>
<h1 className="text-xl font-bold text-white tracking-tight">Dashboard Descomplicar</h1>
<p className="text-xs text-zinc-500">Painel de Gestão</p>
</div>
</div>
<nav className="hidden md:flex items-center gap-1 bg-white/5 rounded-xl p-1">
<a href="#" className="px-4 py-2 rounded-lg bg-brand-500 text-white text-sm font-medium flex items-center gap-2">
<LayoutDashboard className="w-4 h-4" />
Dashboard
</a>
<a href="/monitor" className="px-4 py-2 rounded-lg text-zinc-400 hover:text-white hover:bg-white/10 text-sm font-medium transition-all flex items-center gap-2">
<Activity className="w-4 h-4" />
Monitor
</a>
<a href="/financial" className="px-4 py-2 rounded-lg text-zinc-400 hover:text-white hover:bg-white/10 text-sm font-medium transition-all flex items-center gap-2">
<CreditCard className="w-4 h-4" />
Financeiro
</a>
</nav>
<div className="flex items-center gap-3">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden p-2.5 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 transition-all"
>
{mobileMenuOpen ? <X className="w-5 h-5 text-zinc-400" /> : <Menu className="w-5 h-5 text-zinc-400" />}
</motion.button>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={fetchData}
disabled={refreshing}
className="p-2.5 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 transition-all"
>
<RefreshCw className={`w-5 h-5 text-zinc-400 ${refreshing ? 'animate-spin' : ''}`} />
</motion.button>
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-brand-500 to-violet-600 ring-2 ring-white/10" />
</div>
</div>
</div>
</header>
{/* Mobile Navigation */}
<AnimatePresence>
{mobileMenuOpen && (
<motion.nav
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="md:hidden border-b border-white/5 bg-[#0a0a0f]/95 backdrop-blur-2xl overflow-hidden"
<div className="max-w-[1800px] mx-auto px-6 lg:px-8 py-8">
{/* Header com refresh */}
<div className="flex items-center justify-between mb-2">
<div />
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={fetchData}
disabled={refreshing}
className="p-2.5 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 transition-all"
>
<div className="px-6 py-3 flex flex-col gap-1">
<a href="#" onClick={() => setMobileMenuOpen(false)} className="px-4 py-3 rounded-lg bg-brand-500 text-white text-sm font-medium flex items-center gap-3">
<LayoutDashboard className="w-4 h-4" />
Dashboard
</a>
<a href="/monitor" onClick={() => setMobileMenuOpen(false)} className="px-4 py-3 rounded-lg text-zinc-400 hover:text-white hover:bg-white/10 text-sm font-medium transition-all flex items-center gap-3">
<Activity className="w-4 h-4" />
Monitor
</a>
<a href="/financial" onClick={() => setMobileMenuOpen(false)} className="px-4 py-3 rounded-lg text-zinc-400 hover:text-white hover:bg-white/10 text-sm font-medium transition-all flex items-center gap-3">
<CreditCard className="w-4 h-4" />
Financeiro
</a>
</div>
</motion.nav>
)}
</AnimatePresence>
{/* Main Content */}
<main className="max-w-[1800px] mx-auto px-6 lg:px-8 py-8">
<RefreshCw className={`w-5 h-5 text-zinc-400 ${refreshing ? 'animate-spin' : ''}`} />
</motion.button>
</div>
<AnimatePresence mode="wait">
<motion.div
key="dashboard"
@@ -880,24 +803,18 @@ function App() {
</div>
</motion.div>
</AnimatePresence>
</main>
{/* Footer */}
<footer className="border-t border-white/5 mt-12">
<div className="max-w-[1800px] mx-auto px-6 lg:px-8 py-6">
<div className="flex items-center justify-between text-sm text-zinc-500">
{/* Footer */}
<footer className="border-t border-white/5 mt-12">
<div className="flex items-center justify-between text-sm text-zinc-500 py-6">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
<span>Dashboard Descomplicar v3.0</span>
<span className="text-zinc-700">·</span>
<span>Painel de Gestão</span>
</div>
<span>Actualizado: {new Date().toLocaleString('pt-PT')}</span>
</div>
</div>
</footer>
</div>
</div>
</footer>
</div>
)
}