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