feat(web+api): cockpit gerente — painel, equipe, políticas e positivação
## API - GET /dashboard/manager: KPIs agregados (faturamento, pedidos, ticket médio, promoções ativas), meta total do período, ranking top 10 com clientes atendidos e % meta, positivação por representante, venda necessária/dia - Parâmetros opcionais ?mes&ano para filtrar período - GET /equipe: lista de reps com pedidos, faturamento, ticket médio e % meta (deduplicação de vw_representantes) - GET/POST /politicas/descontos: alçada de desconto por rep (AlcadaDesconto) - GET/POST/PATCH/DELETE /politicas/promocoes: CRUD de promoções com validade - Metas lidas de sar.vw_metas (tipo=GR), não de vw_metas do ERP ## Schema - Novo model Promocao (sar.promocoes) criado via prisma db execute ## Frontend - Cockpit /ger: GerPainel, EquipePage, PoliticasPage - GerPainel: filtro mes/ano, cards KPI, cards de meta vs realizado (atingimento, falta, venda/dia), ranking com clientes atendidos, positivação de clientes por representante (paginada) - Sidebar e BottomNav role-aware: rep / supervisor / gerente (manager+admin) - Clientes acessível ao gerente (carteira completa de todos os reps) - Rotas: HomeRoute redireciona por role; /ger, /ger/equipe, /ger/politicas Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Table, Input, Select, Space, Typography, Tag, Button, Tooltip } from 'antd';
|
||||
import { Grid, Table, Input, Select, Space, Typography, Tag, Button, Tooltip } from 'antd';
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
import { EyeOutlined } from '@ant-design/icons';
|
||||
import type { TableColumnsType } from 'antd';
|
||||
import type { ProdutoSummary } from '@sar/api-interface';
|
||||
@@ -94,6 +96,8 @@ export function CatalogPage() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [selectedIdErp, setSelectedIdErp] = useState<number | null>(null);
|
||||
const limit = 50;
|
||||
const screens = useBreakpoint();
|
||||
const isMobile = !screens.md;
|
||||
|
||||
const { data: pautas, isLoading: pautasLoading } = usePautas();
|
||||
const { data, isLoading } = useCatalog({ q: q || undefined, idPauta, page, limit });
|
||||
@@ -113,11 +117,11 @@ export function CatalogPage() {
|
||||
</div>
|
||||
|
||||
{/* ── Filtros ──────────────────────────────────────────────────── */}
|
||||
<Space style={{ marginBottom: 16 }} wrap>
|
||||
<Space style={{ marginBottom: 16, width: '100%' }} wrap>
|
||||
<Search
|
||||
placeholder="Buscar por código ou descrição..."
|
||||
allowClear
|
||||
style={{ width: 300 }}
|
||||
style={{ width: isMobile ? '100%' : 300 }}
|
||||
onSearch={(v) => {
|
||||
setQ(v);
|
||||
setPage(1);
|
||||
@@ -133,7 +137,7 @@ export function CatalogPage() {
|
||||
placeholder="Selecionar pauta de preços"
|
||||
allowClear
|
||||
loading={pautasLoading}
|
||||
style={{ width: 340 }}
|
||||
style={{ width: isMobile ? '100%' : 340 }}
|
||||
onChange={(v) => {
|
||||
setIdPauta(v as number | undefined);
|
||||
setPage(1);
|
||||
|
||||
Reference in New Issue
Block a user