feat(web+api): catálogo com detalhe do produto, preços por pauta e consulta de pedidos ERP
- Catálogo: modal de detalhe centralizado (1100px) com identificação, estoque, preços base e lista de preços por pauta do representante (DISTINCT para evitar duplicatas) - Contrato: loteMulVenda promovido para ProdutoSummarySchema; PautaPrecoSchema adicionado - Pedido novo: valida lote múltiplo de venda — qtd inicial = lote, step = lote, InputNumber em erro quando inválido, alerta e bloqueio do submit - Consulta ERP: tela OrdersErpPage com listagem de pedidos do ERP (P/E), contrato PedidoErpConsulta, endpoint no orders.controller, rota e sidebar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Drawer,
|
||||
Modal,
|
||||
Dropdown,
|
||||
Grid,
|
||||
Row,
|
||||
@@ -397,9 +397,9 @@ function CustomerExpandedDetail({ summary }: { summary: ClientSummary }) {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── CustomerDetailsDrawer ────────────────────────────────────────────────────
|
||||
// ─── CustomerDetailsModal ────────────────────────────────────────────────────
|
||||
|
||||
function CustomerDetailsDrawer({
|
||||
function CustomerDetailsModal({
|
||||
summary,
|
||||
onClose,
|
||||
onAnalyze,
|
||||
@@ -439,7 +439,7 @@ function CustomerDetailsDrawer({
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<Text strong style={{ fontSize: 16 }}>
|
||||
@@ -448,14 +448,29 @@ function CustomerDetailsDrawer({
|
||||
<CustomerStatusBadge status={summary.activityStatus} />
|
||||
</Space>
|
||||
}
|
||||
open
|
||||
onClose={onClose}
|
||||
width={520}
|
||||
placement="right"
|
||||
styles={{ body: { padding: '16px 24px' } }}
|
||||
open={!!summary}
|
||||
onCancel={onClose}
|
||||
centered
|
||||
width={860}
|
||||
destroyOnHidden
|
||||
styles={{
|
||||
body: { padding: '20px 24px', maxHeight: 'calc(85vh - 110px)', overflowY: 'auto' },
|
||||
}}
|
||||
footer={
|
||||
<Space wrap>
|
||||
<Button onClick={onClose}>Fechar</Button>
|
||||
<Button
|
||||
icon={<WhatsAppOutlined />}
|
||||
style={{
|
||||
background: '#25D366',
|
||||
borderColor: '#25D366',
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
onClick={() => window.open(`https://wa.me/55${phone.replace(/\D/g, '')}`, '_blank')}
|
||||
>
|
||||
WhatsApp
|
||||
</Button>
|
||||
<Button icon={<BarChartOutlined />} onClick={onAnalyze}>
|
||||
Analisar
|
||||
</Button>
|
||||
@@ -483,135 +498,141 @@ function CustomerDetailsDrawer({
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Space orientation="vertical" size={20} style={{ width: '100%' }}>
|
||||
<Space direction="vertical" size={20} style={{ width: '100%' }}>
|
||||
{/* Identificação */}
|
||||
<Card
|
||||
style={{ borderRadius: 8, background: '#F8FAFC', border: '1px solid #EBF0F5' }}
|
||||
styles={{ body: { padding: '14px 16px' } }}
|
||||
>
|
||||
<Text strong style={{ fontSize: 18, color: '#1F2937', display: 'block' }}>
|
||||
{summary.nome}
|
||||
</Text>
|
||||
{summary.razao && (
|
||||
<Text style={{ fontSize: 13, color: '#64748B', display: 'block', marginTop: 2 }}>
|
||||
{summary.razao}
|
||||
</Text>
|
||||
)}
|
||||
{summary.cgcpf && (
|
||||
<Text style={{ fontSize: 13, color: '#94A3B8', fontFamily: 'monospace' }}>
|
||||
{summary.cgcpf}
|
||||
</Text>
|
||||
)}
|
||||
<Row gutter={[16, 8]}>
|
||||
<Col span={16}>
|
||||
<Text strong style={{ fontSize: 18, color: '#1F2937', display: 'block' }}>
|
||||
{summary.nome}
|
||||
</Text>
|
||||
{summary.razao && (
|
||||
<Text style={{ fontSize: 13, color: '#64748B', display: 'block', marginTop: 2 }}>
|
||||
{summary.razao}
|
||||
</Text>
|
||||
)}
|
||||
{summary.cgcpf && (
|
||||
<Text style={{ fontSize: 13, color: '#94A3B8', fontFamily: 'monospace' }}>
|
||||
{summary.cgcpf}
|
||||
</Text>
|
||||
)}
|
||||
</Col>
|
||||
<Col span={8} style={{ textAlign: 'right' }}>
|
||||
<CustomerStatusBadge status={summary.activityStatus} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* Dados cadastrais */}
|
||||
<div>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.07em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#64748B',
|
||||
display: 'block',
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
Dados Cadastrais
|
||||
</Text>
|
||||
<Row gutter={[12, 12]}>
|
||||
<Col span={12}>
|
||||
<span style={label}>Telefone</span>
|
||||
<Space size={4}>
|
||||
<PhoneOutlined style={{ color: '#94A3B8', fontSize: 12 }} />
|
||||
<Text style={{ fontSize: 13 }}>{phone}</Text>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<span style={label}>E-mail</span>
|
||||
<Space size={4}>
|
||||
<MailOutlined style={{ color: '#94A3B8', fontSize: 12 }} />
|
||||
<Text style={{ fontSize: 13, wordBreak: 'break-all' }}>{summary.email ?? '—'}</Text>
|
||||
</Space>
|
||||
</Col>
|
||||
{loadingDetail ? (
|
||||
<Col span={24}>
|
||||
<Spin size="small" />
|
||||
</Col>
|
||||
) : (
|
||||
<>
|
||||
{endereco !== '—' && (
|
||||
<Col span={24}>
|
||||
<span style={label}>Endereço</span>
|
||||
<Text style={{ fontSize: 13 }}>{endereco}</Text>
|
||||
</Col>
|
||||
)}
|
||||
{d?.inscricaoEstadual && (
|
||||
<Col span={12}>
|
||||
<span style={label}>Insc. Estadual</span>
|
||||
<Text style={{ fontSize: 13 }}>{d.inscricaoEstadual}</Text>
|
||||
</Col>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: '4px 0' }} />
|
||||
|
||||
{/* Dados comerciais */}
|
||||
<div>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.07em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#64748B',
|
||||
display: 'block',
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
Dados Comerciais
|
||||
</Text>
|
||||
<Row gutter={[12, 12]}>
|
||||
{limiteFormatado !== '—' && (
|
||||
<Col span={12}>
|
||||
<span style={label}>Limite de Crédito</span>
|
||||
<Text strong style={{ fontSize: 13, color: '#003B8E' }}>
|
||||
{limiteFormatado}
|
||||
</Text>
|
||||
</Col>
|
||||
)}
|
||||
<Col span={12}>
|
||||
<span style={label}>Representante</span>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
{summary.nomeVendedor ?? `Cód. ${summary.codVendedor}`}
|
||||
{summary.nomeVendedor && (
|
||||
<Text type="secondary" style={{ fontSize: 11, marginLeft: 4 }}>
|
||||
(cód. {summary.codVendedor})
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Col>
|
||||
{summary.dtUltimaCompra && (
|
||||
<Col span={12}>
|
||||
<span style={label}>Última Compra</span>
|
||||
{/* Dados cadastrais + comerciais em 2 colunas */}
|
||||
<Row gutter={[24, 16]}>
|
||||
<Col span={12}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.07em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#64748B',
|
||||
display: 'block',
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
Dados Cadastrais
|
||||
</Text>
|
||||
<Space direction="vertical" size={10} style={{ width: '100%' }}>
|
||||
<div>
|
||||
<span style={label}>Telefone</span>
|
||||
<Space size={4}>
|
||||
<CalendarOutlined style={{ color: '#94A3B8', fontSize: 12 }} />
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
{fmtDate(summary.dtUltimaCompra)}
|
||||
{dias !== null && (
|
||||
<Text style={{ fontSize: 11, color: '#94A3B8', marginLeft: 4 }}>
|
||||
({dias} dias)
|
||||
</Text>
|
||||
)}
|
||||
<PhoneOutlined style={{ color: '#94A3B8', fontSize: 12 }} />
|
||||
<Text style={{ fontSize: 13 }}>{phone}</Text>
|
||||
</Space>
|
||||
</div>
|
||||
<div>
|
||||
<span style={label}>E-mail</span>
|
||||
<Space size={4}>
|
||||
<MailOutlined style={{ color: '#94A3B8', fontSize: 12 }} />
|
||||
<Text style={{ fontSize: 13, wordBreak: 'break-all' }}>
|
||||
{summary.email ?? '—'}
|
||||
</Text>
|
||||
</Space>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
{loadingDetail ? (
|
||||
<Spin size="small" />
|
||||
) : (
|
||||
<>
|
||||
{endereco !== '—' && (
|
||||
<div>
|
||||
<span style={label}>Endereço</span>
|
||||
<Text style={{ fontSize: 13 }}>{endereco}</Text>
|
||||
</div>
|
||||
)}
|
||||
{d?.inscricaoEstadual && (
|
||||
<div>
|
||||
<span style={label}>Insc. Estadual</span>
|
||||
<Text style={{ fontSize: 13 }}>{d.inscricaoEstadual}</Text>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.07em',
|
||||
textTransform: 'uppercase',
|
||||
color: '#64748B',
|
||||
display: 'block',
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
Dados Comerciais
|
||||
</Text>
|
||||
<Space direction="vertical" size={10} style={{ width: '100%' }}>
|
||||
{limiteFormatado !== '—' && (
|
||||
<div>
|
||||
<span style={label}>Limite de Crédito</span>
|
||||
<Text strong style={{ fontSize: 13, color: '#003B8E' }}>
|
||||
{limiteFormatado}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<span style={label}>Representante</span>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
{summary.nomeVendedor ?? `Cód. ${summary.codVendedor}`}
|
||||
{summary.nomeVendedor && (
|
||||
<Text type="secondary" style={{ fontSize: 11, marginLeft: 4 }}>
|
||||
(cód. {summary.codVendedor})
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
{summary.dtUltimaCompra && (
|
||||
<div>
|
||||
<span style={label}>Última Compra</span>
|
||||
<Space size={4}>
|
||||
<CalendarOutlined style={{ color: '#94A3B8', fontSize: 12 }} />
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
{fmtDate(summary.dtUltimaCompra)}
|
||||
{dias !== null && (
|
||||
<Text style={{ fontSize: 11, color: '#94A3B8', marginLeft: 4 }}>
|
||||
({dias} dias)
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Últimos pedidos */}
|
||||
{(loadingOrders || orders.length > 0) && (
|
||||
@@ -634,7 +655,7 @@ function CustomerDetailsDrawer({
|
||||
{loadingOrders ? (
|
||||
<Spin size="small" />
|
||||
) : (
|
||||
<Space orientation="vertical" size={6} style={{ width: '100%' }}>
|
||||
<Space direction="vertical" size={6} style={{ width: '100%' }}>
|
||||
{orders.slice(0, 5).map((p) => (
|
||||
<div
|
||||
key={p.id}
|
||||
@@ -706,29 +727,14 @@ function CustomerDetailsDrawer({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
block
|
||||
icon={<WhatsAppOutlined />}
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
background: '#25D366',
|
||||
borderColor: '#25D366',
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
}}
|
||||
onClick={() => window.open(`https://wa.me/55${phone.replace(/\D/g, '')}`, '_blank')}
|
||||
>
|
||||
Enviar WhatsApp
|
||||
</Button>
|
||||
</Space>
|
||||
</Drawer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── CustomerAnalysisDrawer ───────────────────────────────────────────────────
|
||||
// ─── CustomerAnalysisModal ───────────────────────────────────────────────────
|
||||
|
||||
function CustomerAnalysisDrawer({
|
||||
function CustomerAnalysisModal({
|
||||
summary,
|
||||
onClose,
|
||||
}: {
|
||||
@@ -755,25 +761,55 @@ function CustomerAnalysisDrawer({
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<BarChartOutlined />
|
||||
<Text strong>Análise Comercial</Text>
|
||||
<Text strong>Análise Comercial — {summary.nome}</Text>
|
||||
</Space>
|
||||
}
|
||||
open={!!summary}
|
||||
onCancel={onClose}
|
||||
centered
|
||||
width={720}
|
||||
destroyOnHidden
|
||||
styles={{ body: { padding: '20px 24px' } }}
|
||||
footer={
|
||||
<Space>
|
||||
<Button
|
||||
icon={<WhatsAppOutlined />}
|
||||
style={{
|
||||
background: '#25D366',
|
||||
borderColor: '#25D366',
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://wa.me/55${(summary.telefone ?? '').replace(/\D/g, '')}`,
|
||||
'_blank',
|
||||
)
|
||||
}
|
||||
>
|
||||
WhatsApp
|
||||
</Button>
|
||||
<Button
|
||||
icon={<MailOutlined />}
|
||||
style={{ borderRadius: 8 }}
|
||||
onClick={() => window.open(`mailto:${summary.email ?? ''}`, '_blank')}
|
||||
>
|
||||
E-mail
|
||||
</Button>
|
||||
<Button onClick={onClose} style={{ borderRadius: 8 }}>
|
||||
Fechar
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
open
|
||||
onClose={onClose}
|
||||
width={480}
|
||||
placement="right"
|
||||
styles={{ body: { padding: '16px 24px' } }}
|
||||
>
|
||||
<Space orientation="vertical" size={20} style={{ width: '100%' }}>
|
||||
<Space direction="vertical" size={20} style={{ width: '100%' }}>
|
||||
<div>
|
||||
<Text strong style={{ fontSize: 17, color: '#1F2937', display: 'block' }}>
|
||||
{summary.nome}
|
||||
</Text>
|
||||
<Space size={8} style={{ marginTop: 4 }}>
|
||||
<Space size={8}>
|
||||
<CustomerStatusBadge status={summary.activityStatus} />
|
||||
{summary.cgcpf && (
|
||||
<Text style={{ fontSize: 12, color: '#64748B' }}>{summary.cgcpf}</Text>
|
||||
@@ -875,39 +911,8 @@ function CustomerAnalysisDrawer({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Space>
|
||||
<Button
|
||||
icon={<WhatsAppOutlined />}
|
||||
style={{
|
||||
background: '#25D366',
|
||||
borderColor: '#25D366',
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://wa.me/55${(summary.telefone ?? '').replace(/\D/g, '')}`,
|
||||
'_blank',
|
||||
)
|
||||
}
|
||||
>
|
||||
WhatsApp
|
||||
</Button>
|
||||
<Button
|
||||
icon={<MailOutlined />}
|
||||
style={{ borderRadius: 8 }}
|
||||
onClick={() => window.open(`mailto:${summary.email ?? ''}`, '_blank')}
|
||||
>
|
||||
E-mail
|
||||
</Button>
|
||||
<Button onClick={onClose} style={{ borderRadius: 8 }}>
|
||||
Fechar
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</Drawer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1535,9 +1540,9 @@ export function ClientsPage() {
|
||||
)}
|
||||
</Row>
|
||||
|
||||
{/* ── Drawers ───────────────────────────────────────────────────── */}
|
||||
{/* ── Modais ────────────────────────────────────────────────────── */}
|
||||
{detailSummary && (
|
||||
<CustomerDetailsDrawer
|
||||
<CustomerDetailsModal
|
||||
summary={detailSummary}
|
||||
onClose={() => setDetailSummary(null)}
|
||||
onAnalyze={() => {
|
||||
@@ -1547,10 +1552,7 @@ export function ClientsPage() {
|
||||
/>
|
||||
)}
|
||||
{analysisSummary && (
|
||||
<CustomerAnalysisDrawer
|
||||
summary={analysisSummary}
|
||||
onClose={() => setAnalysisSummary(null)}
|
||||
/>
|
||||
<CustomerAnalysisModal summary={analysisSummary} onClose={() => setAnalysisSummary(null)} />
|
||||
)}
|
||||
|
||||
{/* FAB mobile */}
|
||||
|
||||
Reference in New Issue
Block a user