fix edit on products
This commit is contained in:
38
backend/scripts/fix-categories.sql
Normal file
38
backend/scripts/fix-categories.sql
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
-- Fix duplicate categories and clean up product categories
|
||||||
|
-- This will consolidate similar categories and fix naming
|
||||||
|
|
||||||
|
-- Update products that reference categories we're about to merge
|
||||||
|
-- Update any references to 'Fertilizers' to point to 'Fertilizer' (if it exists)
|
||||||
|
UPDATE products SET category_id = (
|
||||||
|
SELECT id FROM product_categories WHERE name = 'Fertilizer' LIMIT 1
|
||||||
|
) WHERE category_id = (
|
||||||
|
SELECT id FROM product_categories WHERE name = 'Fertilizers' LIMIT 1
|
||||||
|
) AND EXISTS (SELECT 1 FROM product_categories WHERE name = 'Fertilizer');
|
||||||
|
|
||||||
|
-- Delete duplicate categories
|
||||||
|
DELETE FROM product_categories WHERE name IN ('Fertilizers')
|
||||||
|
AND EXISTS (SELECT 1 FROM product_categories WHERE name = 'Fertilizer');
|
||||||
|
|
||||||
|
-- Update category names to be consistent (singular form)
|
||||||
|
UPDATE product_categories SET name = 'Herbicide' WHERE name = 'Herbicides';
|
||||||
|
UPDATE product_categories SET name = 'Fertilizer' WHERE name = 'Fertilizers';
|
||||||
|
UPDATE product_categories SET name = 'Fungicide' WHERE name = 'Fungicides';
|
||||||
|
UPDATE product_categories SET name = 'Insecticide' WHERE name = 'Insecticides';
|
||||||
|
UPDATE product_categories SET name = 'Surfactant' WHERE name = 'Surfactants';
|
||||||
|
UPDATE product_categories SET name = 'Adjuvant' WHERE name = 'Adjuvants';
|
||||||
|
UPDATE product_categories SET name = 'Growth Regulator' WHERE name = 'Growth Regulators';
|
||||||
|
|
||||||
|
-- Add any missing core categories
|
||||||
|
INSERT INTO product_categories (name, description) VALUES
|
||||||
|
('Herbicide', 'Products for weed control and prevention'),
|
||||||
|
('Fertilizer', 'Nutrients for lawn growth and health'),
|
||||||
|
('Fungicide', 'Products for disease prevention and treatment'),
|
||||||
|
('Insecticide', 'Products for insect control'),
|
||||||
|
('Pre-emergent', 'Products that prevent weeds from germinating'),
|
||||||
|
('Post-emergent', 'Products that kill existing weeds'),
|
||||||
|
('Growth Regulator', 'Products that modify plant growth'),
|
||||||
|
('Surfactant', 'Products that improve spray coverage and penetration'),
|
||||||
|
('Adjuvant', 'Products that enhance pesticide performance'),
|
||||||
|
('Seed', 'Grass seeds and seed treatments'),
|
||||||
|
('Soil Amendment', 'Products that improve soil conditions')
|
||||||
|
ON CONFLICT (name) DO NOTHING;
|
||||||
@@ -21,6 +21,8 @@ const Products = () => {
|
|||||||
const [selectedCategory, setSelectedCategory] = useState('');
|
const [selectedCategory, setSelectedCategory] = useState('');
|
||||||
const [selectedType, setSelectedType] = useState('');
|
const [selectedType, setSelectedType] = useState('');
|
||||||
const [activeTab, setActiveTab] = useState('shared'); // 'shared' or 'custom'
|
const [activeTab, setActiveTab] = useState('shared'); // 'shared' or 'custom'
|
||||||
|
const [editingProduct, setEditingProduct] = useState(null);
|
||||||
|
const [showEditForm, setShowEditForm] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
@@ -88,6 +90,24 @@ const Products = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEditProduct = (product) => {
|
||||||
|
setEditingProduct(product);
|
||||||
|
setShowEditForm(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateProduct = async (productData) => {
|
||||||
|
try {
|
||||||
|
await productsAPI.updateUserProduct(editingProduct.id, productData);
|
||||||
|
toast.success('Product updated successfully!');
|
||||||
|
setShowEditForm(false);
|
||||||
|
setEditingProduct(null);
|
||||||
|
fetchData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update product:', error);
|
||||||
|
toast.error('Failed to update product');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const ProductCard = ({ product, isUserProduct = false }) => (
|
const ProductCard = ({ product, isUserProduct = false }) => (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="flex justify-between items-start mb-3">
|
<div className="flex justify-between items-start mb-3">
|
||||||
@@ -105,9 +125,13 @@ const Products = () => {
|
|||||||
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium mt-1 ${
|
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium mt-1 ${
|
||||||
product.productType === 'liquid'
|
product.productType === 'liquid'
|
||||||
? 'bg-blue-100 text-blue-800'
|
? 'bg-blue-100 text-blue-800'
|
||||||
: 'bg-green-100 text-green-800'
|
: product.productType === 'granular'
|
||||||
|
? 'bg-green-100 text-green-800'
|
||||||
|
: 'bg-gray-100 text-gray-800'
|
||||||
}`}>
|
}`}>
|
||||||
{product.productType || 'Unknown'}
|
{product.productType ?
|
||||||
|
product.productType.charAt(0).toUpperCase() + product.productType.slice(1)
|
||||||
|
: 'Type not set'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +139,7 @@ const Products = () => {
|
|||||||
{isUserProduct && (
|
{isUserProduct && (
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => {/* Handle edit */}}
|
onClick={() => handleEditProduct(product)}
|
||||||
className="p-1 text-gray-400 hover:text-blue-600"
|
className="p-1 text-gray-400 hover:text-blue-600"
|
||||||
>
|
>
|
||||||
<PencilIcon className="h-4 w-4" />
|
<PencilIcon className="h-4 w-4" />
|
||||||
@@ -341,6 +365,20 @@ const Products = () => {
|
|||||||
categories={categories}
|
categories={categories}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Edit Product Form Modal */}
|
||||||
|
{showEditForm && editingProduct && (
|
||||||
|
<EditProductModal
|
||||||
|
product={editingProduct}
|
||||||
|
onSubmit={handleUpdateProduct}
|
||||||
|
onCancel={() => {
|
||||||
|
setShowEditForm(false);
|
||||||
|
setEditingProduct(null);
|
||||||
|
}}
|
||||||
|
sharedProducts={sharedProducts}
|
||||||
|
categories={categories}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -538,4 +576,124 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Edit Product Modal Component
|
||||||
|
const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categories }) => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
productId: product.baseProductId || '',
|
||||||
|
customName: product.customName || '',
|
||||||
|
customRateAmount: product.customRateAmount || '',
|
||||||
|
customRateUnit: product.customRateUnit || 'lbs/1000 sq ft',
|
||||||
|
notes: product.notes || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!formData.productId && !formData.customName) {
|
||||||
|
toast.error('Please select a base product or enter a custom name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitData = {
|
||||||
|
productId: formData.productId ? parseInt(formData.productId) : null,
|
||||||
|
customName: formData.customName || null,
|
||||||
|
customRateAmount: formData.customRateAmount ? parseFloat(formData.customRateAmount) : null,
|
||||||
|
customRateUnit: formData.customRateUnit || null,
|
||||||
|
notes: formData.notes || null
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit(submitData);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white rounded-lg p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto">
|
||||||
|
<h3 className="text-lg font-semibold mb-4">Edit Product</h3>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="label">Base Product (Optional)</label>
|
||||||
|
<select
|
||||||
|
className="input"
|
||||||
|
value={formData.productId}
|
||||||
|
onChange={(e) => setFormData({ ...formData, productId: e.target.value })}
|
||||||
|
>
|
||||||
|
<option value="">Select a base product...</option>
|
||||||
|
{sharedProducts.map((sharedProduct) => (
|
||||||
|
<option key={sharedProduct.id} value={sharedProduct.id}>
|
||||||
|
{sharedProduct.name} {sharedProduct.brand && `- ${sharedProduct.brand}`}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="label">Custom Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input"
|
||||||
|
value={formData.customName}
|
||||||
|
onChange={(e) => setFormData({ ...formData, customName: e.target.value })}
|
||||||
|
placeholder="e.g., My 24-0-11 Blend, Custom Herbicide Mix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="label">Application Rate</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
className="input"
|
||||||
|
value={formData.customRateAmount}
|
||||||
|
onChange={(e) => setFormData({ ...formData, customRateAmount: e.target.value })}
|
||||||
|
placeholder="2.5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="label">Rate Unit</label>
|
||||||
|
<select
|
||||||
|
className="input"
|
||||||
|
value={formData.customRateUnit}
|
||||||
|
onChange={(e) => setFormData({ ...formData, customRateUnit: e.target.value })}
|
||||||
|
>
|
||||||
|
<option value="lbs/1000 sq ft">lbs/1000 sq ft</option>
|
||||||
|
<option value="oz/1000 sq ft">oz/1000 sq ft</option>
|
||||||
|
<option value="gal/acre">gal/acre</option>
|
||||||
|
<option value="fl oz/1000 sq ft">fl oz/1000 sq ft</option>
|
||||||
|
<option value="ml/1000 sq ft">ml/1000 sq ft</option>
|
||||||
|
<option value="tbsp/1000 sq ft">tbsp/1000 sq ft</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="label">Notes</label>
|
||||||
|
<textarea
|
||||||
|
className="input"
|
||||||
|
rows="3"
|
||||||
|
value={formData.notes}
|
||||||
|
onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
|
||||||
|
placeholder="Special mixing instructions, storage notes, etc."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3 pt-4">
|
||||||
|
<button type="submit" className="btn-primary flex-1">
|
||||||
|
Update Product
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
className="btn-secondary flex-1"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Products;
|
export default Products;
|
||||||
Reference in New Issue
Block a user