feat(web): clientes e catálogo funcionando com dados do ERP
- clients.ts, catalog.ts: corrige bug res.ok/res.json() — apiFetch retorna JSON direto - catalog.service.ts: corrige nomes de coluna da vw_produtos (descr_det, lista_pauta, remove preco_com_ipi inexistente) - CatalogPage.tsx: nova tela — código, descrição, grupo, marca, preço, estoque - router.tsx: adiciona rota /catalogo Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
113
apps/web/src/cockpits/rafael/CatalogPage.tsx
Normal file
113
apps/web/src/cockpits/rafael/CatalogPage.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { useState } from 'react';
|
||||
import { Table, Input, Typography, Tag } from 'antd';
|
||||
import type { TableColumnsType } from 'antd';
|
||||
import type { ProdutoSummary } from '@sar/api-interface';
|
||||
import { useCatalog } from '../../lib/queries/catalog';
|
||||
|
||||
const { Title } = Typography;
|
||||
const { Search } = Input;
|
||||
|
||||
function fmtPrice(v: string | null | undefined): string {
|
||||
const n = Number(v ?? 0);
|
||||
return n > 0 ? n.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }) : '—';
|
||||
}
|
||||
|
||||
const columns: TableColumnsType<ProdutoSummary> = [
|
||||
{
|
||||
title: 'Código',
|
||||
dataIndex: 'codigo',
|
||||
width: 110,
|
||||
render: (v: string) => <span style={{ fontVariantNumeric: 'tabular-nums' }}>{v.trim()}</span>,
|
||||
},
|
||||
{
|
||||
title: 'Descrição',
|
||||
dataIndex: 'descricao',
|
||||
render: (v: string, row: ProdutoSummary) => (
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{v.trim()}</div>
|
||||
{row.grupo && <div style={{ fontSize: 12, color: '#888' }}>{row.grupo.trim()}</div>}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Und',
|
||||
dataIndex: 'unidade',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
render: (v: string | null) => v ?? '—',
|
||||
},
|
||||
{
|
||||
title: 'Marca',
|
||||
dataIndex: 'marca',
|
||||
width: 130,
|
||||
render: (v: string | null) => (v ? <Tag>{v.trim()}</Tag> : null),
|
||||
},
|
||||
{
|
||||
title: 'Preço',
|
||||
dataIndex: 'vlPreco1',
|
||||
width: 110,
|
||||
align: 'right',
|
||||
render: (v: string) => fmtPrice(v),
|
||||
},
|
||||
{
|
||||
title: 'Estoque',
|
||||
dataIndex: 'qtdEstoque',
|
||||
width: 90,
|
||||
align: 'right',
|
||||
render: (v: string | null) => {
|
||||
if (v == null) return '—';
|
||||
const n = Number(v);
|
||||
return (
|
||||
<span style={{ color: n > 0 ? 'inherit' : '#f5222d' }}>{n.toLocaleString('pt-BR')}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function CatalogPage() {
|
||||
const [q, setQ] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const limit = 50;
|
||||
|
||||
const { data, isLoading } = useCatalog({ q: q || undefined, page, limit });
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<Title level={3} style={{ marginBottom: 16 }}>
|
||||
Catálogo de Produtos
|
||||
</Title>
|
||||
|
||||
<Search
|
||||
placeholder="Buscar por código ou descrição..."
|
||||
allowClear
|
||||
style={{ width: 320, marginBottom: 16 }}
|
||||
onSearch={(v) => {
|
||||
setQ(v);
|
||||
setPage(1);
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (!e.target.value) {
|
||||
setQ('');
|
||||
setPage(1);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Table<ProdutoSummary>
|
||||
rowKey="idErp"
|
||||
columns={columns}
|
||||
dataSource={data?.data ?? []}
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize: limit,
|
||||
total: data?.total ?? 0,
|
||||
showSizeChanger: false,
|
||||
showTotal: (t) => `${t.toLocaleString('pt-BR')} produtos`,
|
||||
onChange: (p) => setPage(p),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user