Improve users and purchase needs UI
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 35s

This commit is contained in:
Cauê Faleiros
2026-07-16 12:56:11 -03:00
parent 897d4cbe66
commit 4bfd0d4591
2 changed files with 411 additions and 232 deletions

View File

@@ -23,6 +23,7 @@ import {
Warehouse,
} from 'lucide-react';
import { buildConsumptionReferencePath } from '../catalogLinks';
import PaginationControls from '../components/PaginationControls';
import { adjustSupplyLotInventory, approveSupplyReceipt, consumeSupplyLotForProduction, createSupplyFabricPlan, createSupplyReceipt, deleteSupplyFabricPlan, deleteSupplyReceipt, fetchSupplySummary } from '../dataService';
import type { SupplyFabricPlan, SupplyLot, SupplyMovement, SupplyPurchaseNeed, SupplyReceipt, SupplySummary } from '../types';
@@ -1148,6 +1149,8 @@ const PurchaseNeedsScreen = () => {
const [summary, setSummary] = useState<SupplySummary>(emptySupplySummary);
const [isLoading, setIsLoading] = useState(true);
const [search, setSearch] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
const loadSummary = async () => {
const nextSummary = await fetchSupplySummary();
@@ -1178,6 +1181,10 @@ const PurchaseNeedsScreen = () => {
const visibleNeeds = summary.purchaseNeeds.filter(need => (
!normalizedSearch || normalizeSearch(`${need.material} ${need.suppliers.join(' ')} ${need.colors.join(' ')}`).includes(normalizedSearch)
));
const totalPages = Math.ceil(visibleNeeds.length / itemsPerPage);
const safeCurrentPage = Math.min(currentPage, totalPages || 1);
const startIndex = (safeCurrentPage - 1) * itemsPerPage;
const paginatedNeeds = visibleNeeds.slice(startIndex, startIndex + itemsPerPage);
const purchaseItemCount = summary.purchaseNeeds.filter(need => need.purchaseKg > 0).length;
const suggestedPurchaseSummary = summarizePurchaseNeeds(summary.purchaseNeeds);
const pendingSupplierCount = new Set(summary.purchaseNeeds.flatMap(need => need.suppliers)).size;
@@ -1230,7 +1237,15 @@ const PurchaseNeedsScreen = () => {
</div>
<label className="relative mt-4 block">
<Search className="pointer-events-none absolute left-3 top-3 h-4 w-4 text-dark-muted" />
<input value={search} onChange={(event) => setSearch(event.target.value)} className={`${inputClassName} pl-9`} placeholder="Buscar por material, fornecedor ou cor..." />
<input
value={search}
onChange={(event) => {
setSearch(event.target.value);
setCurrentPage(1);
}}
className={`${inputClassName} pl-9`}
placeholder="Buscar por material, fornecedor ou cor..."
/>
</label>
{isLoading ? (
<div className={emptyStateClassName}>
@@ -1239,20 +1254,33 @@ const PurchaseNeedsScreen = () => {
</div>
) : visibleNeeds.length ? (
<div className="mt-4 overflow-hidden rounded-xl border border-dark-border">
<div className="hidden grid-cols-[1.3fr_110px_110px_110px_110px_110px] border-b border-dark-border bg-dark-input/40 px-4 py-3 text-xs font-bold uppercase tracking-widest text-dark-muted lg:grid">
<span>Material</span>
<span>Planejado</span>
<span>Estoque</span>
<span>Pendente</span>
<span>Comprar</span>
<span>Cobertura</span>
</div>
<div className="divide-y divide-dark-border">
{visibleNeeds.map(need => {
<div className="overflow-x-auto">
<table className="w-full min-w-[920px] table-fixed border-collapse">
<colgroup>
<col />
<col className="w-[120px]" />
<col className="w-[120px]" />
<col className="w-[120px]" />
<col className="w-[120px]" />
<col className="w-[150px]" />
</colgroup>
<thead className="border-b border-dark-border bg-dark-input/40 text-xs font-bold uppercase tracking-widest text-dark-muted">
<tr>
<th scope="col" className="px-4 py-3 text-left">Material</th>
<th scope="col" className="px-4 py-3 text-right">Planejado</th>
<th scope="col" className="px-4 py-3 text-right">Estoque</th>
<th scope="col" className="px-4 py-3 text-right">Pendente</th>
<th scope="col" className="px-4 py-3 text-right">Comprar</th>
<th scope="col" className="px-4 py-3 text-left">Cobertura</th>
</tr>
</thead>
<tbody className="divide-y divide-dark-border bg-dark-card">
{paginatedNeeds.map(need => {
const referenceProduct = need.products?.[0];
return (
<div key={need.material} className="grid grid-cols-1 gap-3 bg-dark-card px-4 py-4 lg:grid-cols-[1.3fr_110px_110px_110px_110px_110px] lg:items-center">
<div>
<tr key={need.material}>
<td className="px-4 py-4 align-middle">
<div className="min-w-0">
<p className="text-sm font-bold text-dark-text">{need.material}</p>
<div className="mt-1 flex flex-wrap items-center gap-2">
<p className="text-xs font-semibold text-dark-muted">
@@ -1277,12 +1305,14 @@ const PurchaseNeedsScreen = () => {
{need.products.length > 2 ? ` +${need.products.length - 2}` : ''}
</p>
) : null}
</div>
<p className="text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.plannedKg)}</p>
<p className="text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.stockKg)}</p>
<p className="text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.pendingKg)}</p>
<p className={`text-sm font-bold ${need.purchaseKg > 0 ? 'text-red-300' : 'text-emerald-300'}`}>{formatNeedQuantity(need, need.purchaseKg)}</p>
<span className={`w-fit rounded-full border px-2.5 py-1 text-xs font-bold ${
</div>
</td>
<td className="px-4 py-4 text-right align-middle text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.plannedKg)}</td>
<td className="px-4 py-4 text-right align-middle text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.stockKg)}</td>
<td className="px-4 py-4 text-right align-middle text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.pendingKg)}</td>
<td className={`px-4 py-4 text-right align-middle text-sm font-bold ${need.purchaseKg > 0 ? 'text-amber-300' : 'text-emerald-300'}`}>{formatNeedQuantity(need, need.purchaseKg)}</td>
<td className="px-4 py-4 align-middle">
<span className={`inline-flex whitespace-nowrap rounded-full border px-2.5 py-1 text-xs font-bold ${
need.missingReference
? 'border-red-400/30 bg-red-400/10 text-red-300'
: need.status === 'critical'
@@ -1293,10 +1323,30 @@ const PurchaseNeedsScreen = () => {
}`}>
{need.missingReference ? 'Sem referência' : purchaseStatusLabels[need.status]}
</span>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<PaginationControls
totalItems={visibleNeeds.length}
currentPage={safeCurrentPage}
totalPages={totalPages}
pageSize={itemsPerPage}
pageSizeOptions={[10, 20, 50, 100]}
itemLabel="materiais"
pageSizeLabel="itens por página"
startIndex={startIndex}
endIndex={Math.min(startIndex + itemsPerPage, visibleNeeds.length)}
onPageChange={setCurrentPage}
onPageSizeChange={(pageSize) => {
setItemsPerPage(pageSize);
setCurrentPage(1);
}}
className="border-t border-dark-border px-4 py-3"
/>
</div>
) : (
<div className={emptyStateClassName}>
@@ -1306,10 +1356,6 @@ const PurchaseNeedsScreen = () => {
</div>
)}
</div>
<div className={`${panelClassName} p-5`}>
<h2 className="text-base font-bold text-dark-text">Como o fluxo está conectado</h2>
<p className="mt-2 text-sm font-semibold text-dark-muted">Plano de malha cria demanda. Recebimento pendente entra como material a receber. Aprovar recebimento move para estoque e movimentações.</p>
</div>
</div>
);
};