From 41b18b8386a491d8c513ee5f0b415163de2141ac Mon Sep 17 00:00:00 2001
From: Jake Kasper
Date: Wed, 3 Sep 2025 14:18:31 -0400
Subject: [PATCH] seed updates
---
frontend/src/pages/Products/Products.js | 205 ++++++++++++++++++++----
1 file changed, 171 insertions(+), 34 deletions(-)
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' && (
-
+ <>
+
+
+ >
)}
@@ -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' ? (
+ <>
+
+
+ >
+ ) : (
+
+
+ setFormData({ ...formData, activeIngredients: e.target.value })}
+ placeholder="e.g., 2,4-D 38.3%, Dicamba 2.77%"
+ />
+
+ )}