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", "autoprefixer": "^10.4.21",
"babel-jest": "^29.7.0", "babel-jest": "^29.7.0",
"cypress": "^14.3.3", "cypress": "^14.3.3",
"eslint": "^9.32.0",
"eslint-config-next": "15.4.4",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-jsdom": "^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 { User, useAuthValidation } from "../../../hooks/useAuthValidation";
import { SellerSearchInput } from "@/src/components/orders/SellerSearchInput"; import { SellerSearchInput } from "@/src/components/orders/SellerSearchInput";
import { ProductSearchInput } from "@/src/components/orders/ProductSearchInput"; import { ProductSearchInput } from "@/src/components/orders/ProductSearchInput";
import { Checkbox } from "@/src/components/ui/checkbox";
/** /**
* Página de Consulta de Pedidos * Página de Consulta de Pedidos
@@ -53,6 +54,7 @@ export default function FindOrdersPage() {
const [usePreloadClients, setUsePreloadClients] = useState(false); const [usePreloadClients, setUsePreloadClients] = useState(false);
const [status, setStatus] = useState(""); const [status, setStatus] = useState("");
const [leadtime, setLeadtime] = useState(""); const [leadtime, setLeadtime] = useState("");
const [onlyPendingTransfers, setOnlyPendingTransfers] = useState(false);
const { const {
orders, orders,
@@ -62,6 +64,7 @@ export default function FindOrdersPage() {
selectedOrderId, selectedOrderId,
orderItems, orderItems,
cutitens, cutitens,
transfers,
loadingItems, loadingItems,
itemsError, itemsError,
currentPage, currentPage,
@@ -91,6 +94,7 @@ export default function FindOrdersPage() {
*/ */
const handleClearFilters = useCallback(() => { const handleClearFilters = useCallback(() => {
setSearchParams({}); setSearchParams({});
setOnlyPendingTransfers(false);
}, [setSearchParams]); }, [setSearchParams]);
/** /**
@@ -369,6 +373,26 @@ export default function FindOrdersPage() {
aria-label="Período" aria-label="Período"
/> />
</div> </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>
<div className="flex gap-4"> <div className="flex gap-4">
@@ -443,7 +467,7 @@ export default function FindOrdersPage() {
loadMoreOrders={loadMoreOrders} loadMoreOrders={loadMoreOrders}
visibleOrdersCount={visibleOrdersCount} visibleOrdersCount={visibleOrdersCount}
stores={stores} stores={stores}
transfers={[]} transfers={transfers}
/> />
</CardContent> </CardContent>
</Card> </Card>

View File

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

View File

@@ -13,6 +13,7 @@ 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"; import { NewColumnsNotification } from "@/src/components/ui/NewColumnsNotification";
import { OrdersTableFooter } from "./OrdersTableFooter";
export type ColumnKey = export type ColumnKey =
| "Data" | "Data"
@@ -233,6 +234,11 @@ export function OrdersTable({
</div> </div>
</div> </div>
{renderPagination()} {renderPagination()}
<OrdersTableFooter
orders={orders}
currentOrders={sortedItems}
totalOrders={orders.length}
/>
</div> </div>
), [ ), [
orders, 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 { transfer } from "../../types";
import { formatDateToDisplay } from "../../../utils/format-helpers"; import { formatDateToDisplay } from "../../../utils/format-helpers";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/src/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/src/components/ui/table";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/src/components/ui/tooltip";
interface TransferenciaTabProps { interface TransferenciaTabProps {
transfers: transfer[]; transfers: transfer[];
@@ -13,6 +14,30 @@ interface TransferenciaTabProps {
export function TransferenciaTab({ transfers = [] }: TransferenciaTabProps) { export function TransferenciaTab({ transfers = [] }: TransferenciaTabProps) {
const cellClass = "whitespace-nowrap"; 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 ( return (
<div className="border border-gray-300"> <div className="border border-gray-300">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@@ -35,8 +60,8 @@ export function TransferenciaTab({ transfers = [] }: TransferenciaTabProps) {
{transfers && transfers.length > 0 ? ( {transfers && transfers.length > 0 ? (
transfers.map((item, index) => { transfers.map((item, index) => {
// Get ship IDs from either standard or alternative field names // Get ship IDs from either standard or alternative field names
const oldShipId = item.oldShipmentId ?? item.oldshipmentid ?? 0; const oldShipId = item.oldShipmentId ?? item.oldShipment ?? item.oldshipmentid ?? 0;
const newShipId = item.newShipmentId ?? item.newshipmentid ?? 0; const newShipId = item.newShipmentId ?? item.newShipment ?? item.newshipmentid ?? 0;
// Format for display // Format for display
const oldShipmentIdText = oldShipId !== 0 ? String(oldShipId) : "-"; 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`}> <TableCell className={`border-r border-gray-200 p-2 text-xs`}>
{item.cause || "-"} {item.cause || "-"}
</TableCell> </TableCell>
<TableCell className={`border-r border-gray-200 p-2 text-xs max-w-[120px] truncate`}> <TableCell className={`border-r border-gray-200 p-2 text-xs max-w-[120px]`}>
{item.transferText || "-"} <TruncatedText text={item.transferText || "-"} maxLength={25} />
</TableCell> </TableCell>
<TableCell className={`${cellClass} border-r border-gray-200 p-2 text-xs`}> <TableCell className={`${cellClass} border-r border-gray-200 p-2 text-xs`}>
{item.userName} {item.userName}

View File

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

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useRef } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import { ordersApi, dataConsultApi } from "@/src/lib/api"; 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 { validateDateRange } from "@/src/utils/date-helpers";
import { extractApiData } from "@/src/utils/api-helpers"; import { extractApiData } from "@/src/utils/api-helpers";
import { useSearchParams, useRouter } from "next/navigation"; import { useSearchParams, useRouter } from "next/navigation";
@@ -45,6 +45,7 @@ interface UseOrderSearchReturn {
selectedOrderId: string | null; selectedOrderId: string | null;
orderItems: OrderItem[]; orderItems: OrderItem[];
cutitens: cutitens[]; cutitens: cutitens[];
transfers: transfer[];
loadingItems: boolean; loadingItems: boolean;
itemsError: string | null; itemsError: string | null;
currentPage: number; currentPage: number;
@@ -140,6 +141,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
const [selectedOrderId, setSelectedOrderId] = useState<string | null>(null); const [selectedOrderId, setSelectedOrderId] = useState<string | null>(null);
const [orderItems, setOrderItems] = useState<OrderItem[]>([]); const [orderItems, setOrderItems] = useState<OrderItem[]>([]);
const [cutItems, setCutItems] = useState<cutitens[]>([]); const [cutItems, setCutItems] = useState<cutitens[]>([]);
const [transfers, setTransfers] = useState<transfer[]>([]);
const [loadingItems, setLoadingItems] = useState(false); const [loadingItems, setLoadingItems] = useState(false);
const [itemsError, setItemsError] = useState<string | null>(null); const [itemsError, setItemsError] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
@@ -337,6 +339,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null); setSelectedOrderId(null);
setOrderItems([]); setOrderItems([]);
setCutItems([]); setCutItems([]);
setTransfers([]);
// Reset pagination state // Reset pagination state
setCurrentPage(1); setCurrentPage(1);
@@ -414,6 +417,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null); setSelectedOrderId(null);
setOrderItems([]); setOrderItems([]);
setCutItems([]); setCutItems([]);
setTransfers([]);
return; return;
} }
@@ -438,6 +442,25 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
} }
setCutItems(cutItems); 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) { } catch (err) {
setItemsError("Erro ao carregar itens do pedido."); setItemsError("Erro ao carregar itens do pedido.");
console.error(err); console.error(err);
@@ -464,6 +487,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null); setSelectedOrderId(null);
setOrderItems([]); setOrderItems([]);
setCutItems([]); setCutItems([]);
setTransfers([]);
} }
}, [currentPage, totalPages]); }, [currentPage, totalPages]);
@@ -475,6 +499,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null); setSelectedOrderId(null);
setOrderItems([]); setOrderItems([]);
setCutItems([]); setCutItems([]);
setTransfers([]);
} }
}, [currentPage]); }, [currentPage]);
@@ -492,6 +517,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
setSelectedOrderId(null); setSelectedOrderId(null);
setOrderItems([]); setOrderItems([]);
setCutItems([]); setCutItems([]);
setTransfers([]);
} }
}, [totalPages]); }, [totalPages]);
@@ -704,6 +730,7 @@ export function useOrderSearch(ordersPerPage: number = 8): UseOrderSearchReturn
selectedOrderId, selectedOrderId,
orderItems, orderItems,
cutitens: cutItems, cutitens: cutItems,
transfers,
loadingItems, loadingItems,
itemsError, itemsError,
currentPage, currentPage,

View File

@@ -3,7 +3,7 @@ import { Cliente } from "../components/types";
// URL base da API // 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 || "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";