425 lines
16 KiB
TypeScript
425 lines
16 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/src/components/ui/card";
|
|
import { Button } from "@/src/components/ui/button";
|
|
import { Search, RefreshCw, ChevronUp, ChevronDown } from "lucide-react";
|
|
import { OrdersTable } from "@/src/components/orders/OrdersTable";
|
|
import { ErrorMessage } from "@/src/components/ui/error-message";
|
|
import { useStores } from "@/src/hooks/useStores";
|
|
import { useOrderSearch } from "@/src/hooks/useOrderSearch";
|
|
import { FilterInput } from "@/src/components/orders/FilterInput";
|
|
import { FilterSelect } from "@/src/components/orders/FilterSelect";
|
|
import { STATUS_OPTIONS } from "@/src/constants/status-options";
|
|
import { DELIVERY_STATUS_OPTIONS } from "@/src/constants/delivery-status-options";
|
|
import { Spinner } from "@/src/components/ui/spinner";
|
|
import { DateRangeFilter } from "@/src/components/orders/DateRangeFilter";
|
|
import { MultiFilterSelect } from "@/src/components/orders/MultiFilterSelect";
|
|
import { CustomerSearchInput } from "@/src/components/orders/CustomerSearchInput";
|
|
import { useCallback, useState, useEffect } from "react";
|
|
import { User, useAuthValidation } from "../../../hooks/useAuthValidation";
|
|
import { SellerSearchInput } from "@/src/components/orders/SellerSearchInput";
|
|
|
|
/**
|
|
* Página de Consulta de Pedidos
|
|
* Permite ao usuário pesquisar pedidos com base em vários filtros
|
|
*
|
|
* @returns Componente da página de consulta de pedidos
|
|
*/
|
|
|
|
export default function FindOrdersPage() {
|
|
const {
|
|
user,
|
|
isAuthenticated,
|
|
isLoading: authLoading,
|
|
error: authError,
|
|
decodedToken,
|
|
} = useAuthValidation({
|
|
authServiceUrl: process.env.NEXT_PUBLIC_AUTH_SERVICE_URL!,
|
|
token: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMzg1IiwidXNlcm5hbWUiOiJKT0VMU09OLlIiLCJyb2xlIjpudWxsLCJpYXQiOjE3NDg4MjQzNDEsImV4cCI6MTc1MzIzMDc0MX0.H4nRp72NTEzHBKij67BgW4NO8Pot9AqPKlaynne694c",
|
|
autoRedirect: false,
|
|
});
|
|
|
|
|
|
|
|
|
|
const [isFiltersExpanded, setIsFiltersExpanded] = useState(true);
|
|
const { storeOptions, loading: loadingStores, stores } = useStores(true);
|
|
const [usePreloadClients, setUsePreloadClients] = useState(false);
|
|
const [status, setStatus] = useState("");
|
|
const [leadtime, setLeadtime] = useState("");
|
|
|
|
const {
|
|
orders,
|
|
loading,
|
|
dateError,
|
|
hasSearched,
|
|
selectedOrderId,
|
|
orderItems,
|
|
cutitens,
|
|
loadingItems,
|
|
itemsError,
|
|
currentPage,
|
|
totalPages,
|
|
currentOrders,
|
|
indexOfFirstOrder,
|
|
indexOfLastOrder,
|
|
searchParams,
|
|
setSearchParams,
|
|
handleSearch,
|
|
handleRowClick,
|
|
goToNextPage,
|
|
goToPreviousPage,
|
|
goToPage,
|
|
loadMoreOrders,
|
|
visibleOrdersCount,
|
|
handleInputChange,
|
|
handleCustomerId,
|
|
handleCustomerFilter,
|
|
handleCustomerSelect,
|
|
handleMultiInputChange,
|
|
} = useOrderSearch(8);
|
|
|
|
/**
|
|
* Limpa todos os filtros de pesquisa
|
|
* @returns {void}
|
|
*/
|
|
const handleClearFilters = useCallback(() => {
|
|
setSearchParams({});
|
|
}, [setSearchParams]);
|
|
|
|
/**
|
|
* Alterna o modo de pré-carregamento de clientes
|
|
* Quando ativado, carrega clientes antecipadamente para melhorar o desempenho da busca
|
|
* @returns {void}
|
|
*/
|
|
const togglePreloadMode = useCallback(() => {
|
|
setUsePreloadClients((prev) => !prev);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="space-y-1">
|
|
<Card className="overflow-hidden">
|
|
<CardHeader
|
|
className="flex flex-row items-center justify-between space-y-0 py-1 px-4 white-slate-80 cursor-pointer"
|
|
onClick={() => setIsFiltersExpanded(!isFiltersExpanded)}
|
|
>
|
|
<CardTitle className="text-lg font-bold">
|
|
Consulta de Pedidos
|
|
</CardTitle>
|
|
<Button variant="ghost" size="sm" className="h-6 px-1">
|
|
{isFiltersExpanded ? (
|
|
<ChevronUp className="h-4 w-4" />
|
|
) : (
|
|
<ChevronDown className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
</CardHeader>
|
|
|
|
{isFiltersExpanded && (
|
|
<CardContent className="p-0">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2 gap-y-2 items-start p-2">
|
|
<div className="flex flex-col space-y-4">
|
|
<div className="filter-field">
|
|
<FilterSelect
|
|
id="codfilial"
|
|
label="Filial de Venda"
|
|
value={searchParams.codfilial}
|
|
onValueChange={(value) =>
|
|
handleInputChange("codfilial", value)
|
|
}
|
|
placeholder="Selecione a filial"
|
|
disabled={loadingStores}
|
|
options={storeOptions}
|
|
loading={loadingStores}
|
|
loadingText="Carregando filiais..."
|
|
className="w-full"
|
|
aria-label="Filial de Venda"
|
|
/>
|
|
</div>
|
|
<div className="filter-field">
|
|
<CustomerSearchInput
|
|
id="customerSearch"
|
|
label="Cliente"
|
|
placeholder="Buscar por codigo, nome ou CPF/CNPJ"
|
|
value={
|
|
searchParams.name ||
|
|
searchParams.customerId ||
|
|
searchParams.document ||
|
|
""
|
|
}
|
|
onChange={handleCustomerFilter}
|
|
onSelect={handleCustomerSelect}
|
|
selectedCustomer={searchParams.selectedCustomer || null}
|
|
preloadClients={usePreloadClients}
|
|
maxPreloadedClients={2000}
|
|
aria-label="Buscar Cliente"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex gap-4">
|
|
<div className="filter-small-field">
|
|
<FilterInput
|
|
id="orderId"
|
|
label="Número do Pedido"
|
|
type="number"
|
|
placeholder="Número do pedido"
|
|
value={searchParams.orderId}
|
|
onChange={(value) => handleInputChange("orderId", value)}
|
|
className="w-full"
|
|
aria-label="Número do Pedido"
|
|
/>
|
|
</div>
|
|
<div className="filter-small-field">
|
|
<FilterInput
|
|
id="invoiceNumber"
|
|
label="Nota Fiscal"
|
|
placeholder="Número da NF"
|
|
value={searchParams.invoiceNumber || ""}
|
|
onChange={(value) =>
|
|
handleInputChange("invoiceNumber", value)
|
|
}
|
|
className="w-full"
|
|
aria-label="Número da Nota Fiscal"
|
|
/>
|
|
</div>
|
|
<div className="filter-small-field">
|
|
<FilterInput
|
|
id="shippimentId"
|
|
label=" Carregamento"
|
|
placeholder="Carregamento"
|
|
value={searchParams.shippimentId || ""}
|
|
onChange={(value) =>
|
|
handleInputChange("shippimentId", value)
|
|
}
|
|
className="w-full"
|
|
aria-label="Carregamento"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<div className="filter-small-field">
|
|
<MultiFilterSelect
|
|
id="orderType"
|
|
label="Tipo de Venda"
|
|
placeholder="Tipo de venda"
|
|
values={
|
|
searchParams.type
|
|
? Array.isArray(searchParams.type)
|
|
? searchParams.type
|
|
: [searchParams.type]
|
|
: []
|
|
}
|
|
onValuesChange={(values) =>
|
|
handleMultiInputChange("type", values)
|
|
}
|
|
options={[
|
|
{ label: "TV1 - Retira Imediata", value: "1" },
|
|
{ label: "TV7 - Faturamento", value: "7" },
|
|
{ label: "TV8 - Entrega", value: "8" },
|
|
{ label: "TV10 - Transferência", value: "10" },
|
|
]}
|
|
className="w-full"
|
|
aria-label="Tipo de Venda"
|
|
/>
|
|
</div>
|
|
<div className="filter-small-field">
|
|
<MultiFilterSelect
|
|
id="deliveryType"
|
|
label="Tipo de Entrega"
|
|
placeholder="Tipo de entrega"
|
|
values={
|
|
searchParams.deliveryType
|
|
? Array.isArray(searchParams.deliveryType)
|
|
? searchParams.deliveryType
|
|
: [searchParams.deliveryType]
|
|
: []
|
|
}
|
|
onValuesChange={(values) =>
|
|
handleMultiInputChange("deliveryType", values)
|
|
}
|
|
options={[
|
|
{ label: "Retira Imediata", value: "RI" },
|
|
{ label: "Entrega", value: "EN" },
|
|
{ label: "Entrega Futura", value: "EF" },
|
|
{ label: "Retira Posterior", value: "RP" },
|
|
]}
|
|
className="w-full"
|
|
aria-label="Tipo de Entrega"
|
|
/>
|
|
</div>
|
|
<div className="filter-small-field">
|
|
<MultiFilterSelect
|
|
id="status"
|
|
label="Situação"
|
|
values={
|
|
Array.isArray(searchParams.status)
|
|
? searchParams.status
|
|
: searchParams.status
|
|
? [searchParams.status]
|
|
: []
|
|
}
|
|
onValuesChange={(values) =>
|
|
handleMultiInputChange("status", values)
|
|
}
|
|
placeholder=" Situação"
|
|
options={STATUS_OPTIONS}
|
|
className="w-full"
|
|
aria-label="Situação do Pedido"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col space-y-4">
|
|
<div className="filter-field">
|
|
<FilterSelect
|
|
id="stockId"
|
|
label="Filial de Estoque"
|
|
value={searchParams.stockId}
|
|
onValueChange={(value) =>
|
|
handleInputChange("stockId", value)
|
|
}
|
|
placeholder="Filial de estoque"
|
|
options={storeOptions}
|
|
loading={loadingStores}
|
|
loadingText="Carregando filiais..."
|
|
className="w-full"
|
|
aria-label="Filial de Estoque"
|
|
/>
|
|
</div>
|
|
|
|
<div className="filter-field">
|
|
<SellerSearchInput
|
|
id="sellerSearch"
|
|
label="Nome do Vendedor"
|
|
placeholder="Buscar por nome ou código do vendedor"
|
|
value={searchParams.sellerName || ""}
|
|
onChange={(value) => handleInputChange("sellerName", value)}
|
|
onSelect={(seller) => {
|
|
if (seller) {
|
|
if (seller.code) {
|
|
handleInputChange("sellerId", seller.code);
|
|
} else {
|
|
handleInputChange("sellerId", seller.id.toString());
|
|
}
|
|
handleInputChange("sellerName", seller.name);
|
|
} else {
|
|
handleInputChange("sellerId", "");
|
|
handleInputChange("sellerName", "");
|
|
}
|
|
}}
|
|
selectedSeller={searchParams.sellerName ? {
|
|
id: parseInt(String(searchParams.sellerId || "0"), 10) || 0,
|
|
name: searchParams.sellerName,
|
|
code: (searchParams.sellerId || "").toString()
|
|
} : undefined}
|
|
aria-label="Buscar Vendedor"
|
|
/>
|
|
</div>
|
|
|
|
<div className="filter-field">
|
|
<DateRangeFilter
|
|
startDateId="createDateIni"
|
|
endDateId="createDateEnd"
|
|
label="Período (Obrigatório)"
|
|
startValue={searchParams.createDateIni}
|
|
endValue={searchParams.createDateEnd}
|
|
onStartChange={(value) =>
|
|
handleInputChange("createDateIni", value)
|
|
}
|
|
onEndChange={(value) =>
|
|
handleInputChange("createDateEnd", value)
|
|
}
|
|
aria-label="Período"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-4">
|
|
<div className="flex flex-col space-y-4"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-end gap-2 mx-4 pb-3">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleClearFilters}
|
|
title="Limpar filtros"
|
|
aria-label="Limpar todos os filtros de pesquisa"
|
|
className="h-8"
|
|
>
|
|
<RefreshCw className="mr-2 h-4 w-4" />
|
|
Limpar
|
|
</Button>
|
|
|
|
<Button
|
|
variant={"blue"}
|
|
onClick={handleSearch}
|
|
disabled={loading}
|
|
className="h-8 px-4"
|
|
aria-label="Pesquisar pedidos com os filtros selecionados"
|
|
>
|
|
{loading ? (
|
|
<Spinner size="sm" />
|
|
) : (
|
|
<Search className="mr-2 h-4 w-4" />
|
|
)}
|
|
Pesquisar
|
|
</Button>
|
|
</div>
|
|
|
|
{dateError && <ErrorMessage message={dateError} />}
|
|
</CardContent>
|
|
)}
|
|
</Card>
|
|
|
|
{loading ? (
|
|
<Card className="overflow-hidden">
|
|
<CardContent className="flex h-40 items-center justify-center p-4">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<Spinner size="md" />
|
|
<p className="text-sm text-muted-foreground">
|
|
Buscando pedidos...
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
) : hasSearched ? (
|
|
<Card className="overflow-hidden">
|
|
<CardContent className="p-0">
|
|
<OrdersTable
|
|
orders={orders}
|
|
currentOrders={currentOrders}
|
|
selectedOrderId={selectedOrderId}
|
|
orderItems={orderItems}
|
|
cutitens={cutitens}
|
|
loadingItems={loadingItems}
|
|
itemsError={itemsError}
|
|
handleRowClick={handleRowClick}
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
indexOfFirstOrder={indexOfFirstOrder}
|
|
indexOfLastOrder={indexOfLastOrder}
|
|
goToPreviousPage={goToPreviousPage}
|
|
goToNextPage={goToNextPage}
|
|
goToPage={goToPage}
|
|
loadMoreOrders={loadMoreOrders}
|
|
visibleOrdersCount={visibleOrdersCount}
|
|
stores={stores}
|
|
transfers={[]}
|
|
status={status}
|
|
leadtime={leadtime}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|