Fix filtering/sorting/pagination for purchase orders
This commit is contained in:
@@ -229,6 +229,7 @@ export default function PurchaseOrders() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [sortColumn, setSortColumn] = useState<string>("order_date");
|
||||
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const [filterValues, setFilterValues] = useState({
|
||||
search: "",
|
||||
status: "all",
|
||||
@@ -315,63 +316,74 @@ export default function PurchaseOrders() {
|
||||
{ value: "receiving_only", label: "Receiving Only" },
|
||||
];
|
||||
|
||||
// Use useMemo to compute filters only when filterValues change
|
||||
const filters = useMemo(() => filterValues, [filterValues]);
|
||||
|
||||
const fetchData = async () => {
|
||||
if (
|
||||
hasInitialFetchRef.current &&
|
||||
import.meta.hot &&
|
||||
purchaseOrders.length > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const searchParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
limit: "100",
|
||||
sortColumn,
|
||||
sortDirection,
|
||||
...(filters.search && { search: filters.search }),
|
||||
...(filters.status !== "all" && { status: filters.status }),
|
||||
...(filters.vendor !== "all" && { vendor: filters.vendor }),
|
||||
...(filters.recordType !== "all" && { recordType: filters.recordType }),
|
||||
});
|
||||
|
||||
// Build search params with proper encoding
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append('page', page.toString());
|
||||
searchParams.append('limit', '100');
|
||||
searchParams.append('sortColumn', sortColumn);
|
||||
searchParams.append('sortDirection', sortDirection);
|
||||
|
||||
if (filters.search) {
|
||||
searchParams.append('search', filters.search);
|
||||
}
|
||||
|
||||
if (filters.status !== 'all') {
|
||||
searchParams.append('status', filters.status);
|
||||
}
|
||||
|
||||
if (filters.vendor !== 'all') {
|
||||
searchParams.append('vendor', filters.vendor);
|
||||
}
|
||||
|
||||
if (filters.recordType !== 'all') {
|
||||
searchParams.append('recordType', filters.recordType);
|
||||
}
|
||||
|
||||
const [purchaseOrdersRes, vendorMetricsRes, costAnalysisRes, deliveryMetricsRes] =
|
||||
await Promise.all([
|
||||
fetch(`/api/purchase-orders?${searchParams}`),
|
||||
fetch("/api/purchase-orders/vendor-metrics"),
|
||||
fetch("/api/purchase-orders/cost-analysis"),
|
||||
fetch("/api/purchase-orders/delivery-metrics"),
|
||||
]);
|
||||
console.log("Fetching data with params:", searchParams.toString());
|
||||
|
||||
// Initialize default data
|
||||
let purchaseOrdersData: PurchaseOrdersResponse = {
|
||||
orders: [],
|
||||
summary: {
|
||||
order_count: 0,
|
||||
total_ordered: 0,
|
||||
total_received: 0,
|
||||
fulfillment_rate: 0,
|
||||
total_value: 0,
|
||||
avg_cost: 0,
|
||||
},
|
||||
pagination: {
|
||||
total: 0,
|
||||
pages: 0,
|
||||
page: 1,
|
||||
limit: 100,
|
||||
},
|
||||
filters: {
|
||||
vendors: [],
|
||||
statuses: [],
|
||||
},
|
||||
};
|
||||
// Fetch orders first separately to handle errors better
|
||||
const purchaseOrdersRes = await fetch(`/api/purchase-orders?${searchParams.toString()}`);
|
||||
|
||||
if (!purchaseOrdersRes.ok) {
|
||||
const errorText = await purchaseOrdersRes.text();
|
||||
console.error("Failed to fetch purchase orders:", errorText);
|
||||
throw new Error(`Failed to fetch purchase orders: ${errorText}`);
|
||||
}
|
||||
|
||||
let vendorMetricsData: VendorMetrics[] = [];
|
||||
let costAnalysisData: CostAnalysis = {
|
||||
const purchaseOrdersData = await purchaseOrdersRes.json();
|
||||
|
||||
// Process orders data immediately
|
||||
const processedOrders = purchaseOrdersData.orders.map((order: any) => ({
|
||||
...order,
|
||||
status: Number(order.status),
|
||||
total_items: Number(order.total_items) || 0,
|
||||
total_quantity: Number(order.total_quantity) || 0,
|
||||
total_cost: Number(order.total_cost) || 0,
|
||||
total_received: Number(order.total_received) || 0,
|
||||
fulfillment_rate: Number(order.fulfillment_rate) || 0,
|
||||
}));
|
||||
|
||||
// Update the main data state
|
||||
setPurchaseOrders(processedOrders);
|
||||
setPagination(purchaseOrdersData.pagination);
|
||||
setFilterOptions(purchaseOrdersData.filters);
|
||||
|
||||
// Now fetch the additional data in parallel
|
||||
const [vendorMetricsRes, costAnalysisRes, deliveryMetricsRes] = await Promise.all([
|
||||
fetch("/api/purchase-orders/vendor-metrics"),
|
||||
fetch("/api/purchase-orders/cost-analysis"),
|
||||
fetch("/api/purchase-orders/delivery-metrics"),
|
||||
]);
|
||||
|
||||
let vendorMetricsData = [];
|
||||
let costAnalysisData = {
|
||||
unique_products: 0,
|
||||
avg_cost: 0,
|
||||
min_cost: 0,
|
||||
@@ -385,72 +397,58 @@ export default function PurchaseOrders() {
|
||||
max_delivery_days: 0
|
||||
};
|
||||
|
||||
// Only try to parse responses if they were successful
|
||||
if (purchaseOrdersRes.ok) {
|
||||
purchaseOrdersData = await purchaseOrdersRes.json();
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to fetch purchase orders:",
|
||||
await purchaseOrdersRes.text()
|
||||
);
|
||||
}
|
||||
|
||||
if (vendorMetricsRes.ok) {
|
||||
vendorMetricsData = await vendorMetricsRes.json();
|
||||
setVendorMetrics(vendorMetricsData);
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to fetch vendor metrics:",
|
||||
await vendorMetricsRes.text()
|
||||
);
|
||||
setVendorMetrics([]);
|
||||
}
|
||||
|
||||
if (costAnalysisRes.ok) {
|
||||
costAnalysisData = await costAnalysisRes.json();
|
||||
setCostAnalysis(costAnalysisData);
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to fetch cost analysis:",
|
||||
await costAnalysisRes.text()
|
||||
);
|
||||
setCostAnalysis({
|
||||
unique_products: 0,
|
||||
avg_cost: 0,
|
||||
min_cost: 0,
|
||||
max_cost: 0,
|
||||
cost_variance: 0,
|
||||
total_spend_by_category: [],
|
||||
});
|
||||
}
|
||||
|
||||
if (deliveryMetricsRes.ok) {
|
||||
deliveryMetricsData = await deliveryMetricsRes.json();
|
||||
|
||||
// Merge delivery metrics into summary
|
||||
const summaryWithDelivery = {
|
||||
...purchaseOrdersData.summary,
|
||||
avg_delivery_days: deliveryMetricsData.avg_delivery_days,
|
||||
max_delivery_days: deliveryMetricsData.max_delivery_days
|
||||
};
|
||||
|
||||
setSummary(summaryWithDelivery);
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to fetch delivery metrics:",
|
||||
await deliveryMetricsRes.text()
|
||||
);
|
||||
setSummary({
|
||||
...purchaseOrdersData.summary,
|
||||
avg_delivery_days: 0,
|
||||
max_delivery_days: 0
|
||||
});
|
||||
}
|
||||
|
||||
// Process orders data
|
||||
const processedOrders = purchaseOrdersData.orders.map((order) => {
|
||||
let processedOrder = {
|
||||
...order,
|
||||
status: Number(order.status),
|
||||
total_items: Number(order.total_items) || 0,
|
||||
total_quantity: Number(order.total_quantity) || 0,
|
||||
total_cost: Number(order.total_cost) || 0,
|
||||
total_received: Number(order.total_received) || 0,
|
||||
fulfillment_rate: Number(order.fulfillment_rate) || 0,
|
||||
};
|
||||
|
||||
return processedOrder;
|
||||
});
|
||||
|
||||
// Merge delivery metrics into summary
|
||||
const summaryWithDelivery = {
|
||||
...purchaseOrdersData.summary,
|
||||
avg_delivery_days: deliveryMetricsData.avg_delivery_days,
|
||||
max_delivery_days: deliveryMetricsData.max_delivery_days
|
||||
};
|
||||
|
||||
setPurchaseOrders(processedOrders);
|
||||
setPagination(purchaseOrdersData.pagination);
|
||||
setFilterOptions(purchaseOrdersData.filters);
|
||||
setSummary(summaryWithDelivery);
|
||||
setVendorMetrics(vendorMetricsData);
|
||||
setCostAnalysis(costAnalysisData);
|
||||
|
||||
// Mark that we've completed an initial fetch
|
||||
hasInitialFetchRef.current = true;
|
||||
} catch (error) {
|
||||
@@ -481,19 +479,74 @@ export default function PurchaseOrders() {
|
||||
}
|
||||
};
|
||||
|
||||
// Setup debounced search
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [page, sortColumn, sortDirection, filters]);
|
||||
const timer = setTimeout(() => {
|
||||
if (searchInput !== filterValues.search) {
|
||||
setFilterValues(prev => ({ ...prev, search: searchInput }));
|
||||
}
|
||||
}, 300); // Use 300ms for better response time
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchInput, filterValues.search]);
|
||||
|
||||
// Reset page to 1 when filters change
|
||||
useEffect(() => {
|
||||
// Reset to page 1 when filters change to ensure proper pagination
|
||||
setPage(1);
|
||||
}, [filterValues]); // Use filterValues directly to avoid unnecessary renders
|
||||
|
||||
// Fetch data when page, sort or filters change
|
||||
useEffect(() => {
|
||||
// Log the current filter state for debugging
|
||||
console.log("Fetching with filters:", filterValues);
|
||||
console.log("Page:", page, "Sort:", sortColumn, sortDirection);
|
||||
|
||||
// Always fetch data - don't use conditional checks that might prevent it
|
||||
fetchData();
|
||||
}, [page, sortColumn, sortDirection, filterValues]);
|
||||
|
||||
// Handle column sorting more consistently
|
||||
const handleSort = (column: string) => {
|
||||
// Reset to page 1 when changing sort to ensure we see the first page of results
|
||||
setPage(1);
|
||||
|
||||
if (sortColumn === column) {
|
||||
setSortDirection((prev) => (prev === "asc" ? "desc" : "asc"));
|
||||
} else {
|
||||
setSortColumn(column);
|
||||
setSortDirection("asc");
|
||||
// For most columns, start with descending to show highest values first
|
||||
if (column === 'id' || column === 'vendor_name') {
|
||||
setSortDirection("asc");
|
||||
} else {
|
||||
setSortDirection("desc");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Update filter handlers
|
||||
const handleStatusChange = (value: string) => {
|
||||
setFilterValues(prev => ({ ...prev, status: value }));
|
||||
};
|
||||
|
||||
const handleVendorChange = (value: string) => {
|
||||
setFilterValues(prev => ({ ...prev, vendor: value }));
|
||||
};
|
||||
|
||||
const handleRecordTypeChange = (value: string) => {
|
||||
setFilterValues(prev => ({ ...prev, recordType: value }));
|
||||
};
|
||||
|
||||
// Clear all filters handler
|
||||
const clearFilters = () => {
|
||||
setSearchInput("");
|
||||
setFilterValues({
|
||||
search: "",
|
||||
status: "all",
|
||||
vendor: "all",
|
||||
recordType: "all",
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: number, recordType: string) => {
|
||||
if (recordType === "receiving_only") {
|
||||
@@ -541,14 +594,15 @@ export default function PurchaseOrders() {
|
||||
const getPaginationItems = () => {
|
||||
const items = [];
|
||||
const totalPages = pagination.pages;
|
||||
const currentPage = page; // Use the local state to ensure sync
|
||||
|
||||
// Always show first page
|
||||
if (totalPages > 0) {
|
||||
items.push(
|
||||
<PaginationItem key="first">
|
||||
<PaginationLink
|
||||
isActive={page === 1}
|
||||
onClick={() => page !== 1 && setPage(1)}
|
||||
isActive={currentPage === 1}
|
||||
onClick={() => currentPage !== 1 && setPage(1)}
|
||||
>
|
||||
1
|
||||
</PaginationLink>
|
||||
@@ -557,7 +611,7 @@ export default function PurchaseOrders() {
|
||||
}
|
||||
|
||||
// Add ellipsis if needed
|
||||
if (page > 3) {
|
||||
if (currentPage > 3) {
|
||||
items.push(
|
||||
<PaginationItem key="ellipsis-1">
|
||||
<PaginationEllipsis />
|
||||
@@ -566,16 +620,16 @@ export default function PurchaseOrders() {
|
||||
}
|
||||
|
||||
// Add pages around current page
|
||||
const startPage = Math.max(2, page - 1);
|
||||
const endPage = Math.min(totalPages - 1, page + 1);
|
||||
const startPage = Math.max(2, currentPage - 1);
|
||||
const endPage = Math.min(totalPages - 1, currentPage + 1);
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
if (i <= 1 || i >= totalPages) continue; // Skip first and last page as they're handled separately
|
||||
items.push(
|
||||
<PaginationItem key={i}>
|
||||
<PaginationLink
|
||||
isActive={page === i}
|
||||
onClick={() => page !== i && setPage(i)}
|
||||
isActive={currentPage === i}
|
||||
onClick={() => currentPage !== i && setPage(i)}
|
||||
>
|
||||
{i}
|
||||
</PaginationLink>
|
||||
@@ -584,7 +638,7 @@ export default function PurchaseOrders() {
|
||||
}
|
||||
|
||||
// Add ellipsis if needed
|
||||
if (page < totalPages - 2) {
|
||||
if (currentPage < totalPages - 2) {
|
||||
items.push(
|
||||
<PaginationItem key="ellipsis-2">
|
||||
<PaginationEllipsis />
|
||||
@@ -597,8 +651,8 @@ export default function PurchaseOrders() {
|
||||
items.push(
|
||||
<PaginationItem key="last">
|
||||
<PaginationLink
|
||||
isActive={page === totalPages}
|
||||
onClick={() => page !== totalPages && setPage(totalPages)}
|
||||
isActive={currentPage === totalPages}
|
||||
onClick={() => currentPage !== totalPages && setPage(totalPages)}
|
||||
>
|
||||
{totalPages}
|
||||
</PaginationLink>
|
||||
@@ -609,6 +663,12 @@ export default function PurchaseOrders() {
|
||||
return items;
|
||||
};
|
||||
|
||||
// Update sort indicators in table headers
|
||||
const getSortIndicator = (column: string) => {
|
||||
if (sortColumn !== column) return null;
|
||||
return sortDirection === "asc" ? " ↑" : " ↓";
|
||||
};
|
||||
|
||||
// Update this function to fetch yearly data
|
||||
const fetchYearlyData = async () => {
|
||||
if (
|
||||
@@ -938,7 +998,7 @@ export default function PurchaseOrders() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-4 text-muted-foreground">
|
||||
No vendor data available for the past 12 months
|
||||
No supplier data available for the past 12 months
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -953,14 +1013,14 @@ export default function PurchaseOrders() {
|
||||
<>
|
||||
<div className="text-sm font-medium mb-2 flex justify-between items-center px-4">
|
||||
<span>
|
||||
Showing received inventory by vendor for the past 12 months
|
||||
Showing received inventory by supplier for the past 12 months
|
||||
</span>
|
||||
<span>{vendorData.length} vendors found</span>
|
||||
<span>{vendorData.length} suppliers found</span>
|
||||
</div>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Vendor</TableHead>
|
||||
<TableHead>Supplier</TableHead>
|
||||
<TableHead>Orders</TableHead>
|
||||
<TableHead>Total Spend</TableHead>
|
||||
<TableHead>% of Total</TableHead>
|
||||
@@ -1063,7 +1123,7 @@ export default function PurchaseOrders() {
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Received by Vendor
|
||||
Received by Supplier
|
||||
</CardTitle>
|
||||
<Dialog
|
||||
open={vendorAnalysisOpen}
|
||||
@@ -1078,7 +1138,7 @@ export default function PurchaseOrders() {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<BarChart3 className="h-5 w-5" />
|
||||
<span>Received Inventory by Vendor</span>
|
||||
<span>Received Inventory by Supplier</span>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="overflow-auto max-h-[70vh]">
|
||||
@@ -1201,18 +1261,14 @@ export default function PurchaseOrders() {
|
||||
<div className="mb-4 flex flex-wrap items-center gap-4">
|
||||
<Input
|
||||
placeholder="Search orders..."
|
||||
value={filterValues.search}
|
||||
onChange={(e) =>
|
||||
setFilterValues((prev) => ({ ...prev, search: e.target.value }))
|
||||
}
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
className="max-w-xs"
|
||||
disabled={loading}
|
||||
/>
|
||||
<Select
|
||||
value={filterValues.status}
|
||||
onValueChange={(value) =>
|
||||
setFilterValues((prev) => ({ ...prev, status: value }))
|
||||
}
|
||||
onValueChange={handleStatusChange}
|
||||
disabled={loading}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
@@ -1228,16 +1284,14 @@ export default function PurchaseOrders() {
|
||||
</Select>
|
||||
<Select
|
||||
value={filterValues.vendor}
|
||||
onValueChange={(value) =>
|
||||
setFilterValues((prev) => ({ ...prev, vendor: value }))
|
||||
}
|
||||
onValueChange={handleVendorChange}
|
||||
disabled={loading}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select vendor" />
|
||||
<SelectValue placeholder="Select supplier" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Vendors</SelectItem>
|
||||
<SelectItem value="all">All Suppliers</SelectItem>
|
||||
{filterOptions?.vendors?.map((vendor) => (
|
||||
<SelectItem key={vendor} value={vendor}>
|
||||
{vendor}
|
||||
@@ -1247,9 +1301,7 @@ export default function PurchaseOrders() {
|
||||
</Select>
|
||||
<Select
|
||||
value={filterValues.recordType}
|
||||
onValueChange={(value) =>
|
||||
setFilterValues((prev) => ({ ...prev, recordType: value }))
|
||||
}
|
||||
onValueChange={handleRecordTypeChange}
|
||||
disabled={loading}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
@@ -1263,6 +1315,18 @@ export default function PurchaseOrders() {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{(filterValues.search || filterValues.status !== "all" || filterValues.vendor !== "all" || filterValues.recordType !== "all") && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={clearFilters}
|
||||
disabled={loading}
|
||||
title="Clear filters"
|
||||
className="gap-1"
|
||||
>
|
||||
<span>Clear</span> ✕
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Purchase Orders Table */}
|
||||
@@ -1292,7 +1356,7 @@ export default function PurchaseOrders() {
|
||||
onClick={() => !loading && handleSort("id")}
|
||||
disabled={loading}
|
||||
>
|
||||
ID
|
||||
ID{getSortIndicator("id")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[140px] text-center">
|
||||
@@ -1302,7 +1366,7 @@ export default function PurchaseOrders() {
|
||||
onClick={() => !loading && handleSort("vendor_name")}
|
||||
disabled={loading}
|
||||
>
|
||||
Supplier
|
||||
Supplier{getSortIndicator("vendor_name")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[115px] text-center">
|
||||
@@ -1312,7 +1376,7 @@ export default function PurchaseOrders() {
|
||||
onClick={() => !loading && handleSort("status")}
|
||||
disabled={loading}
|
||||
>
|
||||
Status
|
||||
Status{getSortIndicator("status")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[150px] text-center">Note</TableHead>
|
||||
@@ -1323,10 +1387,19 @@ export default function PurchaseOrders() {
|
||||
onClick={() => !loading && handleSort("total_cost")}
|
||||
disabled={loading}
|
||||
>
|
||||
Total Cost
|
||||
Total Cost{getSortIndicator("total_cost")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[70px] text-center">
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="ghost"
|
||||
onClick={() => !loading && handleSort("total_items")}
|
||||
disabled={loading}
|
||||
>
|
||||
Products{getSortIndicator("total_items")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[70px] text-center">Products</TableHead>
|
||||
<TableHead className="w-[90px] text-center">
|
||||
<Button
|
||||
className="w-full"
|
||||
@@ -1334,14 +1407,39 @@ export default function PurchaseOrders() {
|
||||
onClick={() => !loading && handleSort("order_date")}
|
||||
disabled={loading}
|
||||
>
|
||||
Order Date
|
||||
Order Date{getSortIndicator("order_date")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[90px] text-center">
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="ghost"
|
||||
onClick={() => !loading && handleSort("receiving_date")}
|
||||
disabled={loading}
|
||||
>
|
||||
Rec'd Date{getSortIndicator("receiving_date")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[70px] text-center">
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="ghost"
|
||||
onClick={() => !loading && handleSort("total_quantity")}
|
||||
disabled={loading}
|
||||
>
|
||||
Ordered{getSortIndicator("total_quantity")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[80px] text-center">
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="ghost"
|
||||
onClick={() => !loading && handleSort("total_received")}
|
||||
disabled={loading}
|
||||
>
|
||||
Received{getSortIndicator("total_received")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
<TableHead className="w-[90px] text-center">Rec'd Date</TableHead>
|
||||
|
||||
<TableHead className="w-[70px] text-center">Ordered</TableHead>
|
||||
|
||||
<TableHead className="w-[80px] text-center">Received</TableHead>
|
||||
<TableHead className="w-[80px] text-center">
|
||||
<Button
|
||||
className="w-full"
|
||||
@@ -1349,7 +1447,7 @@ export default function PurchaseOrders() {
|
||||
onClick={() => !loading && handleSort("fulfillment_rate")}
|
||||
disabled={loading}
|
||||
>
|
||||
% Fulfilled
|
||||
% Fulfilled{getSortIndicator("fulfillment_rate")}
|
||||
</Button>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
|
||||
Reference in New Issue
Block a user