edit fixes

This commit is contained in:
Jake Kasper
2025-08-21 21:05:15 -04:00
parent 33894dec6c
commit a0a9ec5411
3 changed files with 341 additions and 77 deletions

View File

@@ -0,0 +1,60 @@
-- Safe category cleanup that handles existing singular forms
-- This will consolidate similar categories and fix naming
-- First, update any products that reference plural categories to point to singular ones
-- 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');
-- Update any references to 'Herbicides' to point to 'Herbicide' (if it exists)
UPDATE products SET category_id = (
SELECT id FROM product_categories WHERE name = 'Herbicide' LIMIT 1
) WHERE category_id = (
SELECT id FROM product_categories WHERE name = 'Herbicides' LIMIT 1
) AND EXISTS (SELECT 1 FROM product_categories WHERE name = 'Herbicide');
-- Update any references to 'Fungicides' to point to 'Fungicide' (if it exists)
UPDATE products SET category_id = (
SELECT id FROM product_categories WHERE name = 'Fungicide' LIMIT 1
) WHERE category_id = (
SELECT id FROM product_categories WHERE name = 'Fungicides' LIMIT 1
) AND EXISTS (SELECT 1 FROM product_categories WHERE name = 'Fungicide');
-- Update any references to 'Insecticides' to point to 'Insecticide' (if it exists)
UPDATE products SET category_id = (
SELECT id FROM product_categories WHERE name = 'Insecticide' LIMIT 1
) WHERE category_id = (
SELECT id FROM product_categories WHERE name = 'Insecticides' LIMIT 1
) AND EXISTS (SELECT 1 FROM product_categories WHERE name = 'Insecticide');
-- Delete plural categories that now have singular equivalents
DELETE FROM product_categories WHERE name IN ('Fertilizers', 'Herbicides', 'Fungicides', 'Insecticides')
AND EXISTS (SELECT 1 FROM product_categories WHERE name IN ('Fertilizer', 'Herbicide', 'Fungicide', 'Insecticide'));
-- Update remaining plural categories to singular (only if singular doesn't exist)
UPDATE product_categories SET name = 'Surfactant' WHERE name = 'Surfactants'
AND NOT EXISTS (SELECT 1 FROM product_categories WHERE name = 'Surfactant');
UPDATE product_categories SET name = 'Adjuvant' WHERE name = 'Adjuvants'
AND NOT EXISTS (SELECT 1 FROM product_categories WHERE name = 'Adjuvant');
UPDATE product_categories SET name = 'Growth Regulator' WHERE name = 'Growth Regulators'
AND NOT EXISTS (SELECT 1 FROM product_categories WHERE name = 'Growth Regulator');
-- Add any missing core categories (only if they don't exist)
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

@@ -80,7 +80,14 @@ const userProductSchema = Joi.object({
customName: Joi.string().max(255).allow(null).optional(),
customRateAmount: Joi.number().positive().allow(null).optional(),
customRateUnit: Joi.string().max(50).allow(null).optional(),
notes: Joi.string().allow(null, '').optional()
notes: Joi.string().allow(null, '').optional(),
// Additional fields for advanced editing
brand: Joi.string().max(100).allow(null).optional(),
categoryId: Joi.number().integer().positive().allow(null).optional(),
productType: Joi.string().valid('granular', 'liquid', 'seed', 'powder').allow(null).optional(),
activeIngredients: Joi.string().allow(null).optional(),
description: Joi.string().allow(null).optional(),
isAdvancedEdit: Joi.boolean().optional()
});
// Application validation schemas

View File

@@ -583,12 +583,22 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
customName: product.customName || '',
customRateAmount: product.customRateAmount || '',
customRateUnit: product.customRateUnit || 'lbs/1000 sq ft',
notes: product.notes || ''
notes: product.notes || '',
// Add fields for full editing capability
brand: product.brand || '',
categoryId: product.categoryId || '',
productType: product.productType || '',
activeIngredients: product.activeIngredients || '',
description: product.description || ''
});
const [editMode, setEditMode] = useState('basic'); // 'basic' or 'advanced'
const handleSubmit = (e) => {
e.preventDefault();
if (editMode === 'basic') {
// Basic mode validation
if (!formData.productId && !formData.customName) {
toast.error('Please select a base product or enter a custom name');
return;
@@ -603,6 +613,38 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
};
onSubmit(submitData);
} else {
// Advanced mode validation and submission
if (!formData.customName) {
toast.error('Product name is required');
return;
}
if (!formData.categoryId) {
toast.error('Category is required');
return;
}
if (!formData.productType) {
toast.error('Product type is required');
return;
}
const submitData = {
productId: formData.productId ? parseInt(formData.productId) : null,
customName: formData.customName,
customRateAmount: formData.customRateAmount ? parseFloat(formData.customRateAmount) : null,
customRateUnit: formData.customRateUnit || null,
notes: formData.notes || null,
// Additional fields for advanced mode
brand: formData.brand || null,
categoryId: formData.categoryId ? parseInt(formData.categoryId) : null,
productType: formData.productType || null,
activeIngredients: formData.activeIngredients || null,
description: formData.description || null,
isAdvancedEdit: true // Flag to indicate this is an advanced edit
};
onSubmit(submitData);
}
};
return (
@@ -610,7 +652,38 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
<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>
{/* Edit Mode Toggle */}
<div className="mb-4">
<div className="flex bg-gray-100 rounded-lg p-1">
<button
type="button"
onClick={() => setEditMode('basic')}
className={`flex-1 py-2 px-4 rounded-md text-sm font-medium transition-colors ${
editMode === 'basic'
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Basic Edit
</button>
<button
type="button"
onClick={() => setEditMode('advanced')}
className={`flex-1 py-2 px-4 rounded-md text-sm font-medium transition-colors ${
editMode === 'advanced'
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
Advanced Edit
</button>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
{editMode === 'basic' ? (
// Basic editing - existing functionality
<>
<div>
<label className="label">Base Product (Optional)</label>
<select
@@ -677,6 +750,130 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
placeholder="Special mixing instructions, storage notes, etc."
/>
</div>
</>
) : (
// Advanced editing - all fields
<>
<div>
<label className="label">Product Name *</label>
<input
type="text"
className="input"
value={formData.customName}
onChange={(e) => setFormData({ ...formData, customName: e.target.value })}
placeholder="Product name"
required
/>
</div>
<div>
<label className="label">Brand</label>
<input
type="text"
className="input"
value={formData.brand}
onChange={(e) => setFormData({ ...formData, brand: e.target.value })}
placeholder="Manufacturer or brand name"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Category *</label>
<select
className="input"
value={formData.categoryId}
onChange={(e) => setFormData({ ...formData, categoryId: e.target.value })}
required
>
<option value="">Select category...</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
<div>
<label className="label">Product Type *</label>
<select
className="input"
value={formData.productType}
onChange={(e) => setFormData({ ...formData, productType: e.target.value })}
required
>
<option value="">Select type...</option>
<option value="liquid">Liquid</option>
<option value="granular">Granular</option>
<option value="seed">Seed</option>
<option value="powder">Powder</option>
</select>
</div>
</div>
<div>
<label className="label">Active Ingredients</label>
<input
type="text"
className="input"
value={formData.activeIngredients}
onChange={(e) => setFormData({ ...formData, activeIngredients: e.target.value })}
placeholder="e.g., 2,4-D 38.3%, Dicamba 2.77%"
/>
</div>
<div>
<label className="label">Description</label>
<textarea
className="input"
rows="3"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="Product description and usage information"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Default 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">