This commit is contained in:
JuruSysadmin
2025-06-17 13:41:48 -03:00
commit af154c3f7f
197 changed files with 50658 additions and 0 deletions

View File

@@ -0,0 +1,466 @@
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/src/components/ui/table";
import { Button } from "@/src/components/ui/button";
import { Eye, GripVertical } from "lucide-react";
import { OrderItem } from "@/src/components/types";
import { cellClass } from "@/src/constants/status-options";
import { formatCurrency } from "@/src/utils/format";
import { useState, useEffect, } from "react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuTrigger,
DropdownMenuCheckboxItem,
} from "@/src/components/ui/dropdown-menu";
interface Item {
quantity: number | null | undefined;
}
const displayQuantity = (item?: Item) => {
if (!item?.quantity && item?.quantity !== 0) {
return "-";
}
return item.quantity >= 0 ? item.quantity.toString() : "0";
};
const ALL_COLUMNS = [
"Código",
"Descrição",
"Embalagem",
"Cor",
"F. Estoque",
"Tipo Entrega",
"Quantidade",
"Preço Unitário",
"Preço Total",
"Departamento",
"Marca",
];
interface OrderItemsTableProps {
orderItems: OrderItem[];
loadingItems: boolean;
itemsError: string | null;
onViewDetails?: (item: OrderItem) => void;
}
export function OrderItemsTable({
orderItems,
loadingItems,
itemsError,
onViewDetails,
}: OrderItemsTableProps) {
// Carregar as colunas visíveis do localStorage na inicialização
const initVisibleColumns = () => {
try {
const saved = localStorage.getItem('visibleOrderItemColumns');
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 ALL_COLUMNS;
};
// Inicializar a ordem das colunas
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 element = e.currentTarget;
if (element instanceof HTMLElement) {
const timeoutId = setTimeout(() => {
element.classList.add('opacity-50');
}, 0);
// Limpeza em caso de desmontagem
return () => clearTimeout(timeoutId);
}
};
if (loadingItems) {
return <div className="py-4 text-center">Carregando itens...</div>;
}
if (itemsError) {
return <div className="py-2 text-red-500">{itemsError}</div>;
}
if (orderItems.length === 0) {
return <div className="py-2">Nenhum item encontrado para este pedido.</div>;
}
const orderedVisibleColumns = getOrderedVisibleColumns();
return (
<div className="rounded-md border-0">
<div className="flex justify-end mb-2 gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 p-2 bg-white shadow-lg border border-gray-200">
<DropdownMenuLabel className="text-sm font-bold pb-2 mb-2 border-b border-gray-200">Visibilidade das Colunas</DropdownMenuLabel>
<div className="flex justify-between mb-2 border-b pb-2">
<Button
variant="ghost"
size="sm"
onClick={selectAllColumns}
className="text-xs h-7 hover:bg-gray-100"
>
Marcar Todas
</Button>
<Button
variant="ghost"
size="sm"
onClick={unselectAllColumns}
className="text-xs h-7 hover:bg-gray-100"
>
Desmarcar Todas
</Button>
</div>
<div className="max-h-[300px] overflow-y-auto">
{ALL_COLUMNS.map((column) => (
<DropdownMenuCheckboxItem
key={column}
checked={isColumnVisible(column)}
onCheckedChange={() => toggleColumn(column)}
className="flex items-center space-x-2 cursor-pointer py-1.5 hover:bg-gray-100 px-2 rounded"
>
<div className="flex items-center gap-2 text-sm">
{isColumnVisible(column) ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 text-blue-600"
>
<rect width="16" height="16" x="4" y="4" rx="2" />
<path d="m9 12 2 2 4-4" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 text-gray-400"
>
<rect width="16" height="16" x="4" y="4" rx="2" />
</svg>
)}
{column}
</div>
</DropdownMenuCheckboxItem>
))}
</div>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="border border-gray-300">
<div className="overflow-x-auto">
<div className="max-h-[600px] overflow-y-auto">
<Table className="border-collapse border-gray-200">
<TableHeader className="sticky top-0 z-10 bg-white border-b border-gray-200">
<TableRow className="hover:bg-transparent">
{orderedVisibleColumns.map((title, index, array) => (
<TableHead
key={index}
className={`text-xs font-medium text-gray-600 p-2 ${getColumnClass(title)} ${
index < array.length - 1 ? "border-r border-gray-200" : ""
} relative`}
draggable={true}
onDragStart={(e) => handleDragStart(e, title)}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, title)}
>
<div className="flex items-center">
<GripVertical className="h-3 w-3 mr-1 cursor-move text-gray-400" />
{title}
</div>
</TableHead>
))}
{onViewDetails && (
<TableHead className="text-xs font-medium text-gray-600 p-2 w-16">
Ações
</TableHead>
)}
</TableRow>
</TableHeader>
<TableBody>
{orderItems.map((item, index) => (
<TableRow
key={`${item.productId}-${index}`}
className="odd:bg-white even:bg-gray-50 hover:bg-gray-100"
>
{orderedVisibleColumns.map((columnName, colIndex) => {
switch (columnName) {
case "Código":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.productId ?? "-"}
</TableCell>
);
case "Descrição":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.description ?? "-"}
</TableCell>
);
case "Embalagem":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.pacth ?? "-"}
</TableCell>
);
case "Cor":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.color ?? "-"}
</TableCell>
);
case "F. Estoque":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.stockId ?? "-"}
</TableCell>
);
case "Tipo Entrega":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.deliveryType ?? "-"}
</TableCell>
);
case "Quantidade":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-right text-xs`}>
{displayQuantity(item)}
</TableCell>
);
case "Preço Unitário":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-right text-xs`}>
{formatCurrency(item.salePrice)}
</TableCell>
);
case "Preço Total":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-right text-xs`}>
{item.total != null ? formatCurrency(item.total) : "-"}
</TableCell>
);
case "Departamento":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
<div className="break-words hyphens-auto w-full">
{item.department ?? "-"}
</div>
</TableCell>
);
case "Marca":
return (
<TableCell key={colIndex} className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.brand ?? "-"}
</TableCell>
);
default:
return null;
}
})}
{onViewDetails && (
<TableCell className="p-2">
<Button
variant="ghost"
size="icon"
onClick={() => onViewDetails(item)}
title="Ver detalhes do item"
className="h-7 w-7"
>
<Eye className="h-3.5 w-3.5" />
</Button>
</TableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</div>
</div>
);
}