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:
+22
-60
@@ -1,20 +1,15 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Link } from 'react-router-dom'
|
||||
import {
|
||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
|
||||
} from 'recharts'
|
||||
import {
|
||||
Zap,
|
||||
RefreshCw,
|
||||
ArrowLeft,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
DollarSign,
|
||||
Receipt,
|
||||
PiggyBank,
|
||||
Activity,
|
||||
LayoutDashboard,
|
||||
} from 'lucide-react'
|
||||
|
||||
interface FinancialData {
|
||||
@@ -105,7 +100,7 @@ export default function Financial() {
|
||||
|
||||
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 }} animate={{ opacity: 1 }} className="text-center">
|
||||
<DollarSign className="w-12 h-12 text-brand-400 mx-auto mb-4 animate-pulse" />
|
||||
<p className="text-zinc-400">A carregar dados financeiros...</p>
|
||||
@@ -127,59 +122,28 @@ export default function Financial() {
|
||||
}))
|
||||
|
||||
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-[1600px] 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 }}
|
||||
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">Financeiro</h1>
|
||||
<p className="text-xs text-zinc-500">Vendas e Despesas {new Date().getFullYear()}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<span className={`px-4 py-2 rounded-full text-sm font-semibold ${lucroColor}`}>
|
||||
{lucroLabel}: {formatEUR(Math.abs(data.lucro_ano))}
|
||||
</span>
|
||||
<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>
|
||||
<Link
|
||||
to="/monitor"
|
||||
className="hidden md:flex items-center gap-2 px-4 py-2 rounded-xl bg-white/5 hover:bg-white/10 text-zinc-400 hover:text-white transition-all text-sm"
|
||||
>
|
||||
<Activity className="w-4 h-4" />
|
||||
Monitor
|
||||
</Link>
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-xl bg-white/5 hover:bg-white/10 text-zinc-400 hover:text-white transition-all text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span className="hidden md:inline">Dashboard</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="max-w-[1600px] mx-auto px-6 lg:px-8 py-8">
|
||||
{/* Header com resumo e refresh */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white tracking-tight">Financeiro</h2>
|
||||
<p className="text-xs text-zinc-500">Vendas e Despesas {new Date().getFullYear()}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className={`px-4 py-2 rounded-full text-sm font-semibold ${lucroColor}`}>
|
||||
{lucroLabel}: {formatEUR(Math.abs(data.lucro_ano))}
|
||||
</span>
|
||||
<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>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-[1600px] mx-auto px-6 lg:px-8 py-8">
|
||||
<motion.div variants={containerVariants} initial="hidden" animate="show">
|
||||
{/* Summary Cards */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-5 mb-8">
|
||||
@@ -280,8 +244,6 @@ export default function Financial() {
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+22
-59
@@ -1,6 +1,5 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Link } from 'react-router-dom'
|
||||
import {
|
||||
Server,
|
||||
Wifi,
|
||||
@@ -11,11 +10,8 @@ import {
|
||||
RefreshCw,
|
||||
CheckCircle2,
|
||||
AlertTriangle,
|
||||
Zap,
|
||||
ArrowLeft,
|
||||
Activity,
|
||||
Clock,
|
||||
TrendingUp,
|
||||
Wrench,
|
||||
Code,
|
||||
Network,
|
||||
@@ -363,7 +359,7 @@ export default function Monitor() {
|
||||
|
||||
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 }} animate={{ opacity: 1 }} className="text-center">
|
||||
<Activity className="w-12 h-12 text-brand-400 mx-auto mb-4 animate-pulse" />
|
||||
<p className="text-zinc-400">A carregar monitorizacao...</p>
|
||||
@@ -392,59 +388,28 @@ export default function Monitor() {
|
||||
}[data.overall]
|
||||
|
||||
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-[1600px] 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 }}
|
||||
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">Monitorizacao</h1>
|
||||
<p className="text-xs text-zinc-500">Cluster Proxmox Descomplicar</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<span className={`px-4 py-2 rounded-full text-sm font-semibold ${overallColor}`}>
|
||||
{overallLabel}
|
||||
</span>
|
||||
<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>
|
||||
<Link
|
||||
to="/financial"
|
||||
className="hidden md:flex items-center gap-2 px-4 py-2 rounded-xl bg-white/5 hover:bg-white/10 text-zinc-400 hover:text-white transition-all text-sm"
|
||||
>
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
Financeiro
|
||||
</Link>
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-xl bg-white/5 hover:bg-white/10 text-zinc-400 hover:text-white transition-all text-sm"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div className="max-w-[1600px] mx-auto px-6 lg:px-8 py-8">
|
||||
{/* Header com status e refresh */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white tracking-tight">Monitorizacao</h2>
|
||||
<p className="text-xs text-zinc-500">Cluster Proxmox Descomplicar</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className={`px-4 py-2 rounded-full text-sm font-semibold ${overallColor}`}>
|
||||
{overallLabel}
|
||||
</span>
|
||||
<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>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-[1600px] mx-auto px-6 lg:px-8 py-8">
|
||||
<motion.div variants={containerVariants} initial="hidden" animate="show">
|
||||
|
||||
{/* Section 1: Cluster Overview */}
|
||||
@@ -661,9 +626,7 @@ export default function Monitor() {
|
||||
<span>Auto-refresh: 60s</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user