- GET /api/v1/auth/me retorna perfil real do ERP (vw_representantes) - Contrato UserProfile adicionado ao shared api-interface - Hook useCurrentUser() no frontend consome o endpoint - Cockpit rafael → rep, sandra → supervisor (pastas e componentes) - Topbar exibe iniciais do usuário e dropdown com nome, role e "Sair" - Logout limpa token e recarrega para voltar ao DevLogin Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
138 lines
4.5 KiB
TypeScript
138 lines
4.5 KiB
TypeScript
import { Button, Descriptions, Tag, Table, Typography, Spin, Alert, Space, Divider } from 'antd';
|
|
import type { TableColumnsType } from 'antd';
|
|
import { Link, useNavigate, useParams } from '@tanstack/react-router';
|
|
import type { PedidoSummary } from '@sar/api-interface';
|
|
import { SITUA_LABEL } from '@sar/api-interface';
|
|
import { useClientDetail } from '../../lib/queries/clients';
|
|
import { useClientOrders } from '../../lib/queries/orders';
|
|
|
|
const { Title } = Typography;
|
|
|
|
const ACTIVITY_COLOR: Record<string, string> = {
|
|
active: 'success',
|
|
alert: 'warning',
|
|
inactive: 'default',
|
|
};
|
|
const ACTIVITY_LABEL: Record<string, string> = {
|
|
active: 'Ativo',
|
|
alert: 'Alerta',
|
|
inactive: 'Inativo',
|
|
};
|
|
|
|
const orderColumns: TableColumnsType<PedidoSummary> = [
|
|
{
|
|
title: 'Nº',
|
|
dataIndex: 'numPedSar',
|
|
width: 120,
|
|
render: (num: string, row: PedidoSummary) => (
|
|
<Link to="/pedidos/$id" params={{ id: row.id }}>
|
|
{num}
|
|
</Link>
|
|
),
|
|
},
|
|
{
|
|
title: 'Status',
|
|
dataIndex: 'situa',
|
|
width: 140,
|
|
render: (s: number) => {
|
|
const colorMap: Record<number, string> = {
|
|
1: 'warning',
|
|
2: 'processing',
|
|
3: 'error',
|
|
4: 'success',
|
|
};
|
|
return <Tag color={colorMap[s] ?? 'default'}>{SITUA_LABEL[s] ?? String(s)}</Tag>;
|
|
},
|
|
},
|
|
{
|
|
title: 'Total',
|
|
dataIndex: 'total',
|
|
width: 130,
|
|
align: 'right',
|
|
render: (v: string) =>
|
|
Number(v).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }),
|
|
},
|
|
{
|
|
title: 'Data',
|
|
dataIndex: 'dtPedido',
|
|
width: 130,
|
|
render: (v: string) => new Date(v).toLocaleDateString('pt-BR'),
|
|
},
|
|
];
|
|
|
|
export function ClientDetailPage() {
|
|
const { id } = useParams({ from: '/clientes/$id' });
|
|
const idNum = Number(id);
|
|
const navigate = useNavigate();
|
|
const { data: client, isLoading: clientLoading, error: clientError } = useClientDetail(idNum);
|
|
const { data: orders, isLoading: ordersLoading } = useClientOrders(idNum);
|
|
|
|
if (clientLoading) return <Spin style={{ display: 'block', marginTop: 64 }} />;
|
|
if (clientError || !client)
|
|
return <Alert type="error" message="Cliente não encontrado." style={{ margin: 24 }} />;
|
|
|
|
return (
|
|
<div style={{ padding: 24 }}>
|
|
<Space align="center" style={{ marginBottom: 16 }} wrap>
|
|
<Link to="/clientes">← Clientes</Link>
|
|
<Title level={3} style={{ margin: 0 }}>
|
|
{client.razao ?? client.nome}
|
|
</Title>
|
|
<Tag color={ACTIVITY_COLOR[client.activityStatus]}>
|
|
{ACTIVITY_LABEL[client.activityStatus]}
|
|
</Tag>
|
|
<Button
|
|
type="primary"
|
|
onClick={() => void navigate({ to: '/pedidos/novo', search: { clientId: id } })}
|
|
>
|
|
Novo Pedido
|
|
</Button>
|
|
</Space>
|
|
|
|
<Descriptions bordered size="small" column={2} style={{ marginBottom: 24 }}>
|
|
<Descriptions.Item label="Razão Social">{client.nome}</Descriptions.Item>
|
|
<Descriptions.Item label="CNPJ / CPF">{client.cgcpf ?? '—'}</Descriptions.Item>
|
|
<Descriptions.Item label="E-mail">{client.email ?? '—'}</Descriptions.Item>
|
|
<Descriptions.Item label="Telefone">
|
|
{client.ddd ? `(${client.ddd}) ` : ''}
|
|
{client.telefone ?? '—'}
|
|
</Descriptions.Item>
|
|
{client.endereco && (
|
|
<Descriptions.Item label="Endereço" span={2}>
|
|
{client.endereco}
|
|
{client.numEndereco ? `, ${client.numEndereco}` : ''}
|
|
{client.bairro ? ` — ${client.bairro}` : ''}
|
|
{client.cep ? ` — CEP ${client.cep}` : ''}
|
|
</Descriptions.Item>
|
|
)}
|
|
<Descriptions.Item label="Limite de Crédito">
|
|
{client.limiteCreditoStr
|
|
? Number(client.limiteCreditoStr).toLocaleString('pt-BR', {
|
|
style: 'currency',
|
|
currency: 'BRL',
|
|
})
|
|
: '—'}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="Última Compra">
|
|
{client.dtUltimaCompra
|
|
? new Date(client.dtUltimaCompra).toLocaleDateString('pt-BR')
|
|
: '—'}
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
|
|
<Divider orientation="left">Últimos Pedidos</Divider>
|
|
|
|
<Table<PedidoSummary>
|
|
rowKey="id"
|
|
columns={orderColumns}
|
|
dataSource={orders ?? []}
|
|
loading={ordersLoading}
|
|
pagination={false}
|
|
size="small"
|
|
rowClassName={(row) => (row.situa === 1 ? 'row-pending' : '')}
|
|
/>
|
|
<style>{`.row-pending td { background: #fffbe6 !important; }`}</style>
|
|
</div>
|
|
);
|
|
}
|