ajuste na grid

This commit is contained in:
JuruSysadmin
2025-07-25 17:32:52 -03:00
parent 85d202298f
commit 17cdcf4b1e
12 changed files with 2158 additions and 13 deletions

6
.eslintrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}

View File

@@ -122,6 +122,8 @@
"autoprefixer": "^10.4.21",
"babel-jest": "^29.7.0",
"cypress": "^14.3.3",
"eslint": "^9.32.0",
"eslint-config-next": "15.4.4",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",

1858
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,7 @@ import { useCallback, useState, useEffect } from "react";
import { User, useAuthValidation } from "../../../hooks/useAuthValidation";
import { SellerSearchInput } from "@/src/components/orders/SellerSearchInput";
import { ProductSearchInput } from "@/src/components/orders/ProductSearchInput";
import { Checkbox } from "@/src/components/ui/checkbox";
/**
* Página de Consulta de Pedidos
@@ -53,6 +54,7 @@ export default function FindOrdersPage() {
const [usePreloadClients, setUsePreloadClients] = useState(false);
const [status, setStatus] = useState("");
const [leadtime, setLeadtime] = useState("");
const [onlyPendingTransfers, setOnlyPendingTransfers] = useState(false);
const {
orders,
@@ -62,6 +64,7 @@ export default function FindOrdersPage() {
selectedOrderId,
orderItems,
cutitens,
transfers,
loadingItems,
itemsError,
currentPage,
@@ -91,6 +94,7 @@ export default function FindOrdersPage() {
*/
const handleClearFilters = useCallback(() => {
setSearchParams({});
setOnlyPendingTransfers(false);
}, [setSearchParams]);
/**
@@ -369,6 +373,26 @@ export default function FindOrdersPage() {
aria-label="Período"
/>
</div>
<div className="filter-field">
<div className="flex items-center space-x-2">
<Checkbox
id="onlyPendingTransfers"
checked={onlyPendingTransfers}
onCheckedChange={(checked) => {
setOnlyPendingTransfers(checked as boolean);
handleInputChange("onlyPendingTransfer", checked ? "S" : "");
}}
aria-label="Somente transferências pendentes de entrada"
/>
<label
htmlFor="onlyPendingTransfers"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Somente transferências pendentes de entrada
</label>
</div>
</div>
</div>
<div className="flex gap-4">
@@ -443,7 +467,7 @@ export default function FindOrdersPage() {
loadMoreOrders={loadMoreOrders}
visibleOrdersCount={visibleOrdersCount}
stores={stores}
transfers={[]}
transfers={transfers}
/>
</CardContent>
</Card>

View File

@@ -172,8 +172,8 @@ export function OrderDetail({ order, timelineEvents }: OrderDetailProps) {
setTransfers(
items.map(item => ({
...item,
oldShipmentId: Number(item.oldShipmentId ?? 0),
newShipmentId: Number(item.newShipmentId ?? 0),
oldShipmentId: Number(item.oldShipmentId ?? item.oldShipment ?? 0),
newShipmentId: Number(item.newShipmentId ?? item.newShipment ?? 0),
}))
);
} catch {}

View File

@@ -13,6 +13,7 @@ import { RefreshCw, ChevronLeft, ChevronRight } from "lucide-react";
import { useOrderData } from "../../../src/hooks/useOrderData";
import { useSort } from "@/src/hooks/useSort";
import { NewColumnsNotification } from "@/src/components/ui/NewColumnsNotification";
import { OrdersTableFooter } from "./OrdersTableFooter";
export type ColumnKey =
| "Data"
@@ -233,6 +234,11 @@ export function OrdersTable({
</div>
</div>
{renderPagination()}
<OrdersTableFooter
orders={orders}
currentOrders={sortedItems}
totalOrders={orders.length}
/>
</div>
), [
orders,

View File

@@ -0,0 +1,96 @@
import { Order } from "@/src/components/types";
import { useMemo } from "react";
import { Calculator, Package, DollarSign } from "lucide-react";
interface OrdersTableFooterProps {
orders: Order[];
currentOrders: Order[];
totalOrders: number;
}
/**
* Componente de rodapé compacto para exibir totais da tabela de pedidos
*/
export function OrdersTableFooter({ orders, currentOrders, totalOrders }: OrdersTableFooterProps) {
const totals = useMemo(() => {
// Calcular totais apenas dos pedidos da página atual
const totalValue = currentOrders.reduce((sum, order) => {
// Debug: verificar os valores disponíveis
console.log('Order ID:', order.orderId, 'totalValue:', order.totalValue, 'amount:', order.amount, 'totalValue type:', typeof order.totalValue);
let value = 0;
if (typeof order.totalValue === 'number' && order.totalValue > 0) {
value = order.totalValue;
} else if (typeof order.amount === 'number' && order.amount > 0) {
value = order.amount;
} else if (typeof order.totalValue === 'string' && order.totalValue) {
value = parseFloat(order.totalValue) || 0;
} else if (typeof order.amount === 'string' && order.amount) {
value = parseFloat(order.amount) || 0;
}
return sum + value;
}, 0);
const totalWeight = currentOrders.reduce((sum, order) => {
let weight = 0;
if (order.totalWeigth) {
if (typeof order.totalWeigth === 'number') {
weight = order.totalWeigth;
} else if (typeof order.totalWeigth === 'string') {
weight = parseFloat(order.totalWeigth) || 0;
}
}
return sum + weight;
}, 0);
console.log('Totals calculated:', { totalValue, totalWeight, orderCount: currentOrders.length });
return {
totalValue,
totalWeight,
orderCount: currentOrders.length,
totalOrders
};
}, [currentOrders, totalOrders]);
// Formatar valor em reais
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(value);
};
// Formatar peso em kg
const formatWeight = (weight: number) => {
return new Intl.NumberFormat('pt-BR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(weight);
};
if (currentOrders.length === 0) {
return null;
}
return (
<div className="bg-gray-50 border-t border-gray-200 px-4 py-3">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-2 text-gray-600">
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1 text-gray-700">
<span className="font-medium">Valor Total : {formatCurrency(totals.totalValue)}</span>
</div>
<div className="flex items-center gap-1 text-white-700">
<span className="font-medium">Peso Total : {formatWeight(totals.totalWeight)} kg</span>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,97 @@
import { Order } from "@/src/components/types";
import { useMemo } from "react";
import { Card, CardContent } from "@/src/components/ui/card";
import { Badge } from "@/src/components/ui/badge";
import { Calculator, Package, DollarSign } from "lucide-react";
interface OrdersTableSummaryProps {
orders: Order[];
currentOrders: Order[];
totalOrders: number;
}
/**
* Componente que exibe o resumo dos totais de valor e peso dos pedidos
*/
export function OrdersTableSummary({ orders, currentOrders, totalOrders }: OrdersTableSummaryProps) {
const totals = useMemo(() => {
// Calcular totais apenas dos pedidos da página atual
const totalValue = currentOrders.reduce((sum, order) => {
const value = typeof order.totalValue === 'number' ? order.totalValue : 0;
return sum + value;
}, 0);
const totalWeight = currentOrders.reduce((sum, order) => {
let weight = 0;
if (order.totalWeigth) {
if (typeof order.totalWeigth === 'number') {
weight = order.totalWeigth;
} else if (typeof order.totalWeigth === 'string') {
weight = parseFloat(order.totalWeigth) || 0;
}
}
return sum + weight;
}, 0);
return {
totalValue,
totalWeight,
orderCount: currentOrders.length,
totalOrders
};
}, [currentOrders, totalOrders]);
// Formatar valor em reais
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(value);
};
// Formatar peso em kg
const formatWeight = (weight: number) => {
return new Intl.NumberFormat('pt-BR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(weight);
};
if (currentOrders.length === 0) {
return null;
}
return (
<Card className="mt-4 border-t-2 border-blue-200 bg-blue-50">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Calculator className="h-5 w-5 text-blue-600" />
<span className="text-sm font-medium text-gray-700">
Resumo da página atual: {totals.orderCount} pedido{totals.orderCount !== 1 ? 's' : ''}
(Total: {totals.totalOrders} pedido{totals.totalOrders !== 1 ? 's' : ''})
</span>
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<DollarSign className="h-4 w-4 text-green-600" />
<span className="text-sm font-medium text-gray-700">Total Valor:</span>
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200">
{formatCurrency(totals.totalValue)}
</Badge>
</div>
<div className="flex items-center gap-2">
<Package className="h-4 w-4 text-orange-600" />
<span className="text-sm font-medium text-gray-700">Total Peso:</span>
<Badge variant="outline" className="bg-orange-50 text-orange-700 border-orange-200">
{formatWeight(totals.totalWeight)} kg
</Badge>
</div>
</div>
</div>
</CardContent>
</Card>
);
}

View File

@@ -1,6 +1,7 @@
import { transfer } from "../../types";
import { formatDateToDisplay } from "../../../utils/format-helpers";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/src/components/ui/table";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/src/components/ui/tooltip";
interface TransferenciaTabProps {
transfers: transfer[];
@@ -13,6 +14,30 @@ interface TransferenciaTabProps {
export function TransferenciaTab({ transfers = [] }: TransferenciaTabProps) {
const cellClass = "whitespace-nowrap";
// Componente para renderizar texto com tooltip quando necessário
const TruncatedText = ({ text, maxLength = 30 }: { text: string; maxLength?: number }) => {
const isLong = text.length > maxLength;
if (!isLong) {
return <span>{text}</span>;
}
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-help">
{text.substring(0, maxLength)}...
</span>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<p className="whitespace-pre-wrap">{text}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};
return (
<div className="border border-gray-300">
<div className="overflow-x-auto">
@@ -35,8 +60,8 @@ export function TransferenciaTab({ transfers = [] }: TransferenciaTabProps) {
{transfers && transfers.length > 0 ? (
transfers.map((item, index) => {
// Get ship IDs from either standard or alternative field names
const oldShipId = item.oldShipmentId ?? item.oldshipmentid ?? 0;
const newShipId = item.newShipmentId ?? item.newshipmentid ?? 0;
const oldShipId = item.oldShipmentId ?? item.oldShipment ?? item.oldshipmentid ?? 0;
const newShipId = item.newShipmentId ?? item.newShipment ?? item.newshipmentid ?? 0;
// Format for display
const oldShipmentIdText = oldShipId !== 0 ? String(oldShipId) : "-";
@@ -65,8 +90,8 @@ export function TransferenciaTab({ transfers = [] }: TransferenciaTabProps) {
<TableCell className={`border-r border-gray-200 p-2 text-xs`}>
{item.cause || "-"}
</TableCell>
<TableCell className={`border-r border-gray-200 p-2 text-xs max-w-[120px] truncate`}>
{item.transferText || "-"}
<TableCell className={`border-r border-gray-200 p-2 text-xs max-w-[120px]`}>
<TruncatedText text={item.transferText || "-"} maxLength={25} />
</TableCell>
<TableCell className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.userName}

View File

@@ -53,6 +53,8 @@ export interface transfer {
program: string;
oldshipmentid?: number;
newshipmentid?: number;
oldShipment?: number;
newShipment?: number;
}
export interface leadtime{
@@ -279,6 +281,8 @@ export interface OrderSearchParams {
productId?: string;
/** Produto selecionado pelo componente de busca */
selectedProduct?: Product | null;
/** Filtro para mostrar apenas transferências pendentes de entrada */
onlyPendingTransfer?: string;
}
export interface CustomerSearchParams {

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { ordersApi, dataConsultApi } from "@/src/lib/api";
import { OrderSearchParams, OrderItem, Customer, Order, Cliente, cutitens } from "@/src/components/types";
import { OrderSearchParams, OrderItem, Customer, Order, Cliente, cutitens, transfer } from "@/src/components/types";
import { validateDateRange } from "@/src/utils/date-helpers";
import { extractApiData } from "@/src/utils/api-helpers";
import { useSearchParams, useRouter } from "next/navigation";
@@ -45,6 +45,7 @@ interface UseOrderSearchReturn {
selectedOrderId: string | null;
orderItems: OrderItem[];
cutitens: cutitens[];
transfers: transfer[];
loadingItems: boolean;
itemsError: string | null;
currentPage: number;
@@ -140,6 +141,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
const [selectedOrderId, setSelectedOrderId] = useState<string | null>(null);
const [orderItems, setOrderItems] = useState<OrderItem[]>([]);
const [cutItems, setCutItems] = useState<cutitens[]>([]);
const [transfers, setTransfers] = useState<transfer[]>([]);
const [loadingItems, setLoadingItems] = useState(false);
const [itemsError, setItemsError] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1);
@@ -337,6 +339,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null);
setOrderItems([]);
setCutItems([]);
setTransfers([]);
// Reset pagination state
setCurrentPage(1);
@@ -414,6 +417,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null);
setOrderItems([]);
setCutItems([]);
setTransfers([]);
return;
}
@@ -438,6 +442,25 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
}
setCutItems(cutItems);
// Load transfers
const transfersResponse = await ordersApi.getTransfer(parseInt(orderId));
let transfers: transfer[] = [];
if (transfersResponse && typeof transfersResponse === "object") {
if ("data" in transfersResponse && Array.isArray(transfersResponse.data)) {
transfers = transfersResponse.data;
} else if (Array.isArray(transfersResponse)) {
transfers = transfersResponse;
}
}
// Apply the same mapping as in OrderDetail
setTransfers(
transfers.map(item => ({
...item,
oldShipmentId: Number(item.oldShipmentId ?? item.oldShipment ?? 0),
newShipmentId: Number(item.newShipmentId ?? item.newShipment ?? 0),
}))
);
} catch (err) {
setItemsError("Erro ao carregar itens do pedido.");
console.error(err);
@@ -464,6 +487,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null);
setOrderItems([]);
setCutItems([]);
setTransfers([]);
}
}, [currentPage, totalPages]);
@@ -475,6 +499,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null);
setOrderItems([]);
setCutItems([]);
setTransfers([]);
}
}, [currentPage]);
@@ -492,6 +517,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null);
setOrderItems([]);
setCutItems([]);
setTransfers([]);
}
}, [totalPages]);
@@ -704,6 +730,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
selectedOrderId,
orderItems,
cutitens: cutItems,
transfers,
loadingItems,
itemsError,
currentPage,

View File

@@ -3,7 +3,7 @@ import { Cliente } from "../components/types";
// URL base da API
//const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "https://portalapi.jurunense.com";
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://10.1.1.212:8805";
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://10.1.1.212:8807";