feat(web+api): cockpit rep — novo pedido v2, endereço de entrega e cancelamento de orçamento
- NewOrderPage: etapas 1-5 — merge de cards, busca de produto melhorada (autocomplete com estoque + modal catálogo), responsividade mobile (tabela → cards, footer compacto), histórico de compras do cliente e campo de endereço de entrega (cadastrado vs livre) - endEntrega: campo TEXT adicionado em sar.pedidos; contrato CreatePedidoSchema e PedidoDetailSchema atualizados; aparece no detalhe (OrderDetailPage, OrderDetailModal) e no PDF (OrderPrintPage) - Cancelamento de orçamento: PATCH /orders/:id/cancel — rep cancela seu próprio orçamento (situa 0); histórico registrado; UI com Modal.confirm substituindo alert() em OrderActionsMenu - Fix: useClientDetail chamado para cliente selecionado manualmente, garantindo que clientEndStr seja preenchido mesmo sem clientIdParam na URL Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -425,18 +425,23 @@ export function OrderDetailPage() {
|
||||
)}
|
||||
<Descriptions.Item label="Total produtos">{fmt(order.totalProdutos)}</Descriptions.Item>
|
||||
<Descriptions.Item label="Desc. Global">{order.descontoPerc}%</Descriptions.Item>
|
||||
<Descriptions.Item label="Total">
|
||||
<Descriptions.Item label="Total" span={isOrcamento ? 1 : 2}>
|
||||
<Text strong style={{ fontSize: 16 }}>
|
||||
{fmt(order.total)}
|
||||
</Text>
|
||||
</Descriptions.Item>
|
||||
{order.endEntrega && (
|
||||
<Descriptions.Item label="End. Entrega" span={isOrcamento ? 3 : 2}>
|
||||
{order.endEntrega}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
{order.obs && (
|
||||
<Descriptions.Item label="Observações" span={2}>
|
||||
<Descriptions.Item label="Observações" span={isOrcamento ? 3 : 2}>
|
||||
{order.obs}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
{order.motivoRecusa && (
|
||||
<Descriptions.Item label="Motivo Recusa" span={2}>
|
||||
<Descriptions.Item label="Motivo Recusa" span={isOrcamento ? 3 : 2}>
|
||||
<Text type="danger">{order.motivoRecusa}</Text>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
|
||||
@@ -399,11 +399,23 @@ export function OrderPrintPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Observações + rodapé ────────────────────────────────────────── */}
|
||||
{order.obs && (
|
||||
<div style={{ padding: '10px 28px 0' }}>
|
||||
<span style={label}>Observações</span>
|
||||
<div style={{ fontSize: 11, color: '#475569', lineHeight: 1.5 }}>{order.obs}</div>
|
||||
{/* ── Endereço de entrega + Observações ───────────────────────────── */}
|
||||
{(order.endEntrega || order.obs) && (
|
||||
<div style={{ padding: '10px 28px 0', display: 'flex', gap: 24, flexWrap: 'wrap' }}>
|
||||
{order.endEntrega && (
|
||||
<div style={{ flex: 1, minWidth: 220 }}>
|
||||
<span style={label}>Endereço de entrega</span>
|
||||
<div style={{ fontSize: 11, color: '#475569', lineHeight: 1.5 }}>
|
||||
{order.endEntrega}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{order.obs && (
|
||||
<div style={{ flex: 1, minWidth: 220 }}>
|
||||
<span style={label}>Observações</span>
|
||||
<div style={{ fontSize: 11, color: '#475569', lineHeight: 1.5 }}>{order.obs}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
@@ -43,6 +44,7 @@ import { SITUA_LABEL } from '@sar/api-interface';
|
||||
import { useOrderList, useOrderDetail } from '../../lib/queries/orders';
|
||||
import { usePendingOrders } from '../../lib/hooks/usePendingOrders';
|
||||
import { removePendingOrder, retryPendingOrder } from '../../lib/offline/order-queue';
|
||||
import { apiFetch } from '../../lib/api-client';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { useBreakpoint } = Grid;
|
||||
@@ -250,6 +252,19 @@ function OrderActionsMenu({
|
||||
onView: (id: string) => void;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const qc = useQueryClient();
|
||||
const { modal, message: msg } = App.useApp();
|
||||
|
||||
const cancelMutation = useMutation({
|
||||
mutationFn: () => apiFetch(`/orders/${order.id}/cancel`, { method: 'PATCH' }),
|
||||
onSuccess: () => {
|
||||
void msg.success('Orçamento cancelado.');
|
||||
void qc.invalidateQueries({ queryKey: ['orders'] });
|
||||
},
|
||||
onError: (e: unknown) => void msg.error(e instanceof Error ? e.message : 'Erro ao cancelar'),
|
||||
});
|
||||
|
||||
const canCancel = order.situa === 0 && order.fonte !== 'erp';
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
@@ -287,8 +302,18 @@ function OrderActionsMenu({
|
||||
icon: <CloseCircleOutlined />,
|
||||
label: 'Cancelar pedido',
|
||||
danger: true,
|
||||
disabled: order.situa === 3,
|
||||
onClick: () => alert('Cancelamento em breve'),
|
||||
disabled: !canCancel,
|
||||
onClick: () => {
|
||||
if (!canCancel) return;
|
||||
void modal.confirm({
|
||||
title: 'Cancelar orçamento?',
|
||||
content: `O pedido ${order.numPedSar} será marcado como cancelado. Esta ação não pode ser desfeita.`,
|
||||
okText: 'Cancelar pedido',
|
||||
okButtonProps: { danger: true },
|
||||
cancelText: 'Voltar',
|
||||
onOk: () => cancelMutation.mutate(),
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -387,7 +412,7 @@ function OrderDetailModal({ id, onClose }: { id: string | null; onClose: () => v
|
||||
{isLoading && <Spin style={{ display: 'block', margin: '48px auto' }} />}
|
||||
|
||||
{data && (
|
||||
<Space direction="vertical" size={20} style={{ width: '100%' }}>
|
||||
<Space orientation="vertical" size={20} style={{ width: '100%' }}>
|
||||
{/* Status */}
|
||||
<div>
|
||||
<OrderStatusBadge situa={data.situa} descr={data.statusDescr} />
|
||||
@@ -434,6 +459,12 @@ function OrderDetailModal({ id, onClose }: { id: string | null; onClose: () => v
|
||||
<span style={label}>Representante</span>
|
||||
<Text>{data.nomeVendedor ?? `Cód. ${data.codVendedor}`}</Text>
|
||||
</Col>
|
||||
{data.endEntrega && (
|
||||
<Col span={24}>
|
||||
<span style={label}>End. Entrega</span>
|
||||
<Text type="secondary">{data.endEntrega}</Text>
|
||||
</Col>
|
||||
)}
|
||||
{data.obs && (
|
||||
<Col span={24}>
|
||||
<span style={label}>Observações</span>
|
||||
@@ -460,7 +491,7 @@ function OrderDetailModal({ id, onClose }: { id: string | null; onClose: () => v
|
||||
title: 'Produto',
|
||||
key: 'produto',
|
||||
render: (_: unknown, item) => (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Space orientation="vertical" size={0}>
|
||||
<Text style={{ fontSize: 11, color: '#94A3B8' }}>{item.codProduto}</Text>
|
||||
<Text style={{ fontWeight: 500 }}>{item.descProduto}</Text>
|
||||
</Space>
|
||||
|
||||
Reference in New Issue
Block a user