hmm
This commit is contained in:
@@ -266,6 +266,32 @@ export default function ProductsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle toggle product enabled status
|
||||||
|
const handleToggleEnabled = async (productId: string, enabled: boolean) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
await clientFetch(`/products/${productId}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: JSON.stringify({ enabled }),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the local state
|
||||||
|
setProducts(products.map(product =>
|
||||||
|
product._id === productId
|
||||||
|
? { ...product, enabled }
|
||||||
|
: product
|
||||||
|
));
|
||||||
|
|
||||||
|
toast.success(`Product ${enabled ? 'enabled' : 'disabled'} successfully`);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Failed to update product status");
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -315,6 +341,7 @@ export default function ProductsPage() {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
onEdit={handleEditProduct}
|
onEdit={handleEditProduct}
|
||||||
onDelete={handleDeleteProduct}
|
onDelete={handleDeleteProduct}
|
||||||
|
onToggleEnabled={handleToggleEnabled}
|
||||||
getCategoryNameById={getCategoryNameById}
|
getCategoryNameById={getCategoryNameById}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { toast } from "sonner";
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { apiRequest } from "@/lib/api";
|
import { apiRequest } from "@/lib/api";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
||||||
type CategorySelectProps = {
|
type CategorySelectProps = {
|
||||||
categories: { _id: string; name: string; parentId?: string }[];
|
categories: { _id: string; name: string; parentId?: string }[];
|
||||||
@@ -200,34 +201,54 @@ const ProductBasicInfo: React.FC<{
|
|||||||
setProductData: React.Dispatch<React.SetStateAction<ProductData>>;
|
setProductData: React.Dispatch<React.SetStateAction<ProductData>>;
|
||||||
onAddCategory: (newCategory: { _id: string; name: string; parentId?: string }) => void;
|
onAddCategory: (newCategory: { _id: string; name: string; parentId?: string }) => void;
|
||||||
}> = ({ productData, handleChange, categories, setProductData, onAddCategory }) => (
|
}> = ({ productData, handleChange, categories, setProductData, onAddCategory }) => (
|
||||||
<div className="grid gap-4 mb-4">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="name" className="text-sm font-medium">
|
<label htmlFor="name" className="text-sm font-medium">
|
||||||
Name
|
Product Name
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
name="name"
|
name="name"
|
||||||
required
|
value={productData.name}
|
||||||
value={productData.name || ""}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Product name"
|
placeholder="Enter product name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium">Description</label>
|
<label htmlFor="description" className="text-sm font-medium">
|
||||||
<Textarea
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
id="description"
|
id="description"
|
||||||
name="description"
|
name="description"
|
||||||
rows={3}
|
value={productData.description}
|
||||||
value={productData.description || ""}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Product description"
|
placeholder="Enter product description"
|
||||||
|
className="w-full min-h-[100px] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stock Management Section */}
|
<div className="bg-background rounded-lg border border-border p-4">
|
||||||
|
<h3 className="text-sm font-medium mb-4">Product Status</h3>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch
|
||||||
|
id="enabled"
|
||||||
|
checked={productData.enabled !== false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setProductData({
|
||||||
|
...productData,
|
||||||
|
enabled: checked
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor="enabled" className="text-sm">
|
||||||
|
Enable Product
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="bg-background rounded-lg border border-border p-4">
|
<div className="bg-background rounded-lg border border-border p-4">
|
||||||
<h3 className="text-sm font-medium mb-4">Stock Management</h3>
|
<h3 className="text-sm font-medium mb-4">Stock Management</h3>
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { Edit, Trash, AlertTriangle, CheckCircle, AlertCircle } from "lucide-rea
|
|||||||
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";
|
||||||
|
|
||||||
interface ProductTableProps {
|
interface ProductTableProps {
|
||||||
products: Product[];
|
products: Product[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
onEdit: (product: Product) => void;
|
onEdit: (product: Product) => void;
|
||||||
onDelete: (productId: string) => void;
|
onDelete: (productId: string) => void;
|
||||||
|
onToggleEnabled: (productId: string, enabled: boolean) => void;
|
||||||
getCategoryNameById: (categoryId: string) => string;
|
getCategoryNameById: (categoryId: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,6 +19,7 @@ const ProductTable = ({
|
|||||||
loading,
|
loading,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onToggleEnabled,
|
||||||
getCategoryNameById
|
getCategoryNameById
|
||||||
}: ProductTableProps) => {
|
}: ProductTableProps) => {
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ const ProductTable = ({
|
|||||||
<TableHead className="text-center">Category</TableHead>
|
<TableHead className="text-center">Category</TableHead>
|
||||||
<TableHead className="text-center">Unit</TableHead>
|
<TableHead className="text-center">Unit</TableHead>
|
||||||
<TableHead className="text-center">Stock</TableHead>
|
<TableHead className="text-center">Stock</TableHead>
|
||||||
|
<TableHead className="text-center">Enabled</TableHead>
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -59,6 +63,7 @@ const ProductTable = ({
|
|||||||
<TableCell>Loading...</TableCell>
|
<TableCell>Loading...</TableCell>
|
||||||
<TableCell>Loading...</TableCell>
|
<TableCell>Loading...</TableCell>
|
||||||
<TableCell>Loading...</TableCell>
|
<TableCell>Loading...</TableCell>
|
||||||
|
<TableCell>Loading...</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
) : sortedProducts.length > 0 ? (
|
) : sortedProducts.length > 0 ? (
|
||||||
@@ -81,6 +86,12 @@ const ProductTable = ({
|
|||||||
<Badge variant="outline" className="text-xs">Not Tracked</Badge>
|
<Badge variant="outline" className="text-xs">Not Tracked</Badge>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="text-center">
|
||||||
|
<Switch
|
||||||
|
checked={product.enabled !== false}
|
||||||
|
onCheckedChange={(checked) => onToggleEnabled(product._id as string, checked)}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-right flex justify-end space-x-1">
|
<TableCell className="text-right flex justify-end space-x-1">
|
||||||
<Button variant="ghost" size="sm" onClick={() => onEdit(product)}>
|
<Button variant="ghost" size="sm" onClick={() => onEdit(product)}>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
@@ -98,7 +109,7 @@ const ProductTable = ({
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={5} className="h-24 text-center">
|
<TableCell colSpan={6} className="h-24 text-center">
|
||||||
No products found.
|
No products found.
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export interface Product {
|
|||||||
stockStatus?: 'in_stock' | 'low_stock' | 'out_of_stock'
|
stockStatus?: 'in_stock' | 'low_stock' | 'out_of_stock'
|
||||||
unitType: string
|
unitType: string
|
||||||
category: string
|
category: string
|
||||||
|
enabled?: boolean
|
||||||
pricing: PricingTier[]
|
pricing: PricingTier[]
|
||||||
image?: string | File | null
|
image?: string | File | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export interface Product {
|
|||||||
description: string;
|
description: string;
|
||||||
unitType: string;
|
unitType: string;
|
||||||
category: string;
|
category: string;
|
||||||
|
enabled?: boolean;
|
||||||
// Stock management fields
|
// Stock management fields
|
||||||
stockTracking?: boolean;
|
stockTracking?: boolean;
|
||||||
currentStock?: number;
|
currentStock?: number;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"commitHash": "02e0900",
|
"commitHash": "308a816",
|
||||||
"buildTime": "2025-05-19T00:35:36.962Z"
|
"buildTime": "2025-05-23T06:40:22.513Z"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user