Remove dashboard operational copilot

This commit is contained in:
Cauê Faleiros
2026-07-13 11:16:07 -03:00
parent d3ea2048cb
commit 1bf5e518de

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useOutletContext, useNavigate } from 'react-router-dom'; import { useOutletContext, useNavigate } from 'react-router-dom';
import { AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'; import { AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts';
import { AlertTriangle, Bot, ClipboardList, DollarSign, Package, ShoppingCart, TrendingUp } from 'lucide-react'; import { DollarSign, ShoppingCart, TrendingUp } from 'lucide-react';
import DateRangePicker from '../components/DateRangePicker'; import DateRangePicker from '../components/DateRangePicker';
import RefreshStatus from '../components/RefreshStatus'; import RefreshStatus from '../components/RefreshStatus';
import type { DashboardAnalytics, OrderData, DateRange } from '../types'; import type { DashboardAnalytics, OrderData, DateRange } from '../types';
@@ -161,15 +161,6 @@ const kpiIconStyle = (color: string) => ({
color color
}); });
type OperationalInsight = {
title: string;
description: string;
icon: typeof Bot;
accentClassName: string;
actionLabel: string;
actionTo: string;
};
const DashboardSkeleton = () => ( const DashboardSkeleton = () => (
<div className="space-y-6" aria-label="Carregando dashboard"> <div className="space-y-6" aria-label="Carregando dashboard">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
@@ -511,60 +502,6 @@ const Dashboard = () => {
const focusedSeller = focusedSellerId ? sellerTimeSeries.sellers.find(seller => seller.id === focusedSellerId) : null; const focusedSeller = focusedSellerId ? sellerTimeSeries.sellers.find(seller => seller.id === focusedSellerId) : null;
const effectiveFocusedSellerId = focusedSeller?.id || null; const effectiveFocusedSellerId = focusedSeller?.id || null;
const operationalInsights = useMemo<OperationalInsight[]>(() => {
const insights: OperationalInsight[] = [];
const topProduct = salesByProduct[0];
const topRevenueProduct = revenueByProduct[0];
const topSeller = chartRevenueBySeller[0];
const soldUnits = salesByProduct.reduce((total, product) => total + product.value, 0);
const topProductShare = topProduct && soldUnits ? topProduct.value / soldUnits : 0;
if (topProduct && topProductShare >= 0.35) {
insights.push({
title: `${topProduct.name} concentra ${Math.round(topProductShare * 100)}% das unidades vendidas`,
description: 'Priorize a conferência de estoque e corte para evitar ruptura no item mais sensível do período.',
icon: AlertTriangle,
accentClassName: 'border-amber-400/30 bg-amber-400/10 text-amber-300',
actionLabel: 'Abrir corte',
actionTo: '/cutting'
});
}
if (topRevenueProduct && topRevenueProduct.value > 0) {
insights.push({
title: `${topRevenueProduct.name} lidera a receita do período`,
description: `${formatCurrency(topRevenueProduct.value)} em receita. Compare disponibilidade e necessidade de compra antes de vender sem cobertura.`,
icon: Package,
accentClassName: 'border-sky-400/30 bg-sky-400/10 text-sky-300',
actionLabel: 'Ver suprimentos',
actionTo: '/supplies'
});
}
if (topSeller && topSeller.value > 0) {
insights.push({
title: `${topSeller.name} puxa a maior receita por vendedor`,
description: `${formatCurrency(topSeller.value)} no período selecionado. Use o gráfico abaixo para comparar ritmo e concentração.`,
icon: TrendingUp,
accentClassName: 'border-emerald-400/30 bg-emerald-400/10 text-emerald-300',
actionLabel: 'Analisar vendas',
actionTo: '/graph'
});
}
if (totalOrders > 0 && averageOrderValue > 0) {
insights.push({
title: `Ticket médio por item em ${formatCurrency(averageOrderValue)}`,
description: `${new Intl.NumberFormat('pt-BR').format(totalOrders)} unidades vendidas no período. Revise mix de produtos e abastecimento dos campeões.`,
icon: ClipboardList,
accentClassName: 'border-brand-primary/30 bg-brand-primary/10 text-brand-primary',
actionLabel: 'Ver produtos',
actionTo: '/products'
});
}
return insights.slice(0, 4);
}, [averageOrderValue, chartRevenueBySeller, revenueByProduct, salesByProduct, totalOrders]);
const handleManualRefresh = () => { const handleManualRefresh = () => {
void loadDashboardMetrics(dateRange, { force: true }); void loadDashboardMetrics(dateRange, { force: true });
@@ -594,63 +531,6 @@ const Dashboard = () => {
<DashboardSkeleton /> <DashboardSkeleton />
) : ( ) : (
<div className={isRefreshing ? 'refreshing-content space-y-6' : 'space-y-6'} aria-busy={isRefreshing}> <div className={isRefreshing ? 'refreshing-content space-y-6' : 'space-y-6'} aria-busy={isRefreshing}>
<div className="overflow-hidden rounded-2xl border border-brand-primary/20 bg-dark-card shadow-sm">
<div className="border-b border-dark-border p-5">
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div className="flex items-start gap-3">
<div className="rounded-xl border border-brand-primary/25 bg-brand-primary/10 p-3 text-brand-primary">
<Bot className="h-5 w-5" />
</div>
<div>
<h2 className="text-lg font-bold text-dark-text">Copiloto operacional</h2>
<p className="mt-1 max-w-3xl text-sm font-semibold text-dark-muted">
Leituras automáticas do período selecionado para orientar vendas, estoque e produção.
</p>
</div>
</div>
<button
type="button"
onClick={handleManualRefresh}
className="inline-flex h-10 items-center justify-center rounded-xl border border-dark-border bg-dark-input px-4 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary cursor-pointer"
>
Recalcular
</button>
</div>
</div>
{operationalInsights.length ? (
<div className="grid grid-cols-1 gap-3 p-5 xl:grid-cols-2">
{operationalInsights.map((insight) => (
<div key={insight.title} className="rounded-xl border border-dark-border bg-dark-input/35 p-4">
<div className="flex items-start gap-3">
<div className={`rounded-lg border p-2 ${insight.accentClassName}`}>
<insight.icon className="h-4 w-4" />
</div>
<div className="min-w-0 flex-1">
<h3 className="text-sm font-bold text-dark-text">{insight.title}</h3>
<p className="mt-1 text-sm font-semibold text-dark-muted">{insight.description}</p>
<button
type="button"
onClick={() => navigate(insight.actionTo)}
className="mt-3 inline-flex h-8 items-center rounded-lg border border-dark-border bg-dark-card px-3 text-xs font-bold text-dark-text transition-colors hover:border-brand-primary cursor-pointer"
>
{insight.actionLabel}
</button>
</div>
</div>
</div>
))}
</div>
) : (
<div className="p-5">
<div className="rounded-xl border border-dark-border bg-dark-input/35 p-4 text-sm font-semibold text-dark-muted">
Sem sinais operacionais suficientes no período selecionado.
</div>
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-dark-card p-6 rounded-2xl border border-dark-border shadow-sm"> <div className="bg-dark-card p-6 rounded-2xl border border-dark-border shadow-sm">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">