diff --git a/src/components/inventory/inventory-detail-table.tsx b/src/components/inventory/inventory-detail-table.tsx
index b79ee0a..64029c4 100644
--- a/src/components/inventory/inventory-detail-table.tsx
+++ b/src/components/inventory/inventory-detail-table.tsx
@@ -15,11 +15,13 @@ import {
Table,
TableBody,
TableCell,
- TableHead,
- TableHeader,
TableRow,
} from '@/components/ui/table';
import { Download, Search } from 'lucide-react';
+import {
+ SortableTableHead,
+ useSortableTable,
+} from '@/components/ui/sortable-table-head';
type Props = {
data: InventoryDetailRow[];
@@ -43,6 +45,46 @@ function formatDate(val: string | null | undefined): string {
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
}
+type FlatDetailRow = {
+ CustPartNum: string;
+ SkidNum: string;
+ LotNum: string;
+ MfgLot: string;
+ Bin: string;
+ OnHandQty: number;
+ LinearFt: number;
+ TheoreticalWeight: number;
+ WIP_FG: string;
+ NumCoilsPerSkid: number;
+ CustomerPoNum: string;
+ SalesOrderJob: string;
+ AllocRelease: string;
+ DateProcessedToInventory: string;
+};
+
+function flattenRow(row: InventoryDetailRow): FlatDetailRow {
+ return {
+ CustPartNum: row.CustPartNum ?? '',
+ SkidNum: row.SkidNum ?? '',
+ LotNum: row.LotNum ?? '',
+ MfgLot: row.MfgLot ?? '',
+ Bin: row.Bin ?? '',
+ OnHandQty: Number(row.OnHandQty ?? 0),
+ LinearFt: Number(row.LinearFt ?? 0),
+ TheoreticalWeight: Number(row.TheoreticalWeight ?? 0),
+ WIP_FG: row.WIP_FG ?? '',
+ NumCoilsPerSkid: Number(row.NumCoilsPerSkid ?? 0),
+ CustomerPoNum: row.CustomerPoNum ?? '',
+ SalesOrderJob: [row.VorteqSalesOrderNum, row.VorteqJobNum]
+ .filter(Boolean)
+ .join(' / '),
+ AllocRelease: [row.AllocationNumber, row.ShipmentReleaseId]
+ .filter(Boolean)
+ .join(' / '),
+ DateProcessedToInventory: row.DateProcessedToInventory ?? '',
+ };
+}
+
function DetailTableContent({
data,
partNum,
@@ -52,21 +94,25 @@ function DetailTableContent({
}) {
const [searchTerm, setSearchTerm] = useState('');
- const filteredData = data.filter((row) => {
+ const flatData = data.map(flattenRow);
+
+ const filteredData = flatData.filter((row) => {
if (!searchTerm) return true;
const s = searchTerm.toLowerCase();
return (
- row.CustPartNum?.toLowerCase().includes(s) ||
- row.SkidNum?.toLowerCase().includes(s) ||
- row.LotNum?.toLowerCase().includes(s) ||
- row.MfgLot?.toLowerCase().includes(s) ||
- row.Bin?.toLowerCase().includes(s) ||
- row.CustomerPoNum?.toLowerCase().includes(s) ||
- row.VorteqSalesOrderNum?.toLowerCase().includes(s) ||
- row.VorteqJobNum?.toLowerCase().includes(s)
+ row.CustPartNum.toLowerCase().includes(s) ||
+ row.SkidNum.toLowerCase().includes(s) ||
+ row.LotNum.toLowerCase().includes(s) ||
+ row.MfgLot.toLowerCase().includes(s) ||
+ row.Bin.toLowerCase().includes(s) ||
+ row.CustomerPoNum.toLowerCase().includes(s) ||
+ row.SalesOrderJob.toLowerCase().includes(s)
);
});
+ const { sortKey, sortDirection, handleSort, sortedData } =
+ useSortableTable(filteredData);
+
const handleExportCSV = () => {
const headers = [
'Cust Part#',
@@ -80,30 +126,26 @@ function DetailTableContent({
'WIP/FG',
'# Coils Per Skid',
'PO #',
- 'Sales Order',
- 'Job Order',
+ 'Sales Order / Job',
'Alloc # / Release Id',
'Date Processed',
];
- const rows = filteredData.map((row) => [
- row.CustPartNum ?? '',
- row.SkidNum ?? '',
- row.LotNum ?? '',
- row.MfgLot ?? '',
- row.Bin ?? '',
- row.OnHandQty ?? '',
- row.LinearFt ?? '',
- row.TheoreticalWeight ?? '',
- row.WIP_FG ?? '',
- row.NumCoilsPerSkid ?? '',
- row.CustomerPoNum ?? '',
- row.VorteqSalesOrderNum ?? '',
- row.VorteqJobNum ?? '',
- [row.AllocationNumber, row.ShipmentReleaseId]
- .filter(Boolean)
- .join(' / '),
- row.DateProcessedToInventory ?? '',
+ const rows = sortedData.map((row) => [
+ row.CustPartNum,
+ row.SkidNum,
+ row.LotNum,
+ row.MfgLot,
+ row.Bin,
+ row.OnHandQty,
+ row.LinearFt,
+ row.TheoreticalWeight,
+ row.WIP_FG,
+ row.NumCoilsPerSkid,
+ row.CustomerPoNum,
+ row.SalesOrderJob,
+ row.AllocRelease,
+ row.DateProcessedToInventory,
]);
const csvContent = [headers, ...rows]
@@ -138,28 +180,130 @@ function DetailTableContent({
-
+
-
-
- Cust Part#
- Skid#
- Lot#
- Mfg Lot#
- Bin#
- Qty LB
- Lin. Ft
- Theo. Wt
- WIP/FG
- # Coils
- PO #
- Sales Order / Job
- Alloc # / Release
- Date Processed
-
-
+
+
+
+ Cust Part#
+
+
+ Skid#
+
+
+ Lot#
+
+
+ Mfg Lot#
+
+
+ Bin#
+
+
+ Qty LB
+
+
+ Lin. Ft
+
+
+ Theo. Wt
+
+
+ WIP/FG
+
+
+ # Coils
+
+
+ PO #
+
+
+ Sales Order / Job
+
+
+ Alloc # / Release
+
+
+ Date Processed
+
+
+
- {filteredData.length === 0 ? (
+ {sortedData.length === 0 ? (
) : (
- filteredData.map((row, i) => (
-
- {row.CustPartNum ?? ''}
- {row.SkidNum ?? ''}
- {row.LotNum ?? ''}
- {row.MfgLot ?? ''}
- {row.Bin ?? ''}
+ sortedData.map((row, i) => (
+
+ {row.CustPartNum}
+ {row.SkidNum}
+ {row.LotNum}
+ {row.MfgLot}
+ {row.Bin}
{formatNum(row.OnHandQty)}
@@ -185,21 +329,13 @@ function DetailTableContent({
{formatNum(row.TheoreticalWeight)}
- {row.WIP_FG ?? ''}
+ {row.WIP_FG}
{formatNum(row.NumCoilsPerSkid)}
- {row.CustomerPoNum ?? ''}
-
- {[row.VorteqSalesOrderNum, row.VorteqJobNum]
- .filter(Boolean)
- .join(' / ')}
-
-
- {[row.AllocationNumber, row.ShipmentReleaseId]
- .filter(Boolean)
- .join(' / ')}
-
+ {row.CustomerPoNum}
+ {row.SalesOrderJob}
+ {row.AllocRelease}
{formatDate(row.DateProcessedToInventory)}
@@ -211,7 +347,7 @@ function DetailTableContent({
- Showing {filteredData.length} of {data.length} items
+ Showing {sortedData.length} of {data.length} items
>
);
diff --git a/src/components/inventory/inventory-summary-table.tsx b/src/components/inventory/inventory-summary-table.tsx
index bde4761..61fd90e 100644
--- a/src/components/inventory/inventory-summary-table.tsx
+++ b/src/components/inventory/inventory-summary-table.tsx
@@ -5,8 +5,6 @@ import {
Table,
TableBody,
TableCell,
- TableHead,
- TableHeader,
TableRow,
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
@@ -22,6 +20,19 @@ import { Search, Download, Eye, Loader2 } from 'lucide-react';
import { InventorySummaryRow } from '@/services/inventory';
import type { InventoryDetailRow } from '@/services/inventory';
import { InventoryDetailTable } from './inventory-detail-table';
+import {
+ SortableTableHead,
+ useSortableTable,
+} from '@/components/ui/sortable-table-head';
+
+const CATEGORY_COLORS: Record = {
+ wip: 'bg-emerald-600 text-white',
+ 'finished-goods': 'bg-blue-600 text-white',
+ 'processed-other': 'bg-amber-600 text-white',
+ unprocessed: 'bg-orange-500 text-white',
+ 'unprocessed-rr': 'bg-red-600 text-white',
+ 'processed-rr': 'bg-rose-600 text-white',
+};
type InventorySummaryTableProps = {
data: InventorySummaryRow[];
@@ -49,6 +60,11 @@ export function InventorySummaryTable({
row.cust_part_num?.toLowerCase().includes(searchTerm.toLowerCase())
);
+ const { sortKey, sortDirection, handleSort, sortedData } =
+ useSortableTable(filteredData);
+
+ const headerColor = CATEGORY_COLORS[category] || 'bg-slate-700 text-white';
+
const handleExportCSV = () => {
const headers = [
'Plant',
@@ -59,7 +75,7 @@ export function InventorySummaryTable({
'Qty LB',
'Rows',
];
- const rows = filteredData.map((row) => [
+ const rows = sortedData.map((row) => [
row.plant,
row.cust_part_num || '',
row.part_num,
@@ -143,23 +159,74 @@ export function InventorySummaryTable({
-
+
-
-
- Plant
- Cust Part#
- Vorteq Part#
- Vorteq Desc
- Warehouse
- Qty LB
- Rows
-
-
-
+
+
+
+ Plant
+
+
+ Cust Part#
+
+
+ Vorteq Part#
+
+
+ Vorteq Desc
+
+
+ Warehouse
+
+
+ Qty LB
+
+
+ Rows
+
+ |
+
+
- {filteredData.map((row, idx) => (
-
+ {sortedData.map((row, idx) => (
+
{row.plant}
{row.cust_part_num || '-'}
{row.part_num}
@@ -188,12 +255,12 @@ export function InventorySummaryTable({
- Showing {filteredData.length} of {data.length} items
+ Showing {sortedData.length} of {data.length} items
{/* Detail Modal */}