165 lines
10 KiB
TypeScript
165 lines
10 KiB
TypeScript
|
|
|
||
|
|
import React from 'react';
|
||
|
|
import { useCRM } from '../context/CRMContext';
|
||
|
|
import { useNavigate } from 'react-router-dom';
|
||
|
|
import clsx from 'clsx';
|
||
|
|
import { TrendingUp, TrendingDown, DollarSign, Activity, ArrowRight, Package } from 'lucide-react';
|
||
|
|
|
||
|
|
const Dashboard: React.FC = () => {
|
||
|
|
const { orders, inventory, suppliers } = useCRM();
|
||
|
|
const navigate = useNavigate();
|
||
|
|
|
||
|
|
// Financial Calculation
|
||
|
|
const activeOrders = orders.filter(o => o.status === 'Pending' || o.status === 'Paid');
|
||
|
|
const totalInvested = activeOrders.reduce((acc, o) => acc + o.totalCostWithOverhead, 0);
|
||
|
|
// Mocking a growth metric for demonstration
|
||
|
|
const totalProfitHistory = orders.filter(o => o.status === 'Received').reduce((acc, o) => acc + o.estimatedProfit, 0);
|
||
|
|
const inventoryValue = inventory.reduce((acc, i) => acc + ((Number(i.marketPriceBRL) || 0) * (Number(i.quantity) || 0)), 0);
|
||
|
|
|
||
|
|
// "Am I winning?" - Net Result (Mocked relative to investment for delta)
|
||
|
|
const netResultDelta = totalInvested > 0 ? (totalProfitHistory / totalInvested) * 100 : 0;
|
||
|
|
const isWinning = netResultDelta >= 0;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6 animate-in fade-in duration-500 pb-20">
|
||
|
|
|
||
|
|
{/* HERDER: "Am I winning?" Context */}
|
||
|
|
<div className="flex items-end justify-between border-b border-white/5 pb-4">
|
||
|
|
<div>
|
||
|
|
<h2 className="text-xl font-bold text-white tracking-tight">Visão Geral</h2>
|
||
|
|
<p className="text-sm text-slate-500">Performance financeira e operacional.</p>
|
||
|
|
</div>
|
||
|
|
<div className="text-right">
|
||
|
|
<p className="text-[10px] uppercase font-bold text-slate-500 tracking-widest">Resultado Líquido (YTD)</p>
|
||
|
|
<div className={clsx("text-2xl font-bold flex items-center justify-end gap-2", isWinning ? "text-emerald-400" : "text-rose-400")}>
|
||
|
|
{isWinning ? <TrendingUp size={24} /> : <TrendingDown size={24} />}
|
||
|
|
R$ {totalProfitHistory.toLocaleString('pt-BR')}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* HIGH PRECISION METRICS GRID */}
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
|
|
{/* Metric 1: Capital Allocation */}
|
||
|
|
<div className="glass-card p-5 rounded-xl border border-white/10 relative overflow-hidden">
|
||
|
|
<div className="flex justify-between items-start mb-2">
|
||
|
|
<p className="text-[10px] font-bold text-slate-500 uppercase tracking-widest">Capital Alocado</p>
|
||
|
|
<DollarSign size={14} className="text-slate-600" />
|
||
|
|
</div>
|
||
|
|
<h3 className="text-xl font-bold text-white tracking-tight">R$ {totalInvested.toLocaleString('pt-BR')}</h3>
|
||
|
|
<div className="mt-3 flex items-center gap-2">
|
||
|
|
<span className="text-[10px] font-bold text-emerald-400 bg-emerald-400/5 px-1.5 py-0.5 rounded border border-emerald-400/10">+12% vs. mês ant.</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Metric 2: Stock Value (Liquidity) */}
|
||
|
|
<div className="glass-card p-5 rounded-xl border border-white/10 relative overflow-hidden">
|
||
|
|
<div className="flex justify-between items-start mb-2">
|
||
|
|
<p className="text-[10px] font-bold text-slate-500 uppercase tracking-widest">Valor em Estoque</p>
|
||
|
|
<Package size={14} className="text-slate-600" />
|
||
|
|
</div>
|
||
|
|
<h3 className="text-xl font-bold text-white tracking-tight">R$ {inventoryValue.toLocaleString('pt-BR')}</h3>
|
||
|
|
<div className="mt-3 flex items-center gap-2">
|
||
|
|
<span className="text-[10px] font-bold text-slate-400 bg-white/5 px-1.5 py-0.5 rounded border border-white/5">Giro: 15 dias</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Metric 3: Margin */}
|
||
|
|
<div className="glass-card p-5 rounded-xl border border-white/10 relative overflow-hidden">
|
||
|
|
<div className="flex justify-between items-start mb-2">
|
||
|
|
<p className="text-[10px] font-bold text-slate-500 uppercase tracking-widest">Margem Média</p>
|
||
|
|
<Activity size={14} className="text-slate-600" />
|
||
|
|
</div>
|
||
|
|
<h3 className="text-xl font-bold text-white tracking-tight">24.5%</h3>
|
||
|
|
<div className="mt-3 flex items-center gap-2">
|
||
|
|
<span className="text-[10px] font-bold text-rose-400 bg-rose-400/5 px-1.5 py-0.5 rounded border border-rose-400/10">-2.1% (Pressão de Custo)</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Metric 4: Active Orders */}
|
||
|
|
<div className="glass-card p-5 rounded-xl border border-white/10 relative overflow-hidden">
|
||
|
|
<div className="flex justify-between items-start mb-2">
|
||
|
|
<p className="text-[10px] font-bold text-slate-500 uppercase tracking-widest">Ordens Ativas</p>
|
||
|
|
<Activity size={14} className="text-slate-600" />
|
||
|
|
</div>
|
||
|
|
<h3 className="text-xl font-bold text-white tracking-tight">{activeOrders.length}</h3>
|
||
|
|
<div className="mt-3 flex items-center gap-2">
|
||
|
|
<button className="text-[10px] font-bold text-indigo-400 hover:text-indigo-300 flex items-center gap-1 transition-colors">
|
||
|
|
Ver detalhes <ArrowRight size={10} />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* DATA DENSITY TABLE (Bloomberg Style) */}
|
||
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
|
|
{/* ORDER FLOW */}
|
||
|
|
<div className="lg:col-span-2 glass-card rounded-xl border border-white/10 overflow-hidden flex flex-col min-h-[400px]">
|
||
|
|
<div className="px-6 py-4 border-b border-white/5 flex justify-between items-center bg-white/[0.01]">
|
||
|
|
<h3 className="text-sm font-bold text-white uppercase tracking-wider">Fluxo de Pedidos</h3>
|
||
|
|
<button onClick={() => navigate('/orders')} className="text-[10px] text-indigo-400 hover:text-white transition-colors font-bold uppercase tracking-wider">Expandir</button>
|
||
|
|
</div>
|
||
|
|
<table className="w-full text-left border-collapse">
|
||
|
|
<thead className="bg-black/20 text-[10px] font-bold text-slate-500 uppercase tracking-widest border-b border-white/5">
|
||
|
|
<tr>
|
||
|
|
<th className="px-6 py-3 font-semibold">ID</th>
|
||
|
|
<th className="px-6 py-3 font-semibold">Fornecedor</th>
|
||
|
|
<th className="px-6 py-3 font-semibold text-right">Valor</th>
|
||
|
|
<th className="px-6 py-3 font-semibold text-right">Status</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody className="divide-y divide-white/5 text-sm">
|
||
|
|
{orders.length === 0 ? (
|
||
|
|
<tr><td colSpan={4} className="p-8 text-center text-slate-600 italic text-xs">Sem dados recentes.</td></tr>
|
||
|
|
) : (
|
||
|
|
orders.slice(0, 8).map(o => (
|
||
|
|
<tr key={o.id} className="hover:bg-white/[0.02] transition-colors">
|
||
|
|
<td className="px-6 py-3 font-mono text-slate-400 text-xs">{o.id.substring(0, 8)}...</td>
|
||
|
|
<td className="px-6 py-3 font-medium text-slate-200">{o.supplierName}</td>
|
||
|
|
<td className="px-6 py-3 text-right font-mono text-slate-300">R$ {o.totalCostWithOverhead.toLocaleString('pt-BR')}</td>
|
||
|
|
<td className="px-6 py-3 text-right">
|
||
|
|
<span className={clsx(
|
||
|
|
"text-[10px] font-bold px-1.5 py-0.5 rounded border",
|
||
|
|
o.status === 'Received' ? "text-emerald-400 border-emerald-400/20 bg-emerald-400/5" :
|
||
|
|
o.status === 'Pending' ? "text-amber-400 border-amber-400/20 bg-amber-400/5" :
|
||
|
|
"text-slate-500 border-slate-500/20 bg-slate-500/5"
|
||
|
|
)}>
|
||
|
|
{o.status.toUpperCase()}
|
||
|
|
</span>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
))
|
||
|
|
)}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* SUPPLIER PERFORMANCE (Compact List) */}
|
||
|
|
<div className="glass-card rounded-xl border border-white/10 overflow-hidden h-full">
|
||
|
|
<div className="px-6 py-4 border-b border-white/5 bg-white/[0.01]">
|
||
|
|
<h3 className="text-sm font-bold text-white uppercase tracking-wider">Top Fornecedores</h3>
|
||
|
|
</div>
|
||
|
|
<div className="divide-y divide-white/5">
|
||
|
|
{suppliers.slice(0, 6).map((s, i) => (
|
||
|
|
<div key={s.id} className="px-6 py-3 flex items-center justify-between hover:bg-white/[0.02] transition-colors group cursor-pointer">
|
||
|
|
<div className="flex items-center gap-3">
|
||
|
|
<span className="text-xs font-mono text-slate-600 w-4">{i + 1}</span>
|
||
|
|
<div>
|
||
|
|
<p className="text-sm font-bold text-slate-200 group-hover:text-white transition-colors">{s.name}</p>
|
||
|
|
<p className="text-[10px] text-slate-500">Eletrônicos • PY</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="text-right">
|
||
|
|
<p className="text-xs font-mono text-emerald-400">+ R$ 45k</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default Dashboard;
|