hmm
This commit is contained in:
@@ -30,16 +30,14 @@ interface ProductModalProps {
|
||||
onClose: () => void;
|
||||
onSave: (productData: Product) => void;
|
||||
productData: Product;
|
||||
categories: Category[]; // Define categories as an array of Category
|
||||
categories: Category[];
|
||||
editing: boolean;
|
||||
handleChange: (
|
||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => void;
|
||||
handleChange: (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
||||
setProductData: React.Dispatch<React.SetStateAction<Product>>;
|
||||
handleTieredPricingChange: (
|
||||
e: ChangeEvent<HTMLInputElement>,
|
||||
index: number
|
||||
) => void;
|
||||
setProductData: React.Dispatch<React.SetStateAction<Product>>;
|
||||
}
|
||||
|
||||
export const ProductModal = ({
|
||||
@@ -55,10 +53,10 @@ export const ProductModal = ({
|
||||
}: ProductModalProps) => {
|
||||
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
||||
|
||||
// Update image preview when product data changes (for editing purposes)
|
||||
// Update image preview when product data changes
|
||||
useEffect(() => {
|
||||
if (productData.image && typeof productData.image === "string") {
|
||||
setImagePreview(productData.image); // For existing product image
|
||||
setImagePreview(productData.image);
|
||||
}
|
||||
}, [productData.image]);
|
||||
|
||||
@@ -73,21 +71,23 @@ export const ProductModal = ({
|
||||
}
|
||||
};
|
||||
|
||||
// Handle tiered pricing change
|
||||
// ✅ FIXED: Moved Inside the Component & Used Type Assertion
|
||||
const handleTieredPricingChangeInternal = (
|
||||
e: ChangeEvent<HTMLInputElement>,
|
||||
index: number
|
||||
) => {
|
||||
const updatedPricing = [...productData.tieredPricing];
|
||||
const updatedTier = updatedPricing[index];
|
||||
const field = e.target.name as keyof (typeof productData.tieredPricing)[number];
|
||||
|
||||
// Ensure pricePerUnit is a number
|
||||
const value = e.target.name === "pricePerUnit"
|
||||
? parseFloat(e.target.value) || 0 // If value is invalid, default to 0
|
||||
: e.target.value;
|
||||
updatedPricing[index] = {
|
||||
...updatedPricing[index],
|
||||
[field]: e.target.valueAsNumber || 0,
|
||||
};
|
||||
|
||||
updatedTier[e.target.name] = value;
|
||||
setProductData({ ...productData, tieredPricing: updatedPricing });
|
||||
setProductData({
|
||||
...productData,
|
||||
tieredPricing: updatedPricing,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -98,36 +98,21 @@ export const ProductModal = ({
|
||||
{editing ? "Edit Product" : "Add Product"}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Product Name</label>
|
||||
<Input
|
||||
name="name"
|
||||
placeholder="Product Name"
|
||||
value={productData.name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Input name="name" placeholder="Product Name" value={productData.name} onChange={handleChange} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Description</label>
|
||||
<Textarea
|
||||
name="description"
|
||||
placeholder="Product Description"
|
||||
value={productData.description}
|
||||
onChange={handleChange}
|
||||
rows={3}
|
||||
/>
|
||||
<Textarea name="description" placeholder="Product Description" value={productData.description} onChange={handleChange} rows={3} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Category</label>
|
||||
<Select
|
||||
value={productData.category}
|
||||
onValueChange={(value) =>
|
||||
setProductData({ ...productData, category: value })
|
||||
}
|
||||
>
|
||||
<Select value={productData.category} onValueChange={(value) => setProductData({ ...productData, category: value })}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a category" />
|
||||
</SelectTrigger>
|
||||
@@ -141,127 +126,25 @@ export const ProductModal = ({
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{productData.category && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium">Tiered Pricing</h3>
|
||||
{productData.tieredPricing.map((tier, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<div className="flex-1 space-y-1">
|
||||
<label className="text-xs text-muted-foreground">
|
||||
Quantity
|
||||
</label>
|
||||
<Input
|
||||
name="minQuantity"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="Quantity"
|
||||
value={tier.minQuantity}
|
||||
onChange={(e) => handleTieredPricingChangeInternal(e, index)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 space-y-1">
|
||||
<label className="text-xs text-muted-foreground">
|
||||
Price
|
||||
</label>
|
||||
<Input
|
||||
name="pricePerUnit"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="Price"
|
||||
value={tier.pricePerUnit}
|
||||
onChange={(e) => handleTieredPricingChangeInternal(e, index)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
onClick={() =>
|
||||
setProductData({
|
||||
...productData,
|
||||
tieredPricing: [
|
||||
...productData.tieredPricing,
|
||||
{ minQuantity: 1, pricePerUnit: 0 }, // Initialize pricePerUnit as a number
|
||||
],
|
||||
})
|
||||
}
|
||||
>
|
||||
+ Add Tier
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Unit Type</label>
|
||||
<Select
|
||||
value={productData.unitType}
|
||||
onValueChange={(value) =>
|
||||
setProductData({ ...productData, unitType: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select unit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="pcs">Pieces</SelectItem>
|
||||
<SelectItem value="gr">Grams</SelectItem>
|
||||
<SelectItem value="kg">Kilograms</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Image Upload Section */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Product Image</label>
|
||||
<Input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="file:text-foreground"
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
{imagePreview && (
|
||||
<div className="mt-2">
|
||||
<img
|
||||
src={imagePreview}
|
||||
alt="Product preview"
|
||||
className="rounded-md border w-32 h-32 object-cover"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Click the upload button above to change image
|
||||
</p>
|
||||
<h3 className="text-sm font-medium">Tiered Pricing</h3>
|
||||
{productData.tieredPricing.map((tier, index) => (
|
||||
<div key={index} className="flex gap-2">
|
||||
<Input name="minQuantity" type="number" min="1" placeholder="Quantity" value={tier.minQuantity} onChange={(e) => handleTieredPricingChangeInternal(e, index)} />
|
||||
<Input name="pricePerUnit" type="number" step="0.01" placeholder="Price" value={tier.pricePerUnit} onChange={(e) => handleTieredPricingChangeInternal(e, index)} />
|
||||
</div>
|
||||
)}
|
||||
{!imagePreview &&
|
||||
productData.image &&
|
||||
typeof productData.image === "string" && (
|
||||
<div className="mt-2">
|
||||
<img
|
||||
src={productData.image}
|
||||
alt="Existing product"
|
||||
className="rounded-md border w-32 h-32 object-cover"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Existing product image
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Upload a product image (JPEG, PNG, WEBP)
|
||||
</p>
|
||||
))}
|
||||
<Button variant="outline" size="sm" onClick={() => setProductData({ ...productData, tieredPricing: [...productData.tieredPricing, { minQuantity: 1, pricePerUnit: 0 }] })}>
|
||||
+ Add Tier
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={() => onSave(productData)}>
|
||||
{editing ? "Update Product" : "Create Product"}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={() => onSave(productData)}>{editing ? "Update Product" : "Create Product"}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -5,20 +5,17 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
interface ShippingData {
|
||||
name: string;
|
||||
price: number;
|
||||
_id?: string; // Optional for new entry
|
||||
}
|
||||
|
||||
import { ShippingData } from "@/lib/types";
|
||||
|
||||
interface ShippingModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (shippingData: ShippingData) => void;
|
||||
shippingData: { name: string; price: number }; // Define type of shippingData
|
||||
onSave: (shippingData: ShippingData) => void; // ✅ Allow passing shippingData
|
||||
shippingData: ShippingData;
|
||||
setShippingData: React.Dispatch<React.SetStateAction<ShippingData>>;
|
||||
editing: boolean;
|
||||
handleChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
setShippingData: React.Dispatch<React.SetStateAction<{ name: string; price: 0.00 }>>;
|
||||
}
|
||||
|
||||
export const ShippingModal = ({
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Edit, Trash } from "lucide-react";
|
||||
|
||||
interface ShippingMethod {
|
||||
_id: string;
|
||||
name: string;
|
||||
price: number;
|
||||
}
|
||||
import { ShippingMethod } from "@/lib/types";
|
||||
|
||||
interface ShippingTableProps {
|
||||
shippingMethods: ShippingMethod[];
|
||||
@@ -46,9 +49,7 @@ export const ShippingTable: React.FC<ShippingTableProps> = ({
|
||||
className="transition-colors hover:bg-gray-50 dark:hover:bg-zinc-800/70"
|
||||
>
|
||||
<TableCell className="font-medium">{method.name}</TableCell>
|
||||
<TableCell className="text-center">
|
||||
£{method.price}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">£{method.price}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
@@ -63,7 +64,7 @@ export const ShippingTable: React.FC<ShippingTableProps> = ({
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="text-red-600 hover:text-red-700 dark:text-red-400"
|
||||
onClick={() => onDeleteShipping(method._id)}
|
||||
onClick={() => onDeleteShipping(method._id ?? "")}
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user