- Design tokens (CSS variables) espelhando brand.md v1.0:
- Paleta JCS Blue #004a99 + estados funcionais
- Plus Jakarta Sans Variable self-host (LGPD + perf)
- Radius 12/20, sombra 0 4px 25px rgba(0,0,0,0.05)
- Layout topbar 80 + sidebar 260 (brand.md canon)
- AntD ConfigProvider com tema JCS (cores, fonts, radius, shadow, motion)
- TanStack Router + Query setup com defaults conservadores
- AppShell desktop (Topbar + Sidebar) com tom canônico
- RafaelPainel placeholder com vocabulário canônico:
meta de maio, clientes esfriando (OPENFRIOS 47 dias), próxima visita,
comissão+FLEX, copy direta apple-inspired
- Logos copiadas para apps/web/public/
- Limpeza: removidos placeholders Nx (app.tsx, nx-welcome.tsx, styles.css)
- pt-BR locale (dayjs + AntD)
- Build OK: 878KB JS (vai code-splitar pós cockpits separados)
Refs: brand.md, design-artifacts/A-Product-Brief/03-visual-direction.md,
design-artifacts/B-Trigger-Map/personas/02-rafael-representante.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
205 lines
6.7 KiB
TypeScript
205 lines
6.7 KiB
TypeScript
import { Card, Col, Flex, Progress, Row, Space, Tag, Typography } from 'antd';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import {
|
|
faArrowTrendUp,
|
|
faClipboardCheck,
|
|
faCircleExclamation,
|
|
faRoute,
|
|
faMessage,
|
|
} from '@fortawesome/free-solid-svg-icons';
|
|
|
|
const { Title, Text } = Typography;
|
|
|
|
/**
|
|
* Painel do Rafael (Representante) — PRIMARY persona.
|
|
* MOCK data — substituir por TanStack Query quando API estiver pronta.
|
|
* Tom canônico: Direto · Confiante · Específico (vocabulário: meta, carteira, inativo, pedido).
|
|
*/
|
|
export function RafaelPainel() {
|
|
// Mock — em produção vem de TanStack Query
|
|
const metaMes = { atingido: 47600, total: 60000 };
|
|
const metaPct = Math.round((metaMes.atingido / metaMes.total) * 100);
|
|
const falta = metaMes.total - metaMes.atingido;
|
|
|
|
return (
|
|
<Flex vertical gap={24} style={{ maxWidth: 1280, margin: '0 auto' }}>
|
|
{/* Saudação canon (tom: Direto, Específico) */}
|
|
<Flex vertical gap={4}>
|
|
<Title level={2} style={{ margin: 0 }}>
|
|
Bom dia, Rafael
|
|
</Title>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-lg)' }}>
|
|
27 de maio · 4 visitas na agenda · 2 propostas pra avançar
|
|
</Text>
|
|
</Flex>
|
|
|
|
{/* Linha 1 — Meta + KPIs rápidos */}
|
|
<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 direction="vertical" size={0}>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-sm)' }}>
|
|
META DE MAIO
|
|
</Text>
|
|
<Title level={3} style={{ margin: 0 }} className="tabular-nums">
|
|
R$ {metaMes.atingido.toLocaleString('pt-BR')}
|
|
</Title>
|
|
<Text type="secondary">
|
|
de R${' '}
|
|
<span className="tabular-nums">
|
|
{metaMes.total.toLocaleString('pt-BR')}
|
|
</span>
|
|
</Text>
|
|
</Space>
|
|
<Tag color={metaPct >= 80 ? 'success' : 'processing'}>
|
|
{metaPct}% atingido
|
|
</Tag>
|
|
</Flex>
|
|
<Progress
|
|
percent={metaPct}
|
|
showInfo={false}
|
|
strokeColor="var(--jcs-blue)"
|
|
trailColor="var(--jcs-blue-light)"
|
|
/>
|
|
<Text style={{ fontSize: 'var(--text-md)' }}>
|
|
Faltam{' '}
|
|
<strong className="tabular-nums">
|
|
R$ {falta.toLocaleString('pt-BR')}
|
|
</strong>{' '}
|
|
pra fechar maio.
|
|
</Text>
|
|
</Flex>
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col xs={12} md={6}>
|
|
<Card>
|
|
<Space direction="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">
|
|
28
|
|
</Title>
|
|
<Text type="success" style={{ fontSize: 'var(--text-sm)' }}>
|
|
<FontAwesomeIcon icon={faArrowTrendUp} /> +18% vs abril
|
|
</Text>
|
|
</Space>
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col xs={12} md={6}>
|
|
<Card>
|
|
<Space direction="vertical" size={4}>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-sm)' }}>
|
|
COMISSÃO ACUMULADA
|
|
</Text>
|
|
<Title level={3} style={{ margin: 0 }} className="tabular-nums">
|
|
R$ 2.540
|
|
</Title>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-sm)' }}>
|
|
FLEX: R$ 380
|
|
</Text>
|
|
</Space>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
{/* Linha 2 — Alertas + Próxima visita */}
|
|
<Row gutter={[24, 24]}>
|
|
<Col xs={24} lg={12}>
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<FontAwesomeIcon
|
|
icon={faCircleExclamation}
|
|
style={{ color: 'var(--orange)' }}
|
|
/>
|
|
Clientes esfriando
|
|
</Space>
|
|
}
|
|
extra={<Text type="secondary">3 hoje</Text>}
|
|
>
|
|
<Flex vertical gap={12}>
|
|
<ClienteInativoItem nome="OPENFRIOS" dias={47} ultimaCompra="R$ 3.200" />
|
|
<ClienteInativoItem nome="DISTRIBUIDORA NORTE" dias={62} ultimaCompra="R$ 1.880" />
|
|
<ClienteInativoItem nome="MERCADO SÃO PAULO" dias={71} ultimaCompra="R$ 980" />
|
|
</Flex>
|
|
</Card>
|
|
</Col>
|
|
|
|
<Col xs={24} lg={12}>
|
|
<Card
|
|
title={
|
|
<Space>
|
|
<FontAwesomeIcon icon={faRoute} style={{ color: 'var(--jcs-blue)' }} />
|
|
Próxima visita
|
|
</Space>
|
|
}
|
|
>
|
|
<Flex vertical gap={12}>
|
|
<Space direction="vertical" size={4}>
|
|
<Title level={4} style={{ margin: 0, color: 'var(--jcs-blue)' }}>
|
|
OPENFRIOS
|
|
</Title>
|
|
<Text type="secondary">
|
|
Rua das Indústrias, 1.245 · São Paulo, SP · 14:30
|
|
</Text>
|
|
</Space>
|
|
<Flex gap={12} wrap="wrap">
|
|
<Tag icon={<FontAwesomeIcon icon={faClipboardCheck} />} color="processing">
|
|
3 pedidos em andamento
|
|
</Tag>
|
|
<Tag icon={<FontAwesomeIcon icon={faMessage} />} color="success">
|
|
WhatsApp atualizado
|
|
</Tag>
|
|
</Flex>
|
|
</Flex>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
{/* Footer informativo (sem ruído — tom Apple clean) */}
|
|
<Flex justify="center" style={{ paddingTop: 16 }}>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-xs)' }}>
|
|
SAR · Força de Vendas · Powered by JCS Sistemas
|
|
</Text>
|
|
</Flex>
|
|
</Flex>
|
|
);
|
|
}
|
|
|
|
function ClienteInativoItem({
|
|
nome,
|
|
dias,
|
|
ultimaCompra,
|
|
}: {
|
|
nome: string;
|
|
dias: number;
|
|
ultimaCompra: string;
|
|
}) {
|
|
return (
|
|
<Flex
|
|
justify="space-between"
|
|
align="center"
|
|
style={{
|
|
padding: 'var(--space-sm) var(--space-md)',
|
|
borderRadius: 12,
|
|
background: 'var(--bg-surface-alt)',
|
|
}}
|
|
>
|
|
<Space direction="vertical" size={0}>
|
|
<Text strong>{nome}</Text>
|
|
<Text type="secondary" style={{ fontSize: 'var(--text-xs)' }}>
|
|
Última compra: <span className="tabular-nums">{ultimaCompra}</span>
|
|
</Text>
|
|
</Space>
|
|
<Tag color="warning" className="tabular-nums">
|
|
{dias} dias
|
|
</Tag>
|
|
</Flex>
|
|
);
|
|
}
|