seed updates

This commit is contained in:
Jake Kasper
2025-09-03 14:18:31 -04:00
parent 46e2ee3d7b
commit 41b18b8386

View File

@@ -218,34 +218,105 @@ const Products = () => {
</p>
)}
{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 (
<div className="mb-3">
<p className="text-sm font-medium text-gray-900">Seed Blend:</p>
{blend.length === 0 ? (
<p className="text-sm text-gray-600">No blend details</p>
) : (
<div className="mt-1 flex flex-wrap gap-2">
{blend.map((b, idx)=> (
<span key={idx} className="px-2 py-1 rounded bg-gray-100 text-gray-800 text-xs">
{b.cultivar} {parseFloat(b.percent || 0).toFixed(1)}%
</span>
))}
</div>
)}
</div>
);
})()
) : (
product.activeIngredients && (
<p className="text-sm text-gray-700 mb-3">
<strong>Active Ingredients:</strong> {product.activeIngredients}
</p>
)
)}
{/* Application Rates */}
{isUserProduct && product.customRateAmount && (
{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 (
<div className="bg-gray-50 p-3 rounded-lg mb-3">
<p className="text-sm font-medium text-gray-900 mb-1">Your Rates:</p>
{sr.new && <p className="text-sm text-gray-700">New Lawn: {sr.new} {sr.unit || product.customRateUnit}</p>}
{sr.overseed && <p className="text-sm text-gray-700">Overseeding: {sr.overseed} {sr.unit || product.customRateUnit}</p>}
</div>
);
}
} catch {}
}
if (product.customRateAmount) {
return (
<div className="bg-gray-50 p-3 rounded-lg mb-3">
<p className="text-sm font-medium text-gray-900">Your Rate:</p>
<p className="text-sm text-gray-700">
{product.customRateAmount} {product.customRateUnit}
</p>
<p className="text-sm text-gray-700">{product.customRateAmount} {product.customRateUnit}</p>
</div>
);
}
return null;
})()
)}
{!isUserProduct && product.rates && product.rates.length > 0 && (
<div className="bg-gray-50 p-3 rounded-lg mb-3">
<p className="text-sm font-medium text-gray-900 mb-2">Application Rates:</p>
{product.rates.slice(0, 2).map((rate, index) => (
{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 && (
<div className="text-sm text-gray-700"><strong>New Lawn:</strong> {newRate.rateAmount} {newRate.rateUnit}</div>
)}
{overRate && (
<div className="text-sm text-gray-700"><strong>Overseeding:</strong> {overRate.rateAmount} {overRate.rateUnit}</div>
)}
{!newRate && !overRate && product.rates.slice(0,2).map((rate, index)=> (
<div key={index} className="text-sm text-gray-700"><strong>{rate.applicationType}:</strong> {rate.rateAmount} {rate.rateUnit}</div>
))}
</>
);
})()
) : (
product.rates.slice(0, 2).map((rate, index) => (
<div key={index} className="text-sm text-gray-700">
<strong>{rate.applicationType}:</strong> {rate.rateAmount} {rate.rateUnit}
</div>
))}
))
)}
{product.rates.length > 2 && (
<p className="text-xs text-gray-500 mt-1">
+{product.rates.length - 2} more rates
</p>
<p className="text-xs text-gray-500 mt-1">+{product.rates.length - 2} more rates</p>
)}
</div>
)}
@@ -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 })
</div>
{formData.productType === 'seed' && (
<>
<SeedBlendEditor value={seedBlend} onChange={setSeedBlend} />
<div className="grid grid-cols-2 gap-3 mt-3">
<div>
<label className="label text-xs">New Lawn Seeding Rate</label>
<input type="number" step="0.01" className="input" value={seedNewRate} onChange={(e)=> setSeedNewRate(e.target.value)} placeholder="e.g., 7" />
</div>
<div>
<label className="label text-xs">Overseeding Rate</label>
<input type="number" step="0.01" className="input" value={seedOverRate} onChange={(e)=> setSeedOverRate(e.target.value)} placeholder="e.g., 3" />
</div>
</div>
</>
)}
<div>
@@ -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,6 +1399,21 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
</div>
</div>
{formData.productType === 'seed' ? (
<>
<SeedBlendEditor value={editSeedBlend} onChange={setEditSeedBlend} />
<div className="grid grid-cols-2 gap-3 mt-3">
<div>
<label className="label text-xs">New Lawn Seeding Rate</label>
<input type="number" step="0.01" className="input" value={editSeedNewRate} onChange={(e)=> setEditSeedNewRate(e.target.value)} placeholder="e.g., 7" />
</div>
<div>
<label className="label text-xs">Overseeding Rate</label>
<input type="number" step="0.01" className="input" value={editSeedOverRate} onChange={(e)=> setEditSeedOverRate(e.target.value)} placeholder="e.g., 3" />
</div>
</div>
</>
) : (
<div>
<label className="label">Active Ingredients</label>
<input
@@ -1288,6 +1424,7 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
placeholder="e.g., 2,4-D 38.3%, Dicamba 2.77%"
/>
</div>
)}
<div>
<label className="label">Description</label>