| |
| |
| |
| |
|
|
| import React, { useState, useMemo } from 'react'; |
| import { motion, AnimatePresence } from 'motion/react'; |
| import { |
| Package, |
| Plus, |
| History, |
| Trash2, |
| Edit, |
| Folder, |
| AlertTriangle, |
| ChevronRight, |
| ArrowUpRight, |
| ArrowDownRight, |
| Search, |
| Filter, |
| Check, |
| PlusCircle, |
| TrendingUp, |
| Sliders, |
| DollarSign, |
| Tag |
| } from 'lucide-react'; |
| import { TireProduct, StockHistoryItem, StaffUser, SystemSettings } from '../types'; |
|
|
| interface InventoryManagerProps { |
| products: TireProduct[]; |
| history: StockHistoryItem[]; |
| currentStaff: StaffUser; |
| settings: SystemSettings; |
| onAdjustProductStock: (productId: string, quantityChange: number, type: StockHistoryItem['type'], reason: string) => void; |
| onAddNewProduct: (product: TireProduct) => void; |
| onDeleteProduct: (productId: string) => void; |
| } |
|
|
| export default function InventoryManager({ |
| products, |
| history, |
| currentStaff, |
| settings, |
| onAdjustProductStock, |
| onAddNewProduct, |
| onDeleteProduct |
| }: InventoryManagerProps) { |
| const [selectedProduct, setSelectedProduct] = useState<TireProduct | null>(null); |
| const [inventorySearch, setInventorySearch] = useState(''); |
| const [activeSegment, setActiveSegment] = useState<string>('All'); |
| |
| |
| const [showAddModal, setShowAddModal] = useState(false); |
| |
| |
| const [adjustmentQty, setAdjustmentQty] = useState<number>(5); |
| const [adjustmentType, setAdjustmentType] = useState<'STOCK_IN' | 'STOCK_OUT' | 'MANUAL_ADJUSTMENT'>('STOCK_IN'); |
| const [adjustmentReason, setAdjustmentReason] = useState(''); |
|
|
| |
| const [newBrand, setNewBrand] = useState(''); |
| const [newModel, setNewModel] = useState(''); |
| const [newSize, setNewSize] = useState(''); |
| const [newCategory, setNewCategory] = useState<TireProduct['category']>('Passenger'); |
| const [newPrice, setNewPrice] = useState<number>(15000); |
| const [newPurchasePrice, setNewPurchasePrice] = useState<number>(11000); |
| const [newStock, setNewStock] = useState<number>(10); |
| const [newMinStock, setNewMinStock] = useState<number>(5); |
| const [newSku, setNewSku] = useState(''); |
| const [newDesc, setNewDesc] = useState(''); |
|
|
| |
| const filteredProducts = useMemo(() => { |
| return products.filter(p => { |
| const matchSearch = |
| p.brand.toLowerCase().includes(inventorySearch.toLowerCase()) || |
| p.model.toLowerCase().includes(inventorySearch.toLowerCase()) || |
| p.size.toLowerCase().includes(inventorySearch.toLowerCase()) || |
| p.sku.toLowerCase().includes(inventorySearch.toLowerCase()); |
| |
| const matchSegment = activeSegment === 'All' || p.category === activeSegment; |
| return matchSearch && matchSegment; |
| }); |
| }, [products, inventorySearch, activeSegment]); |
|
|
| |
| const selectedProductHistory = useMemo(() => { |
| if (!selectedProduct) return []; |
| return history |
| .filter(h => h.productId === selectedProduct.id) |
| .sort((a, b) => new Date(b.dateTime).getTime() - new Date(a.dateTime).getTime()); |
| }, [history, selectedProduct]); |
|
|
| |
| const totalSkuInSegment = filteredProducts.length; |
| const totalCountInSegment = filteredProducts.reduce((sum, p) => sum + p.stock, 0); |
| const totalValuationInSegment = filteredProducts.reduce((sum, p) => sum + (p.stock * p.price), 0); |
| const totalCostValuation = filteredProducts.reduce((sum, p) => sum + (p.stock * p.purchasePrice), 0); |
| const projectedMargin = totalValuationInSegment - totalCostValuation; |
|
|
| |
| const handleAdjustmentSubmit = (e: React.FormEvent) => { |
| e.preventDefault(); |
| if (!selectedProduct) return; |
| if (adjustmentQty <= 0) { |
| alert("Adjustment quantity must be greater than zero."); |
| return; |
| } |
| if (!adjustmentReason.trim()) { |
| alert("Please state concrete audit adjustment comments."); |
| return; |
| } |
|
|
| |
| let finalQtyChange = adjustmentQty; |
| if (adjustmentType === 'STOCK_OUT') { |
| finalQtyChange = -adjustmentQty; |
| } else if (adjustmentType === 'MANUAL_ADJUSTMENT') { |
| |
| |
| |
| |
| |
| finalQtyChange = adjustmentQty; |
| } |
|
|
| |
| if (adjustmentType === 'STOCK_OUT' && selectedProduct.stock + finalQtyChange < 0) { |
| alert("Cannot complete operation! Net inventory stock cannot go below 0."); |
| return; |
| } |
|
|
| onAdjustProductStock( |
| selectedProduct.id, |
| finalQtyChange, |
| adjustmentType, |
| adjustmentReason.trim() |
| ); |
|
|
| |
| const updatedProd = products.find(p => p.id === selectedProduct.id); |
| if (updatedProd) { |
| setSelectedProduct({ |
| ...updatedProd, |
| stock: updatedProd.stock + finalQtyChange |
| }); |
| } |
|
|
| |
| setAdjustmentQty(5); |
| setAdjustmentReason(''); |
| setAdjustmentType('STOCK_IN'); |
| }; |
|
|
| |
| const handleCreateProductSubmit = (e: React.FormEvent) => { |
| e.preventDefault(); |
| if (!newBrand.trim() || !newModel.trim() || !newSize.trim() || !newSku.trim()) { |
| alert("Brand, Model, Size and SKU are mandatory properties."); |
| return; |
| } |
|
|
| |
| if (products.some(p => p.sku.toUpperCase() === newSku.toUpperCase())) { |
| alert("SKU already active under another tire spec. Assign unique tracking barcode."); |
| return; |
| } |
|
|
| const newProd: TireProduct = { |
| id: 'p_' + Math.random().toString(36).substr(2, 9), |
| sku: newSku.toUpperCase(), |
| brand: newBrand, |
| model: newModel, |
| size: newSize, |
| category: newCategory, |
| stock: newStock, |
| minStock: newMinStock, |
| price: newPrice, |
| purchasePrice: newPurchasePrice, |
| description: newDesc.trim() || undefined |
| }; |
|
|
| onAddNewProduct(newProd); |
|
|
| |
| setNewBrand(''); |
| setNewModel(''); |
| setNewSize(''); |
| setNewSku(''); |
| setNewDesc(''); |
| setNewStock(10); |
| setNewMinStock(5); |
| setNewPrice(15000); |
| setNewPurchasePrice(11000); |
| setShowAddModal(false); |
| }; |
|
|
| const handleProductDelete = (productId: string) => { |
| if (confirm("Are you absolutely sure you want to retire and remove this tire product spec? Historical invoice records will still show details, but stock adjustments will be closed.")) { |
| onDeleteProduct(productId); |
| if (selectedProduct?.id === productId) { |
| setSelectedProduct(null); |
| } |
| } |
| }; |
|
|
| return ( |
| <div className="space-y-6" id="warehouse-management-cockpit"> |
| {/* Category Summaries & Valuation overview cards */} |
| <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4" id="inventory-valuation"> |
| <div className="bg-white border border-slate-200 p-5 rounded-xl shadow-sm space-y-1"> |
| <span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Active Segment SKUs</span> |
| <h4 className="text-lg font-bold text-slate-900">{totalSkuInSegment} Profiles</h4> |
| <p className="text-xs text-slate-500">Currently filtered category</p> |
| </div> |
| |
| <div className="bg-white border border-slate-200 p-5 rounded-xl shadow-sm space-y-1"> |
| <span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Physical Tires Inventory</span> |
| <h4 className="text-lg font-bold text-slate-900">{totalCountInSegment} units</h4> |
| <p className="text-xs text-slate-500">Physical tire units stored</p> |
| </div> |
| |
| <div className="bg-white border border-slate-200 p-5 rounded-xl shadow-sm space-y-1"> |
| <span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Estimated Retail Value</span> |
| <h4 className="text-lg font-bold text-slate-900">{settings.currencySymbol} {totalValuationInSegment.toLocaleString()}</h4> |
| <p className="text-xs text-slate-500">Total potential turnover value</p> |
| </div> |
| |
| <div className="bg-white border border-slate-200 p-5 rounded-xl shadow-sm space-y-1"> |
| <span className="text-[10px] text-slate-400 font-bold uppercase tracking-wider">Projected Profit Margin</span> |
| <h4 className="text-lg font-bold text-emerald-600">{settings.currencySymbol} {projectedMargin.toLocaleString()}</h4> |
| <p className="text-xs text-emerald-500 font-medium font-sans">Est. average margin: {Math.round((projectedMargin / (totalValuationInSegment || 1)) * 100)}%</p> |
| </div> |
| </div> |
| |
| <div className="grid grid-cols-1 lg:grid-cols-12 gap-6"> |
| {/* LEFT COLUMN: Core product list table */} |
| <div className={`${selectedProduct ? 'lg:col-span-7' : 'lg:col-span-12'} bg-white border border-slate-200 rounded-xl shadow-sm p-5 space-y-4 transition-all duration-300`}> |
| <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-b border-slate-100 pb-4"> |
| <div className="space-y-1"> |
| <h3 className="text-sm font-bold text-slate-950 flex items-center gap-2 uppercase tracking-wide"> |
| <Sliders className="w-4.5 h-4.5 text-slate-800" /> |
| Haider Tire Warehouse Records |
| </h3> |
| <p className="text-xs text-slate-400">Configure stocks details, track low alerts, and view specific SKU histories</p> |
| </div> |
| |
| {['owner', 'manager'].includes(currentStaff.role) && ( |
| <button |
| type="button" |
| onClick={() => setShowAddModal(true)} |
| className="bg-slate-900 hover:bg-slate-800 text-white font-medium text-xs px-3.5 py-2.5 rounded-lg cursor-pointer shadow-sm transition flex items-center gap-2" |
| > |
| <PlusCircle className="w-4 h-4" /> |
| Add New SKU |
| </button> |
| )} |
| </div> |
| |
| {/* Filtering row */} |
| <div className="flex flex-col sm:flex-row gap-3"> |
| {/* Search */} |
| <div className="relative flex-1"> |
| <span className="absolute left-3.5 top-3 text-slate-400"> |
| <Search className="w-4 h-4" /> |
| </span> |
| <input |
| type="text" |
| placeholder="Search by Brand, size parameter (e.g. R15), or SKU..." |
| value={inventorySearch} |
| onChange={(e) => setInventorySearch(e.target.value)} |
| className="w-full text-xs border border-slate-200 outline-none focus:border-slate-800 bg-white hover:bg-slate-50/50 focus:bg-white rounded-lg pl-10 pr-4 py-2.5 transition" |
| /> |
| </div> |
| |
| {/* Filter segments tab */} |
| <div className="flex gap-1 overflow-x-auto pb-1 shrink-0 font-sans"> |
| {['All', 'Passenger', 'SUV', 'Commercial', 'Light Truck', 'Truck/Bus'].map(seg => ( |
| <button |
| key={seg} |
| onClick={() => setActiveSegment(seg)} |
| className={`text-xs px-3 py-1.5 font-semibold rounded-lg shrink-0 cursor-pointer transition ${ |
| activeSegment === seg |
| ? 'bg-slate-900 text-white shadow-sm' |
| : 'bg-slate-50 text-slate-600 hover:bg-slate-100 border border-slate-200/60' |
| }`} |
| > |
| {seg} |
| </button> |
| ))} |
| </div> |
| </div> |
| |
| {/* Core Table Grid layout */} |
| <div className="overflow-x-auto scrollbar-thin"> |
| <table className="w-full text-left border-collapse font-sans text-xs"> |
| <thead className="bg-slate-50 text-slate-500 uppercase text-[10px] tracking-wider leading-none"> |
| <tr className="border-b border-slate-200"> |
| <th className="py-3 px-4 font-bold">SKU / Tire Name</th> |
| <th className="py-3 px-4 font-bold">Category</th> |
| <th className="py-3 px-3 font-bold text-center">Remaining</th> |
| <th className="py-3 px-3 font-bold text-right">Cost Price</th> |
| <th className="py-3 px-4 font-bold text-right font-sans font-extrabold">Sell Price</th> |
| <th className="py-3 px-4 text-center">Audit Actions</th> |
| </tr> |
| </thead> |
| <tbody className="divide-y divide-slate-100 align-middle"> |
| {filteredProducts.length === 0 ? ( |
| <tr> |
| <td colSpan={6} className="text-center py-16 text-slate-400 bg-slate-50/20 rounded-xl">No products matched current filters or search query.</td> |
| </tr> |
| ) : ( |
| filteredProducts.map((p) => { |
| const isLow = p.stock <= p.minStock; |
| const isSelected = selectedProduct?.id === p.id; |
| |
| return ( |
| <tr |
| key={p.id} |
| className={`transition hover:bg-blue-50/10 cursor-pointer ${ |
| isSelected ? 'bg-blue-50/30 font-semibold' : '' |
| }`} |
| onClick={() => setSelectedProduct(p)} |
| > |
| <td className="py-4 px-4 space-y-1"> |
| <div className="flex items-center gap-1.5"> |
| <span className="font-bold text-slate-950 text-sm leading-tight">{p.brand} {p.model}</span> |
| {isLow && ( |
| <span className="bg-red-100 text-red-800 text-[9px] font-black px-1.5 py-0.5 rounded animate-pulse"> |
| ALERT |
| </span> |
| )} |
| </div> |
| <div className="flex items-center gap-2 text-[10px] text-slate-500"> |
| <span className="font-mono text-xs text-blue-600 bg-blue-50 font-bold px-1 rounded">{p.size}</span> |
| <span>•</span> |
| <span className="font-mono text-[9px] uppercase font-bold text-slate-400 bg-slate-100 px-1 rounded">SKU: {p.sku}</span> |
| </div> |
| </td> |
| |
| <td className="py-4 px-4"> |
| <span className="bg-slate-100 text-slate-700 font-bold text-[10px] px-2.5 py-1 rounded-full">{p.category}</span> |
| </td> |
| |
| <td className="py-4 px-3 text-center"> |
| <span className={`font-mono font-black text-sm px-2.5 py-1 rounded-lg ${ |
| p.stock === 0 ? 'bg-red-100 text-red-800' : |
| isLow ? 'bg-amber-100 text-amber-800' : 'bg-slate-100 text-slate-900' |
| }`}> |
| {p.stock} |
| </span> |
| </td> |
| |
| <td className="py-4 px-3 text-right text-slate-500 font-mono"> |
| {settings.currencySymbol} {p.purchasePrice.toLocaleString()} |
| </td> |
| |
| <td className="py-4 px-4 text-right font-extrabold text-slate-950 text-sm font-sans"> |
| {settings.currencySymbol} {p.price.toLocaleString()} |
| </td> |
| |
| <td className="py-4 px-4 text-center"> |
| <div className="flex items-center justify-center gap-2" onClick={(e) => e.stopPropagation()}> |
| <button |
| type="button" |
| onClick={() => setSelectedProduct(p)} |
| className="text-blue-600 hover:text-blue-800 p-1.5 rounded-lg hover:bg-blue-50 transition" |
| title="Examine history logs" |
| > |
| <History className="w-4 h-4" /> |
| </button> |
| {['owner', 'manager'].includes(currentStaff.role) && ( |
| <button |
| type="button" |
| onClick={() => handleProductDelete(p.id)} |
| className="text-slate-400 hover:text-red-600 p-1.5 rounded-lg hover:bg-slate-100 transition" |
| title="Remove SKU reference" |
| > |
| <Trash2 className="w-4 h-4" /> |
| </button> |
| )} |
| </div> |
| </td> |
| </tr> |
| ); |
| }) |
| )} |
| </tbody> |
| </table> |
| </div> |
| </div> |
| |
| {/* RIGHT COLUMN: Selective detailed histories & manual stock operations drawer */} |
| {selectedProduct && ( |
| <div className="lg:col-span-5 space-y-6"> |
| <div className="bg-white border border-slate-200 rounded-xl shadow-sm p-5 space-y-5 relative overflow-visible"> |
| <button |
| type="button" |
| onClick={() => setSelectedProduct(null)} |
| className="absolute top-4 right-4 bg-slate-50 hover:bg-slate-150 text-slate-400 hover:text-slate-800 font-bold px-2 py-1 rounded text-xs cursor-pointer" |
| > |
| ✕ |
| </button> |
| |
| {/* Product Header Card */} |
| <div className="space-y-3.5"> |
| <span className="bg-slate-100 text-slate-800 border border-slate-200/60 text-[10px] font-semibold px-2.5 py-0.5 rounded-md uppercase tracking-wider font-sans"> |
| Detailed Tire Portfolio & Log |
| </span> |
| |
| <div> |
| <h4 className="text-lg font-bold text-slate-950 uppercase">{selectedProduct.brand}</h4> |
| <p className="text-xs font-semibold text-slate-500 leading-tight">{selectedProduct.model}</p> |
| </div> |
| |
| <div className="flex flex-wrap gap-2 text-xs"> |
| <span className="font-sans text-[11px] font-semibold text-slate-800 bg-slate-100 border border-slate-200/50 px-2.5 py-0.5 rounded-md"> |
| {selectedProduct.size} |
| </span> |
| <span className="bg-slate-150 text-slate-600 font-sans text-[10px] font-bold uppercase rounded-md px-2 py-0.5 self-center"> |
| SKU: {selectedProduct.sku} |
| </span> |
| </div> |
| </div> |
| |
| {/* MANUAL ADJUSTER STOCK PANEL FOR AUTHORIZED STAFF */} |
| {['owner', 'manager'].includes(currentStaff.role) ? ( |
| <form onSubmit={handleAdjustmentSubmit} className="border-t border-b border-dashed border-slate-200 py-4 space-y-4"> |
| <div className="flex items-center gap-1.5"> |
| <Sliders className="w-4.5 h-4.5 text-blue-600" /> |
| <span className="text-xs font-bold text-slate-950 uppercase">Manual Stock Adjuster Node</span> |
| </div> |
| |
| <div className="grid grid-cols-2 gap-3"> |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Action Mode</label> |
| <select |
| value={adjustmentType} |
| onChange={(e) => setAdjustmentType(e.target.value as any)} |
| className="w-full text-xs border border-slate-200 bg-white shadow-sm p-2 rounded-xl outline-none" |
| > |
| <option value="STOCK_IN">Stock In (+ Incoming)</option> |
| <option value="STOCK_OUT">Stock Out (- Outgoing)</option> |
| <option value="MANUAL_ADJUSTMENT">Recount Adjustment (+/-)</option> |
| </select> |
| </div> |
| |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Tire count change</label> |
| <input |
| type="number" |
| min="1" |
| max="500" |
| value={adjustmentQty} |
| onChange={(e) => setAdjustmentQty(Math.max(1, parseInt(e.target.value) || 0))} |
| className="w-full text-xs font-mono border border-slate-200 bg-white p-2 rounded-xl outline-none focus:border-blue-500 shadow-inner" |
| /> |
| </div> |
| </div> |
| |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Audit Reason comments *</label> |
| <input |
| type="text" |
| placeholder="e.g. Visual count match: added cargo pallets..." |
| value={adjustmentReason} |
| onChange={(e) => setAdjustmentReason(e.target.value)} |
| className="w-full text-xs border border-slate-200 bg-white p-2 text-slate-700 outline-none focus:border-blue-500 rounded-xl" |
| /> |
| </div> |
| |
| <button |
| type="submit" |
| className="w-full bg-slate-900 hover:bg-slate-800 text-white text-xs font-bold py-2.5 rounded-xl transition cursor-pointer flex items-center justify-center gap-1 shadow" |
| > |
| <Check className="w-4 h-4" /> |
| Commit Stock Adjustment History |
| </button> |
| </form> |
| ) : ( |
| <div className="p-3 bg-slate-50 border border-slate-100 rounded-xl text-center text-xs text-slate-400 italic"> |
| Manual adjustments restricted to Owner & Managers. Contact Zain Brother to restock. |
| </div> |
| )} |
| |
| {/* CHRONOLOGICAL HISTORICAL LEDGER FOR THE ACTIVE TIRE */} |
| <div className="space-y-3"> |
| <div className="flex items-center gap-1.5 justify-between"> |
| <span className="text-xs font-bold text-slate-950 uppercase flex items-center gap-1"> |
| <History className="w-4 h-4 text-slate-700" /> |
| Tire Action History |
| </span> |
| <span className="bg-slate-100 text-slate-700 font-bold text-[10px] font-mono px-2 py-0.5 rounded"> |
| {selectedProductHistory.length} ledger logs |
| </span> |
| </div> |
| |
| <div className="space-y-2 max-h-72 overflow-y-auto pr-1"> |
| {selectedProductHistory.length === 0 ? ( |
| <div className="text-center py-10 text-slate-400 text-xs border border-dashed border-slate-150 rounded-xl"> |
| No matching transaction logs for this tire size yet. |
| </div> |
| ) : ( |
| selectedProductHistory.map((item) => { |
| const isAddition = item.type === 'STOCK_IN'; |
| const isReduction = item.type === 'STOCK_OUT'; |
| |
| return ( |
| <div key={item.id} className="border border-slate-50 hover:bg-slate-50/50 p-2.5 rounded-xl space-y-1 text-xs"> |
| <div className="flex justify-between items-center"> |
| <span className="font-mono text-[9px] text-slate-400"> |
| {new Date(item.dateTime).toLocaleString()} |
| </span> |
| |
| <div className="flex items-center gap-1"> |
| {isAddition ? ( |
| <span className="bg-emerald-100 text-emerald-800 text-[9px] font-bold px-1.5 py-0.5 rounded font-mono flex items-center gap-0.5"> |
| <ArrowUpRight className="w-3 h-3" /> |
| +{item.quantity} In |
| </span> |
| ) : isReduction ? ( |
| <span className="bg-red-100 text-red-800 text-[9px] font-bold px-1.5 py-0.5 rounded font-mono flex items-center gap-0.5"> |
| <ArrowDownRight className="w-3 h-3" /> |
| {item.quantity} Sold |
| </span> |
| ) : ( |
| <span className="bg-blue-100 text-blue-800 text-[9px] font-bold px-1.5 py-0.5 rounded font-mono"> |
| Recount: {item.quantity > 0 ? `+${item.quantity}` : item.quantity} |
| </span> |
| )} |
| </div> |
| </div> |
| |
| <p className="font-semibold text-slate-800 leading-tight"> |
| {item.reason} |
| </p> |
| |
| <div className="text-[10px] text-slate-500 pt-1 flex justify-between border-t border-slate-100/50"> |
| <span>Adjusted by: <span className="font-bold text-slate-700">{item.adjustedBy}</span></span> |
| <span>Resulting Stock: <strong className="font-mono">{item.resultingStock}</strong></span> |
| </div> |
| </div> |
| ); |
| }) |
| )} |
| </div> |
| </div> |
| </div> |
| </div> |
| )} |
| </div> |
| |
| {/* CORE MODAL: NEW PRODUCT ADDITION FORM */} |
| {showAddModal && ( |
| <div className="fixed inset-0 bg-slate-950/75 backdrop-blur-sm z-50 flex items-center justify-center p-4"> |
| <div className="bg-white rounded-2xl border border-slate-200 max-w-lg w-full p-6 md:p-8 space-y-6 relative shadow-2xl"> |
| <button |
| onClick={() => setShowAddModal(false)} |
| className="absolute top-4 right-4 bg-slate-100 hover:bg-slate-200 text-slate-500 hover:text-slate-900 font-bold px-3 py-1 rounded text-xs cursor-pointer" |
| > |
| ✕ |
| </button> |
| |
| <div className="pb-3 border-b border-slate-100 text-center"> |
| <h3 className="text-xl font-black text-slate-950">Add Brand-New Tire to Database</h3> |
| <p className="text-xs text-slate-500">Configure core sizing parameters, initial stock, and pricing thresholds</p> |
| </div> |
| |
| <form onSubmit={handleCreateProductSubmit} className="space-y-4 font-sans text-xs"> |
| <div className="grid grid-cols-2 gap-3"> |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Brand SKU Barcode *</label> |
| <input |
| type="text" |
| required |
| placeholder="e.g. DUN1856514" |
| value={newSku} |
| onChange={(e)=>setNewSku(e.target.value)} |
| className="w-full border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| /> |
| </div> |
| |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Category Type</label> |
| <select |
| value={newCategory} |
| onChange={(e)=>setNewCategory(e.target.value as any)} |
| className="w-full border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| > |
| <option value="Passenger">Passenger Radial</option> |
| <option value="SUV">SUV All-Terrain</option> |
| <option value="Commercial">Heavy Commercial Cargo</option> |
| <option value="Light Truck">Light Truck Commercial</option> |
| <option value="Truck/Bus">Truck/Bus Radial (TBR)</option> |
| </select> |
| </div> |
| </div> |
| |
| <div className="grid grid-cols-3 gap-3"> |
| <div className="space-y-1 col-span-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Brand / Maker *</label> |
| <input |
| type="text" |
| required |
| placeholder="e.g. Dunlop" |
| value={newBrand} |
| onChange={(e)=>setNewBrand(e.target.value)} |
| className="w-full border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| /> |
| </div> |
| |
| <div className="space-y-1 col-span-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Model Spec *</label> |
| <input |
| type="text" |
| required |
| placeholder="e.g. SP Touring" |
| value={newModel} |
| onChange={(e)=>setNewModel(e.target.value)} |
| className="w-full border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| /> |
| </div> |
| |
| <div className="space-y-1 col-span-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Size Dimension *</label> |
| <input |
| type="text" |
| required |
| placeholder="e.g. 195/65 R15" |
| value={newSize} |
| onChange={(e)=>setNewSize(e.target.value)} |
| className="w-full border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| /> |
| </div> |
| </div> |
| |
| <div className="grid grid-cols-2 gap-3 pb-3 border-b border-slate-100"> |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Purchase Cost price ({settings.currencySymbol})</label> |
| <input |
| type="number" |
| min="100" |
| required |
| value={newPurchasePrice} |
| onChange={(e)=>setNewPurchasePrice(parseInt(e.target.value) || 0)} |
| className="w-full font-mono border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| /> |
| </div> |
| |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">MSRP Sell Retail price ({settings.currencySymbol})</label> |
| <input |
| type="number" |
| min="100" |
| required |
| value={newPrice} |
| onChange={(e)=>setNewPrice(parseInt(e.target.value) || 0)} |
| className="w-full font-mono border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| /> |
| </div> |
| </div> |
| |
| <div className="grid grid-cols-2 gap-3 text-xs"> |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Initial Stock Count</label> |
| <input |
| type="number" |
| min="0" |
| required |
| value={newStock} |
| onChange={(e)=>setNewStock(parseInt(e.target.value) || 0)} |
| className="w-full border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| /> |
| </div> |
| |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Low Alert Threshold limit</label> |
| <input |
| type="number" |
| min="1" |
| required |
| value={newMinStock} |
| onChange={(e)=>setNewMinStock(parseInt(e.target.value) || 0)} |
| className="w-full border border-slate-200 bg-white p-2 rounded-xl outline-none" |
| /> |
| </div> |
| </div> |
| |
| <div className="space-y-1"> |
| <label className="text-[10px] text-slate-500 font-bold uppercase">Description / Performance parameters</label> |
| <textarea |
| rows={2} |
| placeholder="e.g. Robust local steel belted radial with comfortable rolling noise..." |
| value={newDesc} |
| onChange={(e)=>setNewDesc(e.target.value)} |
| className="w-full border border-slate-200 bg-white p-2 rounded-xl outline-none resize-none" |
| /> |
| </div> |
| |
| <button |
| type="submit" |
| className="w-full bg-slate-900 hover:bg-slate-800 text-white font-medium py-2.5 rounded-lg transition cursor-pointer shadow-sm flex items-center justify-center gap-1.5" |
| > |
| <Check className="w-4 h-4" /> |
| Initialize Tire Profile SKU |
| </button> |
| </form> |
| </div> |
| </div> |
| )} |
| </div> |
| ); |
| } |
|
|