AntD 6 deprecou direction em favor de orientation. 14 ocorrências em ClientsPage, NewOrderPage, RafaelPainel e SandraPainel. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
279 lines
9.6 KiB
TypeScript
279 lines
9.6 KiB
TypeScript
import { Card, Col, Flex, Progress, Row, Skeleton, Space, Tag, Typography } from 'antd';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import {
|
|
faArrowTrendUp,
|
|
faCircleExclamation,
|
|
faClipboardList,
|
|
} from '@fortawesome/free-solid-svg-icons';
|
|
import { Link } from '@tanstack/react-router';
|
|
import type { PedidoSummary } from '@sar/api-interface';
|
|
import { SITUA_LABEL } from '@sar/api-interface';
|
|
import { useRepDashboard } from '../../lib/queries/dashboard';
|
|
|
|
const { Title, Text } = Typography;
|
|
|
|
const SITUA_COLOR: Record<number, string> = {
|
|
1: 'warning',
|
|
2: 'processing',
|
|
3: 'error',
|
|
4: 'success',
|
|
};
|
|
|
|
function fmt(v: number): string {
|
|
return v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
|
|
}
|
|
|
|
function greeting(): string {
|
|
const h = new Date().getHours();
|
|
if (h < 12) return 'Bom dia';
|
|
if (h < 18) return 'Boa tarde';
|
|
return 'Boa noite';
|
|
}
|
|
|
|
function today(): string {
|
|
return new Date().toLocaleDateString('pt-BR', {
|
|
day: 'numeric',
|
|
month: 'long',
|
|
});
|
|
}
|
|
|
|
export function RafaelPainel() {
|
|
const { data, isLoading } = useRepDashboard();
|
|
|
|
if (isLoading || !data) {
|
|
return (
|
|
<Flex vertical gap={24} style={{ maxWidth: 1280, margin: '0 auto' }}>
|
|
<Skeleton active paragraph={{ rows: 2 }} />
|
|
<Row gutter={[24, 24]}>
|
|
<Col xs={24} md={12}>
|
|
<Skeleton active />
|
|
</Col>
|
|
<Col xs={12} md={6}>
|
|
<Skeleton active />
|
|
</Col>
|
|
<Col xs={12} md={6}>
|
|
<Skeleton active />
|
|
</Col>
|
|
</Row>
|
|
</Flex>
|
|
);
|
|
}
|
|
|
|
const { meta, comissao, pedidosMes, pedidosRecentes, clientesInativos, syncedAt } = data;
|
|
|
|
return (
|
|
<Flex vertical gap={24} style={{ maxWidth: 1280, margin: '0 auto' }}>
|
|
{/* Saudação */}
|
|
<Flex vertical gap={4}>
|
|
<Title level={2} style={{ margin: 0 }}>
|
|
{greeting()}, Rafael
|
|
</Title>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-lg)' }}>
|
|
{today()}
|
|
{clientesInativos.length > 0 && (
|
|
<>
|
|
{' '}
|
|
·{' '}
|
|
<span style={{ color: 'var(--orange)' }}>
|
|
{clientesInativos.length} clientes inativos
|
|
</span>
|
|
</>
|
|
)}
|
|
</Text>
|
|
</Flex>
|
|
|
|
{/* Linha 1 — Meta + KPIs */}
|
|
<Row gutter={[24, 24]}>
|
|
<Col xs={24} md={12}>
|
|
<Card style={{ height: '100%' }}>
|
|
<Flex vertical gap={16}>
|
|
<Flex justify="space-between" align="flex-start">
|
|
<Space orientation="vertical" size={0}>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-sm)' }}>
|
|
META DO MÊS
|
|
</Text>
|
|
<Title level={3} style={{ margin: 0 }} className="tabular-nums">
|
|
{fmt(meta.atingido)}
|
|
</Title>
|
|
<Text type="secondary">
|
|
de <span className="tabular-nums">{fmt(meta.total)}</span>
|
|
</Text>
|
|
</Space>
|
|
<Tag
|
|
color={meta.pct >= 100 ? 'success' : meta.pct >= 75 ? 'processing' : 'default'}
|
|
>
|
|
{meta.pct}% atingido
|
|
</Tag>
|
|
</Flex>
|
|
<Progress
|
|
percent={Math.min(meta.pct, 100)}
|
|
showInfo={false}
|
|
strokeColor="var(--jcs-blue)"
|
|
trailColor="var(--jcs-blue-light)"
|
|
/>
|
|
{meta.falta > 0 ? (
|
|
<Text style={{ fontSize: 'var(--text-md)' }}>
|
|
Faltam <strong className="tabular-nums">{fmt(meta.falta)}</strong> pra fechar o
|
|
mês.
|
|
</Text>
|
|
) : (
|
|
<Text style={{ fontSize: 'var(--text-md)', color: 'var(--green)' }}>
|
|
Meta batida! Comissão FLEX ativa.
|
|
</Text>
|
|
)}
|
|
</Flex>
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col xs={12} md={6}>
|
|
<Card>
|
|
<Space orientation="vertical" size={4}>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-sm)' }}>
|
|
PEDIDOS NO MÊS
|
|
</Text>
|
|
<Title level={3} style={{ margin: 0 }} className="tabular-nums">
|
|
{pedidosMes}
|
|
</Title>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-sm)' }}>
|
|
<FontAwesomeIcon icon={faArrowTrendUp} /> últimos 30 dias
|
|
</Text>
|
|
</Space>
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col xs={12} md={6}>
|
|
<Card>
|
|
<Space orientation="vertical" size={4}>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-sm)' }}>
|
|
COMISSÃO ACUMULADA
|
|
</Text>
|
|
<Title level={3} style={{ margin: 0 }} className="tabular-nums">
|
|
{fmt(comissao.total)}
|
|
</Title>
|
|
{comissao.flex > 0 && (
|
|
<Text type="success" style={{ fontSize: 'var(--text-sm)' }}>
|
|
FLEX: {fmt(comissao.flex)}
|
|
</Text>
|
|
)}
|
|
</Space>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
{/* Linha 2 — Clientes inativos + Pedidos recentes */}
|
|
<Row gutter={[24, 24]}>
|
|
<Col xs={24} lg={12}>
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<FontAwesomeIcon icon={faCircleExclamation} style={{ color: 'var(--orange)' }} />
|
|
Clientes esfriando
|
|
</Space>
|
|
}
|
|
extra={
|
|
clientesInativos.length > 0 ? (
|
|
<Text type="secondary">{clientesInativos.length} clientes</Text>
|
|
) : null
|
|
}
|
|
>
|
|
{clientesInativos.length === 0 ? (
|
|
<Text type="secondary">Nenhum cliente inativo. Ótimo trabalho!</Text>
|
|
) : (
|
|
<Flex vertical gap={12}>
|
|
{clientesInativos.map((c) => (
|
|
<Flex
|
|
key={c.idCliente}
|
|
justify="space-between"
|
|
align="center"
|
|
style={{
|
|
padding: 'var(--space-sm) var(--space-md)',
|
|
borderRadius: 12,
|
|
background: c.diasSemCompra > 60 ? '#fff7e6' : 'var(--bg-surface-alt)',
|
|
}}
|
|
>
|
|
<Space orientation="vertical" size={0}>
|
|
<Link to="/clientes/$id" params={{ id: String(c.idCliente) }}>
|
|
<Text strong>{c.nome}</Text>
|
|
</Link>
|
|
{c.ultimaCompraValor && (
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-xs)' }}>
|
|
Última compra:{' '}
|
|
<span className="tabular-nums">
|
|
{Number(c.ultimaCompraValor).toLocaleString('pt-BR', {
|
|
style: 'currency',
|
|
currency: 'BRL',
|
|
})}
|
|
</span>
|
|
</Text>
|
|
)}
|
|
</Space>
|
|
<Tag
|
|
color={c.diasSemCompra > 60 ? 'orange' : 'default'}
|
|
className="tabular-nums"
|
|
>
|
|
{c.diasSemCompra >= 999 ? 'nunca comprou' : `${c.diasSemCompra}d`}
|
|
</Tag>
|
|
</Flex>
|
|
))}
|
|
</Flex>
|
|
)}
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col xs={24} lg={12}>
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<FontAwesomeIcon icon={faClipboardList} style={{ color: 'var(--jcs-blue)' }} />
|
|
Pedidos recentes
|
|
</Space>
|
|
}
|
|
extra={<Link to="/pedidos">Ver todos</Link>}
|
|
>
|
|
{pedidosRecentes.length === 0 ? (
|
|
<Text type="secondary">Nenhum pedido nos últimos 7 dias.</Text>
|
|
) : (
|
|
<Flex vertical gap={10}>
|
|
{pedidosRecentes.map((o: PedidoSummary) => (
|
|
<Flex key={o.id} justify="space-between" align="center">
|
|
<Space orientation="vertical" size={0}>
|
|
<Link to="/pedidos/$id" params={{ id: o.id }}>
|
|
<Text strong className="tabular-nums">
|
|
{o.numPedSar}
|
|
</Text>
|
|
</Link>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-xs)' }}>
|
|
Cód. cliente {o.idCliente}
|
|
</Text>
|
|
</Space>
|
|
<Flex gap={8} align="center">
|
|
<Text className="tabular-nums" style={{ fontSize: 'var(--text-sm)' }}>
|
|
{Number(o.total).toLocaleString('pt-BR', {
|
|
style: 'currency',
|
|
currency: 'BRL',
|
|
})}
|
|
</Text>
|
|
<Tag color={SITUA_COLOR[o.situa] ?? 'default'}>
|
|
{SITUA_LABEL[o.situa] ?? String(o.situa)}
|
|
</Tag>
|
|
</Flex>
|
|
</Flex>
|
|
))}
|
|
</Flex>
|
|
)}
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
<Flex justify="space-between" style={{ paddingTop: 8 }}>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-xs)' }}>
|
|
SAR · Força de Vendas · Powered by JCS Sistemas
|
|
</Text>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-xs)' }}>
|
|
Sync: {new Date(syncedAt).toLocaleTimeString('pt-BR')}
|
|
</Text>
|
|
</Flex>
|
|
</Flex>
|
|
);
|
|
}
|