Ajuste na coluna de pedidos

This commit is contained in:
Joelson de brito ribeiro
2025-07-04 16:27:54 -03:00
parent aa45e139a1
commit 2323f6370c
16 changed files with 448 additions and 356 deletions

View File

@@ -230,6 +230,7 @@ export function OrderDetail({ order, timelineEvents }: OrderDetailProps) {
} else if (window.history.length > 2) { } else if (window.history.length > 2) {
goBack(); goBack();
} else { } else {
// Usar a função que limpa parâmetros ao navegar para orders/find
goToOrdersFind(); goToOrdersFind();
} }
}; };

View File

@@ -19,6 +19,8 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
} from "@/src/components/ui/dropdown-menu"; } from "@/src/components/ui/dropdown-menu";
import { useOrderItemTableColumns } from "@/src/hooks/useOrderItemTableColumns";
import { NewColumnsNotification } from "@/src/components/ui/NewColumnsNotification";
interface Item { interface Item {
quantity: number | null | undefined; quantity: number | null | undefined;
@@ -58,180 +60,23 @@ export function OrderItemsTable({
itemsError, itemsError,
onViewDetails, onViewDetails,
}: OrderItemsTableProps) { }: OrderItemsTableProps) {
// Carregar as colunas visíveis do localStorage na inicialização const {
const initVisibleColumns = () => { visibleColumns,
try { columnOrder,
const saved = localStorage.getItem('visibleOrderItemColumns'); getColumnClass,
if (saved) { getOrderedVisibleColumns,
const parsed = JSON.parse(saved); resetColumnOrder,
if (Array.isArray(parsed) && parsed.length > 0) { toggleColumn,
return parsed; selectAllColumns,
} unselectAllColumns,
} isColumnVisible,
} catch (error) { handleDragStart,
console.error('Erro ao carregar configurações de colunas:', error); handleDragEnd,
} handleDragOver,
return ALL_COLUMNS; handleDrop,
}; newColumnsDetected,
dismissNewColumnsNotification,
// Inicializar a ordem das colunas } = useOrderItemTableColumns(ALL_COLUMNS);
const initColumnOrder = () => {
try {
const saved = localStorage.getItem('orderItemColumnsOrder');
if (saved) {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed) && parsed.length > 0) {
return parsed;
}
}
} catch (error) {
console.error('Erro ao carregar ordem das colunas:', error);
}
return ALL_COLUMNS;
};
// Estado para controlar quais colunas estão visíveis
const [visibleColumns, setVisibleColumns] = useState<string[]>(initVisibleColumns);
// Estado para controlar a ordem das colunas
const [columnOrder, setColumnOrder] = useState<string[]>(initColumnOrder);
// Estado para rastrear coluna sendo arrastada
const [draggedColumn, setDraggedColumn] = useState<string | null>(null);
// Salvar as colunas visíveis no localStorage sempre que elas mudarem
useEffect(() => {
try {
localStorage.setItem('visibleOrderItemColumns', JSON.stringify(visibleColumns));
} catch (error) {
console.error('Erro ao salvar configurações de colunas:', error);
}
}, [visibleColumns]);
// Salvar a ordem das colunas no localStorage
useEffect(() => {
try {
localStorage.setItem('orderItemColumnsOrder', JSON.stringify(columnOrder));
} catch (error) {
console.error('Erro ao salvar ordem das colunas:', error);
}
}, [columnOrder]);
// Função para alternar a visibilidade de uma coluna
const toggleColumn = (column: string) => {
setVisibleColumns((current) => {
if (current.includes(column)) {
return current.filter((c) => c !== column);
} else {
return [...current, column];
}
});
};
// Função para selecionar todas as colunas
const selectAllColumns = () => {
setVisibleColumns(ALL_COLUMNS);
};
// Função para desmarcar todas as colunas
const unselectAllColumns = () => {
// Mantém pelo menos uma coluna para garantir que a tabela não fique vazia
setVisibleColumns(["Código"]);
};
// Função para verificar se uma coluna está visível
const isColumnVisible = (column: string) => visibleColumns.includes(column);
// Funções para drag & drop
const handleDragStart = (e: React.DragEvent<HTMLDivElement>, column: string) => {
setDraggedColumn(column);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', column);
// Adicionar um estilo visual para o elemento sendo arrastado
if (e.currentTarget) {
setTimeout(() => {
if (e.currentTarget) {
e.currentTarget.classList.add('opacity-50');
}
}, 0);
}
};
const handleDragEnd = (e: React.DragEvent<HTMLDivElement>) => {
setDraggedColumn(null);
e.currentTarget.classList.remove('opacity-50');
};
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>, targetColumn: string) => {
e.preventDefault();
if (!draggedColumn || draggedColumn === targetColumn) return;
// Reordenar as colunas
const newColumnOrder = [...columnOrder];
const draggedIndex = newColumnOrder.indexOf(draggedColumn);
const targetIndex = newColumnOrder.indexOf(targetColumn);
if (draggedIndex !== -1 && targetIndex !== -1) {
newColumnOrder.splice(draggedIndex, 1);
newColumnOrder.splice(targetIndex, 0, draggedColumn);
setColumnOrder(newColumnOrder);
}
};
const getColumnClass = (title: string) => {
const textRight = ["Preço Unitário", "Preço Total", "Quantidade"];
const cellWidth: { [key: string]: string } = {
"Código": "w-20",
"Descrição": "w-56",
"Embalagem": "w-24",
"Cor": "w-20",
"F. Estoque": "w-24",
"Tipo Entrega": "w-28",
"Quantidade": "w-24",
"Preço Unitário": "w-24",
"Preço Total": "w-24",
"Departamento": "w-48",
"Marca": "w-24",
};
let classes = "";
// Não aplicar whitespace-nowrap para Departamento
if (title !== "Departamento") {
classes += "whitespace-nowrap ";
}
// Alinhar à direita
if (textRight.includes(title)) {
classes += "text-right ";
}
// Aplicar larguras
if (cellWidth[title]) {
classes += cellWidth[title] + " ";
}
return classes;
};
// Função para ordenar as colunas visíveis na ordem definida pelo usuário
const getOrderedVisibleColumns = () => {
// Primeiro, filtrar as colunas que são visíveis
const visibleColumnSet = new Set(visibleColumns);
// Depois, ordenar as colunas de acordo com a ordem definida pelo usuário
return columnOrder.filter(column => visibleColumnSet.has(column));
};
// Resetar a ordem das colunas
const resetColumnOrder = () => {
setColumnOrder(ALL_COLUMNS);
};
const handleEvent = (e: React.SyntheticEvent) => { const handleEvent = (e: React.SyntheticEvent) => {
const element = e.currentTarget; const element = e.currentTarget;
@@ -261,6 +106,10 @@ const handleEvent = (e: React.SyntheticEvent) => {
return ( return (
<div className="rounded-md border-0"> <div className="rounded-md border-0">
<NewColumnsNotification
newColumns={newColumnsDetected}
onDismiss={dismissNewColumnsNotification}
/>
<div className="flex justify-end mb-2 gap-2"> <div className="flex justify-end mb-2 gap-2">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>

View File

@@ -12,6 +12,7 @@ import { SelectedOrderItems } from "./SelectedOrderItems";
import { RefreshCw, ChevronLeft, ChevronRight } from "lucide-react"; import { RefreshCw, ChevronLeft, ChevronRight } from "lucide-react";
import { useOrderData } from "../../../src/hooks/useOrderData"; import { useOrderData } from "../../../src/hooks/useOrderData";
import { useSort } from "@/src/hooks/useSort"; import { useSort } from "@/src/hooks/useSort";
import { NewColumnsNotification } from "@/src/components/ui/NewColumnsNotification";
export type ColumnKey = export type ColumnKey =
| "Data" | "Data"
@@ -28,14 +29,17 @@ export type ColumnKey =
| "Valor" | "Valor"
| "Peso(kg)" | "Peso(kg)"
| "Carregamento" | "Carregamento"
| "Dt Carregamento"
| "Cobrança" | "Cobrança"
| "Trasportadora" | "Trasportadora"
| "Praça" | "Praça"
| "Nota fiscal" | "Nota fiscal"
| "Dt Faturamento" | "Dt Faturamento"
| "Motorista"
| "Placa"
const ALL_COLUMNS: ColumnKey[] = [ const ALL_COLUMNS: ColumnKey[] = [
"Data", "Data previsão de entrega", "Agendamento", "Pedido", "Tipo Pedido", "Cliente", "Vendedor", "Filial", "Situação", "Rota", "Valor", "Peso(kg)", "Carregamento", "Cobrança", "Trasportadora", "Praça", "Nota fiscal", "Dt Faturamento", "TV 7" "Data", "Data previsão de entrega", "Agendamento", "Pedido", "Tipo Pedido", "Cliente", "Vendedor", "Filial", "Situação", "Dt Carregamento", "Rota", "Valor", "Peso(kg)", "Carregamento", "Cobrança", "Trasportadora", "Praça", "Nota fiscal", "Dt Faturamento", "TV 7", "Motorista", "Placa"
]; ];
export interface OrdersTableProps { export interface OrdersTableProps {
@@ -103,6 +107,7 @@ export function OrdersTable({
"Valor": (order: Order) => parseFloat(String(order.totalValue || "0")), "Valor": (order: Order) => parseFloat(String(order.totalValue || "0")),
"Peso(kg)": (order: Order) => parseFloat(String(order.totalWeigth || "0")), "Peso(kg)": (order: Order) => parseFloat(String(order.totalWeigth || "0")),
"Dt Faturamento": (order: Order) => new Date(order.invoiceDate || ""), "Dt Faturamento": (order: Order) => new Date(order.invoiceDate || ""),
"Dt Carregamento": (order: Order) => new Date(order.shipmentDate || ""),
// Add other column accessors as needed // Add other column accessors as needed
}), []); }), []);
@@ -126,6 +131,8 @@ export function OrdersTable({
handleDragEnd, handleDragEnd,
handleDragOver, handleDragOver,
handleDrop, handleDrop,
newColumnsDetected,
dismissNewColumnsNotification,
} = useOrderTableColumns(ALL_COLUMNS); } = useOrderTableColumns(ALL_COLUMNS);
const { tableRef } = useKeyboardNavigation({ const { tableRef } = useKeyboardNavigation({
@@ -248,6 +255,10 @@ export function OrdersTable({
return ( return (
<div className="rounded-md border-0" ref={tableRef} tabIndex={0}> <div className="rounded-md border-0" ref={tableRef} tabIndex={0}>
<NewColumnsNotification
newColumns={newColumnsDetected}
onDismiss={dismissNewColumnsNotification}
/>
{renderHeaderControls()} {renderHeaderControls()}
{renderOrdersTable()} {renderOrdersTable()}
<SelectedOrderItems <SelectedOrderItems

View File

@@ -43,7 +43,7 @@ const ColumnHeader = memo(({
onSort?: (column: string) => void; onSort?: (column: string) => void;
}) => { }) => {
// Determinar se esta coluna é ordenável // Determinar se esta coluna é ordenável
const isSortable = ["Data", "Valor", "Peso(kg)", "Pedido", "Dt Faturamento"].includes(title); const isSortable = ["Data", "Valor", "Peso(kg)", "Pedido", "Dt Faturamento", "Dt Carregamento"].includes(title);
// Verificar se esta coluna está sendo ordenada // Verificar se esta coluna está sendo ordenada
const isSorted = sortConfig && sortConfig.key === title; const isSorted = sortConfig && sortConfig.key === title;

View File

@@ -82,6 +82,7 @@ export const orderCellRenderers: Record<OrderColumn, React.FC<CellRenderProps>>
[OrderColumn.Value]: ({ order }) => <>{formatCurrency(order.amount || 0)}</>, [OrderColumn.Value]: ({ order }) => <>{formatCurrency(order.amount || 0)}</>,
[OrderColumn.Weight]: ({ order }) => <>{order.totalWeigth}</>, [OrderColumn.Weight]: ({ order }) => <>{order.totalWeigth}</>,
[OrderColumn.Shipment]: ({ order }) => <>{order.shipmentId}</>, [OrderColumn.Shipment]: ({ order }) => <>{order.shipmentId}</>,
[OrderColumn.ShipmentDate]: ({ order }) => <>{formatDateSafe(order.shipmentDate)}</>,
[OrderColumn.Billing]: ({ order }) => <>{order.billingName}</>, [OrderColumn.Billing]: ({ order }) => <>{order.billingName}</>,
[OrderColumn.Carrier]: ({ order }) => <>{order.carrier}</>, [OrderColumn.Carrier]: ({ order }) => <>{order.carrier}</>,
[OrderColumn.Place]: ({ order }) => <>{order.deliveryLocal}</>, [OrderColumn.Place]: ({ order }) => <>{order.deliveryLocal}</>,
@@ -89,4 +90,6 @@ export const orderCellRenderers: Record<OrderColumn, React.FC<CellRenderProps>>
[OrderColumn.InvoiceDate]: ({ order }) => <>{formatDateSafe(order.invoiceDate)}</>, [OrderColumn.InvoiceDate]: ({ order }) => <>{formatDateSafe(order.invoiceDate)}</>,
[OrderColumn.DeliveryDate]: ({ order }) => <>{order.deliveryDate ? formatDateSafe(order.deliveryDate) : "-"}</>, [OrderColumn.DeliveryDate]: ({ order }) => <>{order.deliveryDate ? formatDateSafe(order.deliveryDate) : "-"}</>,
[OrderColumn.SchedulerDelivery]: ({ order }) => <>{order.schedulerDelivery || "-"}</>, [OrderColumn.SchedulerDelivery]: ({ order }) => <>{order.schedulerDelivery || "-"}</>,
[OrderColumn.Driver]: ({ order }) => <>{order.driver || "-"}</>,
[OrderColumn.CarIdentification]: ({ order }) => <>{order.carIdentification || "-"}</>,
}; };

View File

@@ -15,10 +15,13 @@ export const columnClasses: Record<OrderColumn, string> = {
[OrderColumn.Value]: "p-2 whitespace-nowrap text-right border-r border-gray-200 text-xs", [OrderColumn.Value]: "p-2 whitespace-nowrap text-right border-r border-gray-200 text-xs",
[OrderColumn.Weight]: "p-2 whitespace-nowrap text-right border-r border-gray-200 text-xs", [OrderColumn.Weight]: "p-2 whitespace-nowrap text-right border-r border-gray-200 text-xs",
[OrderColumn.Shipment]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs", [OrderColumn.Shipment]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.ShipmentDate]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.Billing]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs", [OrderColumn.Billing]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.Carrier]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs", [OrderColumn.Carrier]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.Place]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs", [OrderColumn.Place]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.Invoice]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs", [OrderColumn.Invoice]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.InvoiceDate]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs", [OrderColumn.InvoiceDate]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.DeliveryDate]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs", [OrderColumn.DeliveryDate]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.Driver]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
[OrderColumn.CarIdentification]: "p-2 whitespace-nowrap border-r border-gray-200 text-xs",
}; };

View File

@@ -15,10 +15,13 @@ export enum OrderColumn {
Value = "Valor", Value = "Valor",
Weight = "Peso(kg)", Weight = "Peso(kg)",
Shipment = "Carregamento", Shipment = "Carregamento",
ShipmentDate = "Dt Carregamento",
Billing = "Cobrança", Billing = "Cobrança",
Carrier = "Trasportadora", Carrier = "Trasportadora",
Place = "Praça", Place = "Praça",
Invoice = "Nota fiscal", Invoice = "Nota fiscal",
InvoiceDate = "Dt Faturamento" InvoiceDate = "Dt Faturamento",
Driver = "Motorista",
CarIdentification = "Placa"
} }

View File

@@ -0,0 +1,29 @@
import { Button } from "@/src/components/ui/button";
import { useOrderSearch } from "@/src/hooks/useOrderSearch";
interface ClearAndNavigateButtonProps {
children: React.ReactNode;
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
size?: "default" | "sm" | "lg" | "icon";
className?: string;
}
export function ClearAndNavigateButton({
children,
variant = "outline",
size = "default",
className
}: ClearAndNavigateButtonProps) {
const { clearAndNavigateToOrders } = useOrderSearch(8);
return (
<Button
variant={variant}
size={size}
onClick={clearAndNavigateToOrders}
className={className}
>
{children}
</Button>
);
}

View File

@@ -0,0 +1,37 @@
import { Alert, AlertDescription } from "@/src/components/ui/alert";
import { X, Info } from "lucide-react";
import { Button } from "@/src/components/ui/button";
interface NewColumnsNotificationProps {
newColumns: string[];
onDismiss: () => void;
}
export function NewColumnsNotification({ newColumns, onDismiss }: NewColumnsNotificationProps) {
if (newColumns.length === 0) return null;
return (
<Alert className="mb-4 border-blue-200 bg-blue-50">
<div className="flex items-start justify-between">
<div className="flex items-start gap-2">
<Info className="h-4 w-4 text-blue-600 mt-0.5" />
<AlertDescription className="text-blue-800">
<span className="font-medium">Novas colunas detectadas:</span>{" "}
{newColumns.join(", ")}.
<span className="text-blue-600 text-sm">
{" "}Elas foram automaticamente adicionadas à sua visualização.
</span>
</AlertDescription>
</div>
<Button
variant="ghost"
size="sm"
onClick={onDismiss}
className="h-6 w-6 p-0 text-blue-600 hover:text-blue-800 hover:bg-blue-100"
>
<X className="h-4 w-4" />
</Button>
</div>
</Alert>
);
}

View File

@@ -27,6 +27,16 @@ export function useNavigation() {
router.push("/orders/find"); router.push("/orders/find");
}; };
// Função para navegar para orders/find limpando parâmetros da URL
const goToOrdersFindClean = () => {
router.push("/orders/find");
};
// Função para navegar para outras páginas limpando parâmetros
const navigateToClean = (path: string) => {
router.push(path);
};
// Helper to build URL with search params // Helper to build URL with search params
const buildUrl = (base: string, params: Record<string, string>) => { const buildUrl = (base: string, params: Record<string, string>) => {
const url = new URL(base, window.location.origin); const url = new URL(base, window.location.origin);
@@ -46,6 +56,8 @@ export function useNavigation() {
goBack, goBack,
goToHome, goToHome,
goToOrdersFind, goToOrdersFind,
goToOrdersFindClean,
navigateToClean,
buildUrl buildUrl
}; };
} }

View File

@@ -0,0 +1,52 @@
import { useCallback } from "react";
import { useTableColumns } from "./useTableColumns";
export function useOrderItemTableColumns(allColumns: string[]) {
const tableColumns = useTableColumns({
allColumns,
storageKey: "OrderItem",
defaultVisibleColumns: allColumns,
});
// Memoizar as classes de colunas para evitar recálculos desnecessários
const getColumnClass = useCallback((title: string) => {
const textRight = ["Preço Unitário", "Preço Total", "Quantidade"];
const cellWidth: { [key: string]: string } = {
"Código": "w-20",
"Descrição": "w-56",
"Embalagem": "w-24",
"Cor": "w-20",
"F. Estoque": "w-24",
"Tipo Entrega": "w-28",
"Quantidade": "w-24",
"Preço Unitário": "w-24",
"Preço Total": "w-24",
"Departamento": "w-48",
"Marca": "w-24",
};
let classes = "";
// Não aplicar whitespace-nowrap para Departamento
if (title !== "Departamento") {
classes += "whitespace-nowrap ";
}
// Alinhar à direita
if (textRight.includes(title)) {
classes += "text-right ";
}
// Aplicar larguras
if (cellWidth[title]) {
classes += cellWidth[title] + " ";
}
return classes;
}, []);
return {
...tableColumns,
getColumnClass,
};
}

View File

@@ -6,6 +6,7 @@ import { extractApiData } from "@/src/utils/api-helpers";
import { useSearchParams, useRouter } from "next/navigation"; import { useSearchParams, useRouter } from "next/navigation";
import { clientesApi } from "@/src/lib/api" import { clientesApi } from "@/src/lib/api"
import { setStorage, getStorage } from "@/src/utils/storage"; import { setStorage, getStorage } from "@/src/utils/storage";
import { clearUrlAndNavigate } from "@/src/utils/url-helpers";
/** /**
* Função de utilidade para aplicar debounce em funções * Função de utilidade para aplicar debounce em funções
@@ -66,6 +67,7 @@ interface UseOrderSearchReturn {
handleCustomerFilter: (value: string) => Promise<void>; handleCustomerFilter: (value: string) => Promise<void>;
setInitialOrders: (savedOrders: Order[], savedPage: number, savedHasSearched: boolean) => void; setInitialOrders: (savedOrders: Order[], savedPage: number, savedHasSearched: boolean) => void;
clearStoredParams: () => void; clearStoredParams: () => void;
clearAndNavigateToOrders: () => void;
handleMultiInputChange: (field: keyof OrderSearchParams, values: string[]) => void; handleMultiInputChange: (field: keyof OrderSearchParams, values: string[]) => void;
} }
@@ -657,6 +659,16 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setStorage("orderSearchParams", null); setStorage("orderSearchParams", null);
}, []); }, []);
// Função para limpar parâmetros e navegar para orders/find
const clearAndNavigateToOrders = useCallback(() => {
// Limpar parâmetros do localStorage
setStorage("orderSearchParams", null);
// Limpar parâmetros do estado
setSearchParams({});
// Limpar URL e navegar
clearUrlAndNavigate("/orders/find");
}, [setSearchParams]);
/** /**
* Manipula a seleção de múltiplos valores em filtros de seleção * Manipula a seleção de múltiplos valores em filtros de seleção
* *
@@ -701,6 +713,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
handleCustomerFilter, handleCustomerFilter,
setInitialOrders, setInitialOrders,
clearStoredParams, clearStoredParams,
clearAndNavigateToOrders,
handleMultiInputChange handleMultiInputChange
}; };
} }

View File

@@ -1,87 +1,12 @@
import { useState, useEffect, useMemo, useCallback } from "react"; import { useCallback } from "react";
import { ChevronDown, ChevronUp } from "lucide-react"; import { useTableColumns } from "./useTableColumns";
export function useOrderTableColumns(allColumns: string[]) { export function useOrderTableColumns(allColumns: string[]) {
// Carregar as colunas visíveis do localStorage na inicialização const tableColumns = useTableColumns({
const initVisibleColumns = () => { allColumns,
try { storageKey: "Order",
const saved = localStorage.getItem('visibleOrderColumns'); defaultVisibleColumns: allColumns,
if (saved) { });
const parsed = JSON.parse(saved);
if (Array.isArray(parsed) && parsed.length > 0) {
return parsed;
}
}
} catch (error) {
console.error('Erro ao carregar configurações de colunas:', error);
}
return allColumns;
};
// Inicializar a ordem das colunas
const initColumnOrder = () => {
try {
const saved = localStorage.getItem('orderColumnsOrder');
if (saved) {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed) && parsed.length > 0) {
return parsed;
}
}
} catch (error) {
console.error('Erro ao carregar ordem das colunas:', error);
}
return allColumns;
};
// Estado para controlar quais colunas estão visíveis
const [visibleColumns, setVisibleColumns] = useState<string[]>(initVisibleColumns);
// Estado para controlar a ordem das colunas
const [columnOrder, setColumnOrder] = useState<string[]>(initColumnOrder);
// Estado para rastrear coluna sendo arrastada
const [draggedColumn, setDraggedColumn] = useState<string | null>(null);
useEffect(() => {
try {
const savedAllColumns = localStorage.getItem('allOrderColumns');
if (savedAllColumns) {
const parsedAllColumns = JSON.parse(savedAllColumns);
if (JSON.stringify(parsedAllColumns) !== JSON.stringify(allColumns)) {
localStorage.removeItem('visibleOrderColumns');
localStorage.removeItem('orderColumnsOrder');
localStorage.setItem('allOrderColumns', JSON.stringify(allColumns));
// Recarregar a página para aplicar as novas colunas
window.location.reload();
}
} else {
localStorage.setItem('allOrderColumns', JSON.stringify(allColumns));
}
} catch (error) {
console.error('Erro ao verificar as colunas no localStorage:', error);
}
}, [allColumns]);
// Salvar as colunas visíveis no localStorage sempre que elas mudarem
useEffect(() => {
try {
localStorage.setItem('visibleOrderColumns', JSON.stringify(visibleColumns));
} catch (error) {
console.error('Erro ao salvar configurações de colunas:', error);
}
}, [visibleColumns]);
// Salvar a ordem das colunas no localStorage
useEffect(() => {
try {
localStorage.setItem('orderColumnsOrder', JSON.stringify(columnOrder));
} catch (error) {
console.error('Erro ao salvar ordem das colunas:', error);
}
}, [columnOrder]);
// Memoizar as classes de colunas para evitar recálculos desnecessários // Memoizar as classes de colunas para evitar recálculos desnecessários
const getColumnClass = useCallback((title: string) => { const getColumnClass = useCallback((title: string) => {
@@ -98,12 +23,15 @@ export function useOrderTableColumns(allColumns: string[]) {
"Valor": "w-20", "Valor": "w-20",
"Peso(kg)": "w-20", "Peso(kg)": "w-20",
"Carregamento": "w-28", "Carregamento": "w-28",
"Dt Carregamento": "w-28",
"Cobrança": "w-28", "Cobrança": "w-28",
"Praça": "w-20", "Praça": "w-20",
"Nota fiscal": "w-20", "Nota fiscal": "w-20",
"Dt Faturamento": "w-28", "Dt Faturamento": "w-28",
"Rota": "w-24", "Rota": "w-24",
"Trasportadora": "w-28", "Trasportadora": "w-28",
"Motorista": "w-24",
"Placa": "w-32",
}; };
let classes = "whitespace-nowrap "; let classes = "whitespace-nowrap ";
@@ -121,103 +49,8 @@ export function useOrderTableColumns(allColumns: string[]) {
return classes; return classes;
}, []); }, []);
// Funções para drag & drop
const handleDragStart = useCallback((e: React.DragEvent<HTMLDivElement>, column: string) => {
setDraggedColumn(column);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', column);
const target = e.currentTarget as HTMLDivElement; // salve a referência antes
// aplique a classe imediatamente (sem setTimeout)
target.classList.add('opacity-50');
}, []);
const handleDragEnd = useCallback((e: React.DragEvent<HTMLDivElement>) => {
setDraggedColumn(null);
e.currentTarget.classList.remove('opacity-50');
}, []);
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}, []);
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>, targetColumn: string) => {
e.preventDefault();
if (!draggedColumn || draggedColumn === targetColumn) return;
// Reordenar as colunas
const newColumnOrder = [...columnOrder];
const draggedIndex = newColumnOrder.indexOf(draggedColumn);
const targetIndex = newColumnOrder.indexOf(targetColumn);
if (draggedIndex !== -1 && targetIndex !== -1) {
newColumnOrder.splice(draggedIndex, 1);
newColumnOrder.splice(targetIndex, 0, draggedColumn);
setColumnOrder(newColumnOrder);
}
}, [draggedColumn, columnOrder]);
// Função para alternar a visibilidade de uma coluna
const toggleColumn = useCallback((column: string) => {
setVisibleColumns((current) => {
if (current.includes(column)) {
return current.filter((c) => c !== column);
} else {
return [...current, column];
}
});
}, []);
// Função para selecionar todas as colunas
const selectAllColumns = useCallback(() => {
setVisibleColumns(allColumns);
}, [allColumns]);
// Função para desmarcar todas as colunas
const unselectAllColumns = useCallback(() => {
// Mantém pelo menos uma coluna para garantir que a tabela não fique vazia
setVisibleColumns(["Pedido"]);
}, []);
// Função para verificar se uma coluna está visível
const isColumnVisible = useCallback((column: string) => {
return visibleColumns.includes(column);
}, [visibleColumns]);
// Função para ordenar as colunas visíveis na ordem definida pelo usuário
// Memoizando o resultado para evitar recálculos desnecessários
const getOrderedVisibleColumns = useCallback(() => {
// Primeiro, filtrar as colunas que são visíveis
const visibleColumnSet = new Set(visibleColumns);
// Depois, ordenar as colunas de acordo com a ordem definida pelo usuário
return columnOrder.filter(column => visibleColumnSet.has(column));
}, [visibleColumns, columnOrder]);
// Resetar a ordem das colunas
const resetColumnOrder = useCallback(() => {
setColumnOrder(allColumns);
}, [allColumns]);
return { return {
visibleColumns, ...tableColumns,
columnOrder,
getColumnClass, getColumnClass,
getOrderedVisibleColumns,
resetColumnOrder,
toggleColumn,
selectAllColumns,
unselectAllColumns,
isColumnVisible,
handleDragStart,
handleDragEnd,
handleDragOver,
handleDrop
}; };
} }

View File

@@ -0,0 +1,217 @@
import { useState, useEffect, useCallback } from "react";
interface UseTableColumnsOptions {
allColumns: string[];
storageKey: string;
defaultVisibleColumns?: string[];
}
export function useTableColumns({
allColumns,
storageKey,
defaultVisibleColumns
}: UseTableColumnsOptions) {
// Carregar as colunas visíveis do localStorage na inicialização
const initVisibleColumns = () => {
try {
const saved = localStorage.getItem(`visible${storageKey}Columns`);
if (saved) {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed) && parsed.length > 0) {
return parsed;
}
}
} catch (error) {
console.error('Erro ao carregar configurações de colunas:', error);
}
return defaultVisibleColumns || allColumns;
};
// Inicializar a ordem das colunas
const initColumnOrder = () => {
try {
const saved = localStorage.getItem(`${storageKey}ColumnsOrder`);
if (saved) {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed) && parsed.length > 0) {
return parsed;
}
}
} catch (error) {
console.error('Erro ao carregar ordem das colunas:', error);
}
return allColumns;
};
// Estado para controlar quais colunas estão visíveis
const [visibleColumns, setVisibleColumns] = useState<string[]>(initVisibleColumns);
// Estado para controlar a ordem das colunas
const [columnOrder, setColumnOrder] = useState<string[]>(initColumnOrder);
// Estado para rastrear coluna sendo arrastada
const [draggedColumn, setDraggedColumn] = useState<string | null>(null);
// Estado para notificar sobre novas colunas
const [newColumnsDetected, setNewColumnsDetected] = useState<string[]>([]);
useEffect(() => {
try {
const savedAllColumns = localStorage.getItem(`all${storageKey}Columns`);
if (savedAllColumns) {
const parsedAllColumns = JSON.parse(savedAllColumns);
if (JSON.stringify(parsedAllColumns) !== JSON.stringify(allColumns)) {
// Detectar novas colunas
const newColumns = allColumns.filter(col => !parsedAllColumns.includes(col));
if (newColumns.length > 0) {
// Adicionar novas colunas às colunas visíveis
setVisibleColumns(prev => {
const updated = [...prev, ...newColumns];
// Remover duplicatas
return [...new Set(updated)];
});
// Atualizar a ordem das colunas incluindo as novas
setColumnOrder(prev => {
const updated = [...prev, ...newColumns];
// Remover duplicatas
return [...new Set(updated)];
});
// Notificar sobre as novas colunas
setNewColumnsDetected(newColumns);
// Limpar a notificação após 5 segundos
setTimeout(() => {
setNewColumnsDetected([]);
}, 5000);
}
// Salvar as novas colunas no localStorage
localStorage.setItem(`all${storageKey}Columns`, JSON.stringify(allColumns));
}
} else {
localStorage.setItem(`all${storageKey}Columns`, JSON.stringify(allColumns));
}
} catch (error) {
console.error('Erro ao verificar as colunas no localStorage:', error);
}
}, [allColumns, storageKey]);
// Salvar as colunas visíveis no localStorage sempre que elas mudarem
useEffect(() => {
try {
localStorage.setItem(`visible${storageKey}Columns`, JSON.stringify(visibleColumns));
} catch (error) {
console.error('Erro ao salvar configurações de colunas:', error);
}
}, [visibleColumns, storageKey]);
// Salvar a ordem das colunas no localStorage
useEffect(() => {
try {
localStorage.setItem(`${storageKey}ColumnsOrder`, JSON.stringify(columnOrder));
} catch (error) {
console.error('Erro ao salvar ordem das colunas:', error);
}
}, [columnOrder, storageKey]);
// Funções para drag & drop
const handleDragStart = useCallback((e: React.DragEvent<HTMLDivElement>, column: string) => {
setDraggedColumn(column);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', column);
const target = e.currentTarget as HTMLDivElement;
target.classList.add('opacity-50');
}, []);
const handleDragEnd = useCallback((e: React.DragEvent<HTMLDivElement>) => {
setDraggedColumn(null);
e.currentTarget.classList.remove('opacity-50');
}, []);
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}, []);
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>, targetColumn: string) => {
e.preventDefault();
if (!draggedColumn || draggedColumn === targetColumn) return;
// Reordenar as colunas
const newColumnOrder = [...columnOrder];
const draggedIndex = newColumnOrder.indexOf(draggedColumn);
const targetIndex = newColumnOrder.indexOf(targetColumn);
if (draggedIndex !== -1 && targetIndex !== -1) {
newColumnOrder.splice(draggedIndex, 1);
newColumnOrder.splice(targetIndex, 0, draggedColumn);
setColumnOrder(newColumnOrder);
}
}, [draggedColumn, columnOrder]);
// Função para alternar a visibilidade de uma coluna
const toggleColumn = useCallback((column: string) => {
setVisibleColumns((current) => {
if (current.includes(column)) {
return current.filter((c) => c !== column);
} else {
return [...current, column];
}
});
}, []);
// Função para selecionar todas as colunas
const selectAllColumns = useCallback(() => {
setVisibleColumns(allColumns);
}, [allColumns]);
// Função para desmarcar todas as colunas
const unselectAllColumns = useCallback(() => {
// Mantém pelo menos uma coluna para garantir que a tabela não fique vazia
const firstColumn = allColumns[0] || "Código";
setVisibleColumns([firstColumn]);
}, [allColumns]);
// Função para verificar se uma coluna está visível
const isColumnVisible = useCallback((column: string) => {
return visibleColumns.includes(column);
}, [visibleColumns]);
// Função para ordenar as colunas visíveis na ordem definida pelo usuário
const getOrderedVisibleColumns = useCallback(() => {
// Primeiro, filtrar as colunas que são visíveis
const visibleColumnSet = new Set(visibleColumns);
// Depois, ordenar as colunas de acordo com a ordem definida pelo usuário
return columnOrder.filter(column => visibleColumnSet.has(column));
}, [visibleColumns, columnOrder]);
// Resetar a ordem das colunas
const resetColumnOrder = useCallback(() => {
setColumnOrder(allColumns);
}, [allColumns]);
// Função para limpar a notificação de novas colunas
const dismissNewColumnsNotification = useCallback(() => {
setNewColumnsDetected([]);
}, []);
return {
visibleColumns,
columnOrder,
getOrderedVisibleColumns,
resetColumnOrder,
toggleColumn,
selectAllColumns,
unselectAllColumns,
isColumnVisible,
handleDragStart,
handleDragEnd,
handleDragOver,
handleDrop,
newColumnsDetected,
dismissNewColumnsNotification,
};
}

View File

@@ -2,7 +2,7 @@ import { leadtime } from "../components/types";
import { Cliente } from "../components/types"; import { Cliente } from "../components/types";
// URL base da API // URL base da API
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://10.40.0.2:8888"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "https://portalapi.jurunense.com";
/** /**
* Classe de erro personalizada para requisições de API. * Classe de erro personalizada para requisições de API.

View File

@@ -72,3 +72,32 @@ export function buildUrl(
return queryString ? `${baseUrl}?${queryString}` : baseUrl; return queryString ? `${baseUrl}?${queryString}` : baseUrl;
} }
/**
* Limpa os parâmetros da URL atual e navega para uma nova URL
*
* @param path Caminho para navegar (sem parâmetros)
*/
export function clearUrlAndNavigate(path: string): void {
if (typeof window !== 'undefined') {
window.history.replaceState({ path }, "", path);
window.location.href = path;
}
}
/**
* Limpa parâmetros específicos da URL atual
*
* @param paramsToRemove Array de parâmetros para remover
*/
export function clearUrlParams(paramsToRemove: string[]): void {
if (typeof window !== 'undefined') {
const url = new URL(window.location.href);
paramsToRemove.forEach(param => {
url.searchParams.delete(param);
});
window.history.replaceState({ path: url.pathname + url.search }, "", url.pathname + url.search);
}
}