This commit is contained in:
@@ -22,6 +22,7 @@ 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";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
type CategorySelectProps = {
|
type CategorySelectProps = {
|
||||||
categories: { _id: string; name: string; parentId?: string }[];
|
categories: { _id: string; name: string; parentId?: string }[];
|
||||||
@@ -213,141 +214,40 @@ 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="space-y-6">
|
<div className="space-y-8">
|
||||||
<div>
|
<div className="space-y-4">
|
||||||
<label htmlFor="name" className="text-sm font-medium">
|
<div className="grid gap-2">
|
||||||
Product Name
|
<label htmlFor="name" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||||
</label>
|
Product Name
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
value={productData.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder="Enter product name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="description" className="text-sm font-medium">
|
|
||||||
Description
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
value={productData.description}
|
|
||||||
onChange={handleChange}
|
|
||||||
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 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>
|
</label>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-background rounded-lg border border-border p-4">
|
|
||||||
<h3 className="text-sm font-medium mb-4">Stock Management</h3>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2 mb-4">
|
|
||||||
<input
|
|
||||||
id="stockTracking"
|
|
||||||
name="stockTracking"
|
|
||||||
type="checkbox"
|
|
||||||
className="h-4 w-4 rounded border-gray-300"
|
|
||||||
checked={productData.stockTracking !== false}
|
|
||||||
onChange={(e) => {
|
|
||||||
setProductData({
|
|
||||||
...productData,
|
|
||||||
stockTracking: e.target.checked
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label htmlFor="stockTracking" className="text-sm">
|
|
||||||
Enable Stock Tracking
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{productData.stockTracking !== false && (
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<label htmlFor="lowStockThreshold" className="text-sm font-medium">
|
|
||||||
Low Stock Threshold
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
id="lowStockThreshold"
|
|
||||||
name="lowStockThreshold"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
step={productData.unitType === 'gr' || productData.unitType === 'ml' ? '0.1' : '1'}
|
|
||||||
value={productData.lowStockThreshold || 10}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder="10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label htmlFor="currentStock" className="text-sm font-medium">
|
|
||||||
Current Stock
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
id="currentStock"
|
|
||||||
name="currentStock"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={productData.unitType === 'gr' || productData.unitType === 'ml' ? '0.1' : '1'}
|
|
||||||
value={productData.currentStock || 0}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-background rounded-lg border border-border p-4">
|
|
||||||
<h3 className="text-sm font-medium mb-4">💰 Cost & Profit Tracking</h3>
|
|
||||||
<p className="text-xs text-muted-foreground mb-4">
|
|
||||||
Track your costs to automatically calculate profit margins and markup percentages.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="costPerUnit" className="text-sm font-medium">
|
|
||||||
Cost Per Unit (Optional)
|
|
||||||
</label>
|
|
||||||
<p className="text-xs text-muted-foreground mb-2">
|
|
||||||
How much you paid for each unit of this product
|
|
||||||
</p>
|
|
||||||
<Input
|
<Input
|
||||||
id="costPerUnit"
|
id="name"
|
||||||
name="costPerUnit"
|
name="name"
|
||||||
type="number"
|
value={productData.name}
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
value={productData.costPerUnit || ''}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="0.00"
|
placeholder="e.g. Premium Wireless Headphones"
|
||||||
|
className="border-border/50 bg-background/50 focus:bg-background transition-colors"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label htmlFor="description" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
name="description"
|
||||||
|
value={productData.description}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Describe your product features and benefits..."
|
||||||
|
className="flex w-full rounded-md border border-border/50 bg-background/50 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 min-h-[120px] resize-y focus:bg-background transition-colors"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium">Category</label>
|
<label className="text-sm font-medium leading-none">Category</label>
|
||||||
<CategorySelect
|
<CategorySelect
|
||||||
categories={categories}
|
categories={categories}
|
||||||
value={productData.category}
|
value={productData.category}
|
||||||
@@ -355,8 +255,8 @@ const ProductBasicInfo: React.FC<{
|
|||||||
onAddCategory={onAddCategory}
|
onAddCategory={onAddCategory}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium">Unit Type</label>
|
<label className="text-sm font-medium leading-none">Unit Type</label>
|
||||||
<UnitTypeSelect
|
<UnitTypeSelect
|
||||||
value={productData.unitType}
|
value={productData.unitType}
|
||||||
setProductData={setProductData}
|
setProductData={setProductData}
|
||||||
@@ -364,6 +264,117 @@ const ProductBasicInfo: React.FC<{
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-muted/20 rounded-xl border border-border/40 overflow-hidden">
|
||||||
|
<div className="p-4 border-b border-border/40 bg-muted/30">
|
||||||
|
<h3 className="text-sm font-semibold flex items-center gap-2">
|
||||||
|
Inventory Management
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-5 space-y-6">
|
||||||
|
<div className="flex items-center justify-between p-3 rounded-lg border border-border/40 bg-background/40">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<label htmlFor="stockTracking" className="text-sm font-medium cursor-pointer">Track Stock Quantity</label>
|
||||||
|
<p className="text-xs text-muted-foreground">Automatically update stock when orders are placed</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="stockTracking"
|
||||||
|
checked={productData.stockTracking !== false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setProductData({
|
||||||
|
...productData,
|
||||||
|
stockTracking: checked
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{productData.stockTracking !== false && (
|
||||||
|
<div className="grid grid-cols-2 gap-5 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="currentStock" className="text-sm font-medium text-muted-foreground">
|
||||||
|
Current Quantity
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="currentStock"
|
||||||
|
name="currentStock"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step={productData.unitType === 'gr' || productData.unitType === 'ml' ? '0.1' : '1'}
|
||||||
|
value={productData.currentStock || 0}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="0"
|
||||||
|
className="font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="lowStockThreshold" className="text-sm font-medium text-muted-foreground">
|
||||||
|
Low Stock Alert At
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="lowStockThreshold"
|
||||||
|
name="lowStockThreshold"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
step={productData.unitType === 'gr' || productData.unitType === 'ml' ? '0.1' : '1'}
|
||||||
|
value={productData.lowStockThreshold || 10}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="10"
|
||||||
|
className="font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-muted/20 rounded-xl border border-border/40 overflow-hidden">
|
||||||
|
<div className="p-4 border-b border-border/40 bg-muted/30 flex justify-between items-center">
|
||||||
|
<h3 className="text-sm font-semibold">Cost Analysis</h3>
|
||||||
|
<Badge variant="outline" className="text-[10px] font-normal">Optional</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="p-5">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label htmlFor="costPerUnit" className="text-sm font-medium">
|
||||||
|
Cost Per Unit
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<span className="absolute left-3 top-2.5 text-muted-foreground text-sm">$</span>
|
||||||
|
<Input
|
||||||
|
id="costPerUnit"
|
||||||
|
name="costPerUnit"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="0.01"
|
||||||
|
value={productData.costPerUnit || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="0.00"
|
||||||
|
className="pl-7 font-mono"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground mt-1.5">
|
||||||
|
Enter your cost to calculate profit margins automatically. This is never shown to customers.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between p-4 rounded-xl border border-border/40 bg-blue-500/5">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<label htmlFor="enabled" className="text-sm font-medium text-foreground">Product Visibility</label>
|
||||||
|
<p className="text-xs text-muted-foreground">Make this product visible in your store</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="enabled"
|
||||||
|
checked={productData.enabled !== false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setProductData({
|
||||||
|
...productData,
|
||||||
|
enabled: checked
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user