Update product-table.tsx
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
Table,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
@@ -14,11 +13,15 @@ import {
|
||||
AlertCircle,
|
||||
Calculator,
|
||||
Copy,
|
||||
PackageOffset,
|
||||
Archive
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Product } from "@/models/products";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
interface ProductTableProps {
|
||||
products: Product[];
|
||||
@@ -68,35 +71,53 @@ const ProductTable = ({
|
||||
}
|
||||
};
|
||||
|
||||
const renderProductRow = (product: Product, isDisabled: boolean = false) => (
|
||||
<TableRow
|
||||
const renderProductRow = (product: Product, index: number, isDisabled: boolean = false) => (
|
||||
<motion.tr
|
||||
key={product._id}
|
||||
className={`transition-colors hover:bg-gray-50 dark:hover:bg-zinc-800/70 ${isDisabled ? "opacity-60" : ""}`}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2, delay: index * 0.05 }}
|
||||
className={`group hover:bg-muted/40 border-b border-border/50 transition-colors ${isDisabled ? "opacity-60 bg-muted/20" : ""}`}
|
||||
>
|
||||
<TableCell>
|
||||
<div className="font-medium truncate max-w-[180px]">{product.name}</div>
|
||||
<div className="hidden sm:block text-sm text-muted-foreground mt-1">
|
||||
{getCategoryNameById(product.category)}
|
||||
<TableCell className="font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 rounded bg-muted/50 flex items-center justify-center text-muted-foreground">
|
||||
{product.image ? (
|
||||
<img src={product.image} alt={product.name} className="h-full w-full object-cover rounded" />
|
||||
) : (
|
||||
<span className="text-xs font-bold">{product.name.charAt(0).toUpperCase()}</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="truncate max-w-[180px]">{product.name}</div>
|
||||
<div className="sm:hidden text-xs text-muted-foreground">
|
||||
{getCategoryNameById(product.category)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="hidden sm:table-cell text-center">
|
||||
{getCategoryNameById(product.category)}
|
||||
<Badge variant="outline" className="font-normal bg-background/50">
|
||||
{getCategoryNameById(product.category)}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell text-center">
|
||||
<TableCell className="hidden md:table-cell text-center text-muted-foreground text-sm">
|
||||
{product.unitType}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{product.stockTracking ? (
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
<div className="flex items-center justify-center gap-1.5">
|
||||
{getStockIcon(product)}
|
||||
<span className="text-sm">
|
||||
{product.currentStock !== undefined ? product.currentStock : 0}{" "}
|
||||
{product.unitType}
|
||||
<span className={`text-sm font-medium ${product.stockStatus === 'out_of_stock' ? 'text-destructive' :
|
||||
product.stockStatus === 'low_stock' ? 'text-amber-500' : 'text-foreground'
|
||||
}`}>
|
||||
{product.currentStock !== undefined ? product.currentStock : 0}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Not Tracked
|
||||
<Badge variant="secondary" className="text-[10px] h-5 px-1.5 text-muted-foreground bg-muted/50">
|
||||
Unlimited
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
@@ -106,57 +127,61 @@ const ProductTable = ({
|
||||
onCheckedChange={(checked) =>
|
||||
onToggleEnabled(product._id as string, checked)
|
||||
}
|
||||
className="data-[state=checked]:bg-primary"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right flex justify-end space-x-1">
|
||||
{onProfitAnalysis && (
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
{onProfitAnalysis && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
onProfitAnalysis(product._id as string, product.name)
|
||||
}
|
||||
className="h-8 w-8 text-emerald-500 hover:text-emerald-600 hover:bg-emerald-500/10"
|
||||
title="Profit Analysis"
|
||||
>
|
||||
<Calculator className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{onClone && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onClone(product)}
|
||||
className="h-8 w-8 text-blue-500 hover:text-blue-600 hover:bg-blue-500/10"
|
||||
title="Clone Listing"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
onProfitAnalysis(product._id as string, product.name)
|
||||
}
|
||||
className="text-green-600 hover:text-green-700 hover:bg-green-50 dark:hover:bg-green-950/20"
|
||||
title="Profit Analysis"
|
||||
size="icon"
|
||||
onClick={() => onEdit(product)}
|
||||
className="h-8 w-8 text-muted-foreground hover:text-foreground"
|
||||
title="Edit Product"
|
||||
>
|
||||
<Calculator className="h-4 w-4" />
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{onClone && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onClone(product)}
|
||||
className="text-blue-600 hover:text-blue-700 hover:bg-blue-50 dark:hover:bg-blue-950/20"
|
||||
title="Clone Listing"
|
||||
size="icon"
|
||||
onClick={() => onDelete(product._id as string)}
|
||||
className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
|
||||
title="Delete Product"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onEdit(product)}
|
||||
title="Edit Product"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onDelete(product._id as string)}
|
||||
className="text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-950/20"
|
||||
title="Delete Product"
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</motion.tr>
|
||||
);
|
||||
|
||||
const renderTableHeader = () => (
|
||||
<TableHeader className="bg-gray-50 dark:bg-zinc-800/50">
|
||||
<TableRow className="hover:bg-transparent">
|
||||
<TableHeader className="bg-muted/50 sticky top-0 z-10">
|
||||
<TableRow className="hover:bg-transparent border-border/50">
|
||||
<TableHead className="w-[200px]">Product</TableHead>
|
||||
<TableHead className="hidden sm:table-cell text-center">
|
||||
Category
|
||||
@@ -166,57 +191,86 @@ const ProductTable = ({
|
||||
<TableHead className="hidden lg:table-cell text-center">
|
||||
Enabled
|
||||
</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
<TableHead className="text-right pr-6">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
{/* Enabled Products Table */}
|
||||
<div className="rounded-lg border dark:border-zinc-700 shadow-sm overflow-hidden">
|
||||
<Table className="relative">
|
||||
{renderTableHeader()}
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
Array.from({ length: 1 }).map((_, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>Loading...</TableCell>
|
||||
<TableCell>Loading...</TableCell>
|
||||
<TableCell>Loading...</TableCell>
|
||||
<TableCell>Loading...</TableCell>
|
||||
<TableCell>Loading...</TableCell>
|
||||
<TableCell>Loading...</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : sortedEnabledProducts.length > 0 ? (
|
||||
sortedEnabledProducts.map((product) => renderProductRow(product))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="h-24 text-center">
|
||||
No enabled products found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden">
|
||||
<CardHeader className="py-4 px-6 border-b border-border/50 bg-muted/30">
|
||||
<CardTitle className="text-lg font-medium flex items-center gap-2">
|
||||
<CheckCircle className="h-5 w-5 text-primary" />
|
||||
Active Products
|
||||
<Badge variant="secondary" className="ml-2 bg-background/80 backdrop-blur-sm">
|
||||
{sortedEnabledProducts.length}
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="max-h-[600px] overflow-auto">
|
||||
<Table>
|
||||
{renderTableHeader()}
|
||||
<TableBody>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="h-32 text-center text-muted-foreground">
|
||||
Loading products...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : sortedEnabledProducts.length > 0 ? (
|
||||
sortedEnabledProducts.map((product, index) => renderProductRow(product, index))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="h-32 text-center text-muted-foreground">
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<PackageOffset className="h-8 w-8 opacity-50" />
|
||||
<p>No active products found</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Disabled Products Section */}
|
||||
{!loading && disabledProducts.length > 0 && (
|
||||
<div className="rounded-lg border dark:border-zinc-700 shadow-sm overflow-hidden bg-gray-50/30 dark:bg-zinc-900/30">
|
||||
<Table className="relative">
|
||||
{renderTableHeader()}
|
||||
<TableBody>
|
||||
{sortedDisabledProducts.map((product) =>
|
||||
renderProductRow(product, true),
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<Card className="border-border/40 bg-background/30 backdrop-blur-sm shadow-sm overflow-hidden opacity-90">
|
||||
<CardHeader className="py-4 px-6 border-b border-border/50 bg-muted/20">
|
||||
<CardTitle className="text-lg font-medium flex items-center gap-2 text-muted-foreground">
|
||||
<Archive className="h-5 w-5" />
|
||||
Archived / Disabled
|
||||
<Badge variant="outline" className="ml-2">
|
||||
{sortedDisabledProducts.length}
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="max-h-[400px] overflow-auto">
|
||||
<Table>
|
||||
{renderTableHeader()}
|
||||
<TableBody>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{sortedDisabledProducts.map((product, index) =>
|
||||
renderProductRow(product, index, true),
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default ProductTable;
|
||||
|
||||
Reference in New Issue
Block a user