feat(auth): endpoint /auth/me, cockpits renomeados e menu de logout
- 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>
This commit is contained in:
97
apps/web/src/cockpits/supervisor/ApprovalQueuePage.tsx
Normal file
97
apps/web/src/cockpits/supervisor/ApprovalQueuePage.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Table, Tag, Typography, Badge, Space } from 'antd';
|
||||
import type { TableColumnsType } from 'antd';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import type { PedidoSummary } from '@sar/api-interface';
|
||||
import { useOrderList } from '../../lib/queries/orders';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function hoursWaiting(createdAt: string): number {
|
||||
return Math.floor((Date.now() - new Date(createdAt).getTime()) / 3_600_000);
|
||||
}
|
||||
|
||||
const columns: TableColumnsType<PedidoSummary> = [
|
||||
{
|
||||
title: 'Nº',
|
||||
dataIndex: 'numPedSar',
|
||||
width: 120,
|
||||
render: (num: string, row: PedidoSummary) => (
|
||||
<Link to="/pedidos/$id" params={{ id: row.id }}>
|
||||
{num}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{ title: 'Rep (cód)', dataIndex: 'codVendedor', width: 100 },
|
||||
{ title: 'Cliente (cód)', dataIndex: 'idCliente', width: 110 },
|
||||
{
|
||||
title: 'Total',
|
||||
dataIndex: 'total',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: (v: string) =>
|
||||
Number(v).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }),
|
||||
},
|
||||
{
|
||||
title: 'Desc. %',
|
||||
dataIndex: 'descontoPerc',
|
||||
width: 90,
|
||||
align: 'right',
|
||||
render: (v: string) => `${v}%`,
|
||||
},
|
||||
{
|
||||
title: 'Aguardando',
|
||||
dataIndex: 'createdAt',
|
||||
width: 130,
|
||||
render: (v: string) => {
|
||||
const h = hoursWaiting(v);
|
||||
return <Tag color={h > 2 ? 'red' : 'orange'}>{h}h</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
width: 100,
|
||||
render: (_: unknown, row: PedidoSummary) => (
|
||||
<Link to="/pedidos/$id" params={{ id: row.id }}>
|
||||
<Tag color="blue" style={{ cursor: 'pointer' }}>
|
||||
Analisar
|
||||
</Tag>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export function ApprovalQueuePage() {
|
||||
// situa=1 = Pendente de Aprovação
|
||||
const { data, isLoading } = useOrderList({ situa: 1, limit: 200 });
|
||||
|
||||
const urgentCount = data?.data.filter((o) => hoursWaiting(o.createdAt) > 2).length ?? 0;
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<Space align="center" style={{ marginBottom: 16 }}>
|
||||
<Title level={3} style={{ margin: 0 }}>
|
||||
Fila de Aprovações
|
||||
</Title>
|
||||
{urgentCount > 0 && (
|
||||
<Badge
|
||||
count={urgentCount}
|
||||
style={{ backgroundColor: '#cf1322' }}
|
||||
title={`${urgentCount} urgente(s) — mais de 2h aguardando`}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
<Table<PedidoSummary>
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={data?.data ?? []}
|
||||
loading={isLoading}
|
||||
rowClassName={(row) => (hoursWaiting(row.createdAt) > 2 ? 'row-urgent' : '')}
|
||||
pagination={false}
|
||||
locale={{ emptyText: 'Nenhum pedido aguardando aprovação.' }}
|
||||
/>
|
||||
|
||||
<style>{`.row-urgent td { background: #fff1f0 !important; }`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user