Files
sar/apps/web/src/cockpits/rafael/RafaelPainel.tsx
julian 3a42723c71 feat(web): foundation com brand JCS + AntD theme + Rafael painel placeholder
- 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>
2026-05-27 17:12:34 +00:00

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>
);
}