feat(api,web): detalhamento da carteira do representante

Tela dedicada que abre pelo botao "Detalhar carteira" na listagem de
clientes, com leitura acionavel da carteira em vez de so uma tabela.

- `/reports/carteira` passa a devolver `faturamentoTotal`,
  `participacaoPct` e `faturamentoRepTotal`, permitindo medir o peso de
  cada cliente no faturamento do rep.
- `CarteirePage`: cards de resumo (ativos / em risco / inativos / sem
  pedido), faturamento total e InsightCards que apontam concentracao em
  poucos clientes, maior cliente em risco, maior inativo e clientes nunca
  positivados.
- Clientes classificados por dias sem comprar (30d em risco, 60d inativo),
  com navegacao direta para a ficha.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 18:16:47 +00:00
parent 2649bc9e94
commit 51602dd47e
5 changed files with 485 additions and 36 deletions

View File

@@ -186,7 +186,13 @@ function CustomerMetrics({ stats }: { stats: PortfolioStats }) {
// ─── CustomerPortfolioCard ────────────────────────────────────────────────────
function CustomerPortfolioCard({ stats }: { stats: PortfolioStats }) {
function CustomerPortfolioCard({
stats,
onDetalhar,
}: {
stats: PortfolioStats;
onDetalhar: () => void;
}) {
const mesAtual = new Date().toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' });
const total = stats.ativos + stats.emAlerta + stats.inativos;
@@ -313,6 +319,7 @@ function CustomerPortfolioCard({ stats }: { stats: PortfolioStats }) {
<Divider style={{ margin: '12px 0' }} />
<Button
block
onClick={onDetalhar}
style={{ borderRadius: 8, fontWeight: 600, color: '#003B8E', borderColor: '#003B8E' }}
>
Detalhar carteira
@@ -500,7 +507,7 @@ function CustomerDetailsModal({
</Space>
}
>
<Space direction="vertical" size={20} style={{ width: '100%' }}>
<Space orientation="vertical" size={20} style={{ width: '100%' }}>
{/* Identificação */}
<Card
style={{ borderRadius: 8, background: '#F8FAFC', border: '1px solid #EBF0F5' }}
@@ -544,7 +551,7 @@ function CustomerDetailsModal({
>
Dados Cadastrais
</Text>
<Space direction="vertical" size={10} style={{ width: '100%' }}>
<Space orientation="vertical" size={10} style={{ width: '100%' }}>
<div>
<span style={label}>Telefone</span>
<Space size={4}>
@@ -596,7 +603,7 @@ function CustomerDetailsModal({
>
Dados Comerciais
</Text>
<Space direction="vertical" size={10} style={{ width: '100%' }}>
<Space orientation="vertical" size={10} style={{ width: '100%' }}>
{limiteFormatado !== '—' && (
<div>
<span style={label}>Limite de Crédito</span>
@@ -657,7 +664,7 @@ function CustomerDetailsModal({
{loadingOrders ? (
<Spin size="small" />
) : (
<Space direction="vertical" size={6} style={{ width: '100%' }}>
<Space orientation="vertical" size={6} style={{ width: '100%' }}>
{orders.slice(0, 5).map((p) => (
<div
key={p.id}
@@ -811,7 +818,7 @@ function CustomerAnalysisModal({
</Space>
}
>
<Space direction="vertical" size={20} style={{ width: '100%' }}>
<Space orientation="vertical" size={20} style={{ width: '100%' }}>
<div>
<Space size={8}>
<CustomerStatusBadge status={summary.activityStatus} />
@@ -1456,7 +1463,10 @@ export function ClientsPage() {
{/* ── Portfolio mobile (antes da lista) ─────────────────────────── */}
{isMobile && (
<>
<CustomerPortfolioCard stats={stats} />
<CustomerPortfolioCard
stats={stats}
onDetalhar={() => navigate({ to: '/clientes/carteira' })}
/>
<div style={{ marginBottom: 16 }} />
</>
)}
@@ -1539,7 +1549,10 @@ export function ClientsPage() {
{!isMobile && (
<Col lg={7}>
<CustomerPortfolioCard stats={stats} />
<CustomerPortfolioCard
stats={stats}
onDetalhar={() => navigate({ to: '/clientes/carteira' })}
/>
</Col>
)}
</Row>