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 [selectedType, setSelectedType] = useState('');
|
||||
const [activeTab, setActiveTab] = useState('shared'); // 'shared' or 'custom'
|
||||
const [editingProduct, setEditingProduct] = useState(null);
|
||||
const [showEditForm, setShowEditForm] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
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 }) => (
|
||||
<div className="card">
|
||||
<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 ${
|
||||
product.productType === 'liquid'
|
||||
? '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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,7 +139,7 @@ const Products = () => {
|
||||
{isUserProduct && (
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => {/* Handle edit */}}
|
||||
onClick={() => handleEditProduct(product)}
|
||||
className="p-1 text-gray-400 hover:text-blue-600"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
@@ -341,6 +365,20 @@ const Products = () => {
|
||||
categories={categories}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Edit Product Form Modal */}
|
||||
{showEditForm && editingProduct && (
|
||||
<EditProductModal
|
||||
product={editingProduct}
|
||||
onSubmit={handleUpdateProduct}
|
||||
onCancel={() => {
|
||||
setShowEditForm(false);
|
||||
setEditingProduct(null);
|
||||
}}
|
||||
sharedProducts={sharedProducts}
|
||||
categories={categories}
|
||||
/>
|
||||
)}
|
||||
</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;
|
||||
Reference in New Issue
Block a user