diff --git a/inventory/src/components/products/ProductTable.tsx b/inventory/src/components/products/ProductTable.tsx
index e8cc85d..b569b34 100644
--- a/inventory/src/components/products/ProductTable.tsx
+++ b/inventory/src/components/products/ProductTable.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { ArrowUpDown, GripVertical } from "lucide-react";
+import { SortAsc, SortDesc } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
Table,
@@ -87,20 +87,17 @@ function SortableHeader({ column, columnDef, onSort, sortColumn, sortDirection }
"cursor-pointer select-none",
columnDef?.width
)}
+ {...attributes}
+ {...listeners}
+ onClick={() => onSort(column)}
>
-
-
-
-
-
onSort(column)}>
- {columnDef?.label ?? column}
- {sortColumn === column && (
-
- )}
-
+
+ {columnDef?.label ?? column}
+ {sortColumn === column && (
+ sortDirection === 'desc'
+ ?
+ :
+ )}
);
@@ -161,15 +158,19 @@ export function ProductTable({
case 'critical':
return
Critical;
case 'reorder':
- return
Reorder;
+ return
Reorder;
case 'healthy':
- return
Healthy;
+ return
Healthy;
case 'overstocked':
return
Overstocked;
case 'new':
return
New;
+ case 'out of stock':
+ return
Out of Stock;
+ case 'at-risk':
+ return
At Risk;
default:
- return null;
+ return
{status};
}
};
diff --git a/inventory/src/components/products/ProductViews.tsx b/inventory/src/components/products/ProductViews.tsx
index 176efb4..3eaa6e4 100644
--- a/inventory/src/components/products/ProductViews.tsx
+++ b/inventory/src/components/products/ProductViews.tsx
@@ -1,6 +1,6 @@
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Product } from "@/types/products"
-import { AlertCircle, AlertTriangle, CheckCircle2, PackageSearch, Star } from "lucide-react"
+import { AlertCircle, AlertTriangle, CheckCircle2, PackageSearch, Sparkles } from "lucide-react"
export type ProductView = {
id: string
@@ -15,42 +15,42 @@ export const PRODUCT_VIEWS: ProductView[] = [
id: "all",
label: "All Products",
icon: PackageSearch,
- iconClassName: "text-muted-foreground",
+ iconClassName: "",
columns: ["image", "title", "SKU", "stock_quantity", "price", "stock_status"]
},
{
id: "Critical",
label: "Critical Stock",
icon: AlertTriangle,
- iconClassName: "text-destructive",
+ iconClassName: "",
columns: ["image", "title", "SKU", "stock_quantity", "daily_sales_avg", "reorder_qty", "replenishable", "last_purchase_date", "lead_time_status"]
},
{
id: "Reorder",
label: "Reorder Soon",
icon: AlertCircle,
- iconClassName: "text-warning",
+ iconClassName: "",
columns: ["image", "title", "SKU", "stock_quantity", "daily_sales_avg", "reorder_qty", "replenishable", "last_purchase_date", "lead_time_status"]
},
{
id: "Healthy",
label: "Healthy Stock",
icon: CheckCircle2,
- iconClassName: "text-success",
+ iconClassName: "",
columns: ["image", "title", "stock_quantity", "daily_sales_avg", "stock_status", "abc_class"]
},
{
id: "Overstocked",
label: "Overstock",
icon: PackageSearch,
- iconClassName: "text-muted-foreground",
+ iconClassName: "",
columns: ["image", "title", "stock_quantity", "daily_sales_avg", "overstocked_amt", "replenishable", "last_sale_date", "abc_class"]
},
{
id: "New",
label: "New Products",
- icon: Star,
- iconClassName: "text-accent",
+ icon: Sparkles,
+ iconClassName: "",
columns: ["image", "title", "stock_quantity", "daily_sales_avg", "stock_status", "abc_class"]
}
]
diff --git a/inventory/src/pages/Products.tsx b/inventory/src/pages/Products.tsx
index cbc3570..053c4e6 100644
--- a/inventory/src/pages/Products.tsx
+++ b/inventory/src/pages/Products.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo } from 'react';
import { useQuery, keepPreviousData } from '@tanstack/react-query';
import { ProductFilters } from '@/components/products/ProductFilters';
import { ProductTable } from '@/components/products/ProductTable';
@@ -75,47 +75,155 @@ const AVAILABLE_COLUMNS: ColumnDef[] = [
{ key: 'last_purchase_date', label: 'Last Purchase', group: 'Lead Time' },
];
-// Default visible columns
-const DEFAULT_VISIBLE_COLUMNS: ColumnKey[] = [
- 'image',
- 'title',
- 'SKU',
- 'brand',
- 'vendor',
- 'stock_quantity',
- 'stock_status',
- 'replenishable',
- 'reorder_qty',
- 'price',
- 'regular_price',
- 'daily_sales_avg',
- 'weekly_sales_avg',
- 'monthly_sales_avg',
-];
+// Define default columns for each view
+const VIEW_COLUMNS: Record
= {
+ all: [
+ 'image',
+ 'title',
+ 'brand',
+ 'vendor',
+ 'stock_quantity',
+ 'stock_status',
+ 'reorder_qty',
+ 'price',
+ 'regular_price',
+ 'daily_sales_avg',
+ 'weekly_sales_avg',
+ 'monthly_sales_avg',
+ ],
+ critical: [
+ 'image',
+ 'title',
+ 'stock_quantity',
+ 'daily_sales_avg',
+ 'weekly_sales_avg',
+ 'reorder_qty',
+ 'vendor',
+ 'last_purchase_date',
+ 'current_lead_time',
+ ],
+ reorder: [
+ 'image',
+ 'title',
+ 'stock_quantity',
+ 'daily_sales_avg',
+ 'weekly_sales_avg',
+ 'reorder_qty',
+ 'vendor',
+ 'last_purchase_date',
+ ],
+ overstocked: [
+ 'image',
+ 'title',
+ 'stock_quantity',
+ 'daily_sales_avg',
+ 'weekly_sales_avg',
+ 'overstocked_amt',
+ 'days_of_inventory',
+ ],
+ 'at-risk': [
+ 'image',
+ 'title',
+ 'stock_quantity',
+ 'daily_sales_avg',
+ 'weekly_sales_avg',
+ 'monthly_sales_avg',
+ 'days_of_inventory',
+ ],
+ new: [
+ 'image',
+ 'title',
+ 'stock_quantity',
+ 'vendor',
+ 'brand',
+ 'price',
+ 'regular_price',
+ ],
+ healthy: [
+ 'image',
+ 'title',
+ 'stock_quantity',
+ 'daily_sales_avg',
+ 'weekly_sales_avg',
+ 'monthly_sales_avg',
+ 'days_of_inventory',
+ ],
+};
export function Products() {
const [filters, setFilters] = useState>({});
const [sortColumn, setSortColumn] = useState('title');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
const [currentPage, setCurrentPage] = useState(1);
- const [visibleColumns, setVisibleColumns] = useState>(new Set(DEFAULT_VISIBLE_COLUMNS));
- const [columnOrder, setColumnOrder] = useState([
- ...DEFAULT_VISIBLE_COLUMNS,
- ...AVAILABLE_COLUMNS.map(col => col.key).filter(key => !DEFAULT_VISIBLE_COLUMNS.includes(key))
- ]);
- const [selectedProductId, setSelectedProductId] = useState(null);
const [activeView, setActiveView] = useState("all");
const [pageSize] = useState(50);
const [showNonReplenishable, setShowNonReplenishable] = useState(false);
+ const [selectedProductId, setSelectedProductId] = useState(null);
+
+ // Store visible columns and order for each view
+ const [viewColumns, setViewColumns] = useState>>(() => {
+ const initialColumns: Record> = {};
+ Object.entries(VIEW_COLUMNS).forEach(([view, columns]) => {
+ initialColumns[view] = new Set(columns);
+ });
+ return initialColumns;
+ });
+
+ const [viewColumnOrder, setViewColumnOrder] = useState>(() => {
+ const initialOrder: Record = {};
+ Object.entries(VIEW_COLUMNS).forEach(([view, defaultColumns]) => {
+ initialOrder[view] = [
+ ...defaultColumns,
+ ...AVAILABLE_COLUMNS.map(col => col.key).filter(key => !defaultColumns.includes(key))
+ ];
+ });
+ return initialOrder;
+ });
- // Group columns by their group property
- const columnsByGroup = AVAILABLE_COLUMNS.reduce((acc, col) => {
- if (!acc[col.group]) {
- acc[col.group] = [];
+ // Get current view's columns
+ const visibleColumns = useMemo(() => {
+ const columns = new Set(viewColumns[activeView] || VIEW_COLUMNS.all);
+ if (showNonReplenishable) {
+ columns.add('replenishable');
}
- acc[col.group].push(col);
- return acc;
- }, {} as Record);
+ return columns;
+ }, [viewColumns, activeView, showNonReplenishable]);
+
+ const columnOrder = viewColumnOrder[activeView] || viewColumnOrder.all;
+
+ // Handle column visibility changes
+ const handleColumnVisibilityChange = (column: ColumnKey, isVisible: boolean) => {
+ setViewColumns(prev => ({
+ ...prev,
+ [activeView]: isVisible
+ ? new Set([...prev[activeView], column])
+ : new Set([...prev[activeView]].filter(col => col !== column))
+ }));
+ };
+
+ // Handle column order changes
+ const handleColumnOrderChange = (newOrder: ColumnKey[]) => {
+ setViewColumnOrder(prev => ({
+ ...prev,
+ [activeView]: newOrder
+ }));
+ };
+
+ // Reset columns to default for current view
+ const resetColumnsToDefault = () => {
+ setViewColumns(prev => ({
+ ...prev,
+ [activeView]: new Set(VIEW_COLUMNS[activeView] || VIEW_COLUMNS.all)
+ }));
+ setViewColumnOrder(prev => ({
+ ...prev,
+ [activeView]: [
+ ...(VIEW_COLUMNS[activeView] || VIEW_COLUMNS.all),
+ ...AVAILABLE_COLUMNS.map(col => col.key)
+ .filter(key => !(VIEW_COLUMNS[activeView] || VIEW_COLUMNS.all).includes(key))
+ ]
+ }));
+ };
// Function to fetch products data
const fetchProducts = async () => {
@@ -195,6 +303,15 @@ export function Products() {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
+ // Group columns by their group property
+ const columnsByGroup = AVAILABLE_COLUMNS.reduce((acc, col) => {
+ if (!acc[col.group]) {
+ acc[col.group] = [];
+ }
+ acc[col.group].push(col);
+ return acc;
+ }, {} as Record);
+
const renderColumnToggle = () => (
@@ -205,38 +322,38 @@ export function Products() {
e.preventDefault()}
>
Toggle columns
- {Object.entries(columnsByGroup).map(([group, columns]) => (
-
-
- {group}
-
- {columns.map((column) => (
- {
- e.preventDefault();
- const newVisibleColumns = new Set(visibleColumns);
- if (newVisibleColumns.has(column.key)) {
- newVisibleColumns.delete(column.key);
- } else {
- newVisibleColumns.add(column.key);
- }
- setVisibleColumns(newVisibleColumns);
- }}
- >
- {column.label}
-
- ))}
-
-
- ))}
+
+ {Object.entries(columnsByGroup).map(([group, columns]) => (
+
+
+ {group}
+
+ {columns.map((column) => (
+ handleColumnVisibilityChange(column.key, checked)}
+ >
+ {column.label}
+
+ ))}
+
+ ))}
+
+
+
);
@@ -311,7 +428,7 @@ export function Products() {
visibleColumns={visibleColumns}
columnDefs={AVAILABLE_COLUMNS}
columnOrder={columnOrder}
- onColumnOrderChange={setColumnOrder}
+ onColumnOrderChange={handleColumnOrderChange}
onRowClick={(product) => setSelectedProductId(product.product_id)}
/>