product updates

This commit is contained in:
Jake Kasper
2025-08-21 20:24:55 -04:00
parent 81f800b5de
commit d3092b4c7b
2 changed files with 72 additions and 5 deletions

View File

@@ -76,11 +76,11 @@ const productRateSchema = Joi.object({
}); });
const userProductSchema = Joi.object({ const userProductSchema = Joi.object({
productId: Joi.number().integer().positive(), productId: Joi.number().integer().positive().allow(null).optional(),
customName: Joi.string().max(255), customName: Joi.string().max(255).allow(null).optional(),
customRateAmount: Joi.number().positive(), customRateAmount: Joi.number().positive().allow(null).optional(),
customRateUnit: Joi.string().max(50), customRateUnit: Joi.string().max(50).allow(null).optional(),
notes: Joi.string() notes: Joi.string().allow(null, '').optional()
}); });
// Application validation schemas // Application validation schemas

View File

@@ -350,6 +350,10 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
productId: '', productId: '',
customName: '', customName: '',
categoryId: '',
productType: 'granular',
brand: '',
activeIngredients: '',
customRateAmount: '', customRateAmount: '',
customRateUnit: 'lbs/1000 sq ft', customRateUnit: 'lbs/1000 sq ft',
notes: '' notes: ''
@@ -363,6 +367,12 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
return; return;
} }
// If creating a completely custom product (no base product), require more fields
if (!formData.productId && (!formData.categoryId || !formData.productType)) {
toast.error('Please select a category and product type for custom products');
return;
}
const submitData = { const submitData = {
productId: formData.productId ? parseInt(formData.productId) : null, productId: formData.productId ? parseInt(formData.productId) : null,
customName: formData.customName || null, customName: formData.customName || null,
@@ -413,6 +423,63 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
</p> </p>
</div> </div>
{/* Show additional fields when creating completely custom product */}
{!formData.productId && (
<>
<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={!formData.productId}
>
<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 })}
>
<option value="granular">Granular</option>
<option value="liquid">Liquid</option>
</select>
</div>
</div>
<div>
<label className="label">Brand</label>
<input
type="text"
className="input"
value={formData.brand}
onChange={(e) => setFormData({ ...formData, brand: e.target.value })}
placeholder="e.g., Scotts, Syngenta, Custom Mix"
/>
</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 25%, Nitrogen 24%, Iron 2%"
/>
</div>
</>
)}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="label">Application Rate</label> <label className="label">Application Rate</label>