diff --git a/frontend/src/pages/Products/Products.js b/frontend/src/pages/Products/Products.js index 3651a90..b47a2f5 100644 --- a/frontend/src/pages/Products/Products.js +++ b/frontend/src/pages/Products/Products.js @@ -218,34 +218,105 @@ const Products = () => {

)} - {product.activeIngredients && ( -

- Active Ingredients: {product.activeIngredients} -

+ {/* Seed blend pretty display */} + {product.productType === 'seed' ? ( + (()=>{ + let blend = []; + // Shared product + if (Array.isArray(product.seedBlend) && product.seedBlend.length) { + blend = product.seedBlend; + } else if (product.activeIngredients) { + // User product stores blend in activeIngredients JSON + try { + const ai = typeof product.activeIngredients === 'string' ? JSON.parse(product.activeIngredients) : product.activeIngredients; + blend = ai?.seedBlend || []; + } catch { /* ignore */ } + } + return ( +
+

Seed Blend:

+ {blend.length === 0 ? ( +

No blend details

+ ) : ( +
+ {blend.map((b, idx)=> ( + + {b.cultivar} — {parseFloat(b.percent || 0).toFixed(1)}% + + ))} +
+ )} +
+ ); + })() + ) : ( + product.activeIngredients && ( +

+ Active Ingredients: {product.activeIngredients} +

+ ) )} {/* Application Rates */} - {isUserProduct && product.customRateAmount && ( -
-

Your Rate:

-

- {product.customRateAmount} {product.customRateUnit} -

-
+ {isUserProduct && ( + (()=>{ + if (product.productType === 'seed') { + try { + const ai = typeof product.activeIngredients === 'string' ? JSON.parse(product.activeIngredients) : product.activeIngredients; + const sr = ai?.seedRates; + if (sr?.new || sr?.overseed) { + return ( +
+

Your Rates:

+ {sr.new &&

New Lawn: {sr.new} {sr.unit || product.customRateUnit}

} + {sr.overseed &&

Overseeding: {sr.overseed} {sr.unit || product.customRateUnit}

} +
+ ); + } + } catch {} + } + if (product.customRateAmount) { + return ( +
+

Your Rate:

+

{product.customRateAmount} {product.customRateUnit}

+
+ ); + } + return null; + })() )} {!isUserProduct && product.rates && product.rates.length > 0 && (

Application Rates:

- {product.rates.slice(0, 2).map((rate, index) => ( -
- {rate.applicationType}: {rate.rateAmount} {rate.rateUnit} -
- ))} + {product.productType === 'seed' ? ( + (()=>{ + const newRate = product.rates.find(r=> (r.applicationType||'').toLowerCase().includes('new')); + const overRate = product.rates.find(r=> (r.applicationType||'').toLowerCase().includes('over')); + return ( + <> + {newRate && ( +
New Lawn: {newRate.rateAmount} {newRate.rateUnit}
+ )} + {overRate && ( +
Overseeding: {overRate.rateAmount} {overRate.rateUnit}
+ )} + {!newRate && !overRate && product.rates.slice(0,2).map((rate, index)=> ( +
{rate.applicationType}: {rate.rateAmount} {rate.rateUnit}
+ ))} + + ); + })() + ) : ( + product.rates.slice(0, 2).map((rate, index) => ( +
+ {rate.applicationType}: {rate.rateAmount} {rate.rateUnit} +
+ )) + )} {product.rates.length > 2 && ( -

- +{product.rates.length - 2} more rates -

+

+{product.rates.length - 2} more rates

)}
)} @@ -464,6 +535,8 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories }) const [spreaderSettings, setSpreaderSettings] = useState([]); const [seedBlend, setSeedBlend] = useState([]); // [{cultivar:'', percent:0}] + const [seedNewRate, setSeedNewRate] = useState(''); + const [seedOverRate, setSeedOverRate] = useState(''); const [availableSpreaders, setAvailableSpreaders] = useState([]); const [loadingSpreaders, setLoadingSpreaders] = useState(false); const [newSpreaderSetting, setNewSpreaderSetting] = useState({ @@ -549,9 +622,17 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories }) spreaderSettings: formData.productType === 'granular' ? spreaderSettings : [] }; - // If this is a seed product and user entered a blend, pack it into activeIngredients as JSON - if (formData.productType === 'seed' && Array.isArray(seedBlend) && seedBlend.length > 0) { - submitData.activeIngredients = JSON.stringify({ seedBlend }); + // If seed, include blend and optional seed rates in activeIngredients JSON + if (formData.productType === 'seed') { + const payload = { seedBlend: Array.isArray(seedBlend) ? seedBlend : [] }; + if (seedNewRate || seedOverRate) { + payload.seedRates = { + new: seedNewRate ? parseFloat(seedNewRate) : null, + overseed: seedOverRate ? parseFloat(seedOverRate) : null, + unit: formData.customRateUnit + }; + } + submitData.activeIngredients = JSON.stringify(payload); } onSubmit(submitData); @@ -632,7 +713,19 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories }) {formData.productType === 'seed' && ( - + <> + +
+
+ + setSeedNewRate(e.target.value)} placeholder="e.g., 7" /> +
+
+ + setSeedOverRate(e.target.value)} placeholder="e.g., 3" /> +
+
+ )}
@@ -840,6 +933,9 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor activeIngredients: product.activeIngredients || '', description: product.description || '' }); + const [editSeedBlend, setEditSeedBlend] = useState([]); + const [editSeedNewRate, setEditSeedNewRate] = useState(''); + const [editSeedOverRate, setEditSeedOverRate] = useState(''); const [editMode, setEditMode] = useState('basic'); // 'basic' or 'advanced' const [editSpreaderSettings, setEditSpreaderSettings] = useState([]); @@ -903,6 +999,24 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor loadSpreaderSettings(); }, [product.id, product.productType]); + // Parse seed data from activeIngredients when opening + useEffect(() => { + if (product.productType === 'seed') { + try { + const ai = typeof product.activeIngredients === 'string' ? JSON.parse(product.activeIngredients) : product.activeIngredients; + if (ai?.seedBlend) setEditSeedBlend(ai.seedBlend); + if (ai?.seedRates) { + setEditSeedNewRate(ai.seedRates.new || ''); + setEditSeedOverRate(ai.seedRates.overseed || ''); + if (ai.seedRates.unit && !formData.customRateUnit) { + setFormData(prev => ({ ...prev, customRateUnit: ai.seedRates.unit })); + } + } + } catch {} + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleSubmit = (e) => { e.preventDefault(); @@ -947,7 +1061,14 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor brand: formData.brand || null, categoryId: formData.categoryId ? parseInt(formData.categoryId) : null, productType: formData.productType || null, - activeIngredients: formData.activeIngredients || null, + activeIngredients: formData.productType === 'seed' ? JSON.stringify({ + seedBlend: editSeedBlend, + seedRates: (editSeedNewRate || editSeedOverRate) ? { + new: editSeedNewRate ? parseFloat(editSeedNewRate) : null, + overseed: editSeedOverRate ? parseFloat(editSeedOverRate) : null, + unit: formData.customRateUnit + } : undefined + }) : (formData.activeIngredients || null), description: formData.description || null, isAdvancedEdit: true, // Flag to indicate this is an advanced edit // Include spreader settings for granular products @@ -1278,16 +1399,32 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
-
- - setFormData({ ...formData, activeIngredients: e.target.value })} - placeholder="e.g., 2,4-D 38.3%, Dicamba 2.77%" - /> -
+ {formData.productType === 'seed' ? ( + <> + +
+
+ + setEditSeedNewRate(e.target.value)} placeholder="e.g., 7" /> +
+
+ + setEditSeedOverRate(e.target.value)} placeholder="e.g., 3" /> +
+
+ + ) : ( +
+ + setFormData({ ...formData, activeIngredients: e.target.value })} + placeholder="e.g., 2,4-D 38.3%, Dicamba 2.77%" + /> +
+ )}