fix edit on products

This commit is contained in:
Jake Kasper
2025-08-21 21:00:52 -04:00
parent d3092b4c7b
commit 33894dec6c
2 changed files with 199 additions and 3 deletions

View 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;

View File

@@ -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;