tank mix 1
This commit is contained in:
@@ -89,44 +89,59 @@ router.get('/plans', async (req, res, next) => {
|
|||||||
queryParams
|
queryParams
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get spreader settings for each plan
|
// Get spreader settings and product details for each plan
|
||||||
const plansWithSettings = await Promise.all(
|
const plansWithSettings = await Promise.all(
|
||||||
result.rows.map(async (plan) => {
|
result.rows.map(async (plan) => {
|
||||||
let spreaderSetting = null;
|
let spreaderSetting = null;
|
||||||
|
let productDetails = [];
|
||||||
|
|
||||||
|
// Get all products for this plan
|
||||||
|
const productsResult = await pool.query(
|
||||||
|
`SELECT app.*,
|
||||||
|
COALESCE(p.name, up.custom_name) as product_name,
|
||||||
|
COALESCE(p.brand, up.custom_brand) as product_brand,
|
||||||
|
COALESCE(p.product_type, up.custom_product_type) as product_type,
|
||||||
|
p.name as shared_name,
|
||||||
|
up.custom_name as user_name
|
||||||
|
FROM application_plan_products app
|
||||||
|
LEFT JOIN products p ON app.product_id = p.id
|
||||||
|
LEFT JOIN user_products up ON app.user_product_id = up.id
|
||||||
|
WHERE app.plan_id = $1`,
|
||||||
|
[plan.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
productDetails = productsResult.rows.map(product => ({
|
||||||
|
name: product.product_name,
|
||||||
|
brand: product.product_brand,
|
||||||
|
type: product.product_type,
|
||||||
|
rateAmount: parseFloat(product.rate_amount || 0),
|
||||||
|
rateUnit: product.rate_unit,
|
||||||
|
calculatedAmount: parseFloat(product.calculated_product_amount || 0),
|
||||||
|
isShared: !!product.shared_name
|
||||||
|
}));
|
||||||
|
|
||||||
// Only get spreader settings for granular applications with equipment
|
// Only get spreader settings for granular applications with equipment
|
||||||
if (plan.equipment_name) {
|
if (plan.equipment_name && productsResult.rows.length > 0) {
|
||||||
// Get the first product for this plan to determine if it's granular
|
const firstProduct = productsResult.rows[0];
|
||||||
const productResult = await pool.query(
|
const productType = firstProduct.shared_name ?
|
||||||
`SELECT app.product_id, app.user_product_id, p.product_type as shared_type, up.custom_product_type as user_type
|
(await pool.query('SELECT product_type FROM products WHERE id = $1', [firstProduct.product_id])).rows[0]?.product_type :
|
||||||
FROM application_plan_products app
|
firstProduct.custom_product_type;
|
||||||
LEFT JOIN products p ON app.product_id = p.id
|
|
||||||
LEFT JOIN user_products up ON app.user_product_id = up.id
|
|
||||||
WHERE app.plan_id = $1
|
|
||||||
LIMIT 1`,
|
|
||||||
[plan.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (productResult.rows.length > 0) {
|
if (productType === 'granular') {
|
||||||
const product = productResult.rows[0];
|
// Get equipment ID
|
||||||
const productType = product.shared_type || product.user_type;
|
const equipmentResult = await pool.query(
|
||||||
|
'SELECT id FROM user_equipment WHERE custom_name = $1 AND user_id = $2',
|
||||||
|
[plan.equipment_name, req.user.id]
|
||||||
|
);
|
||||||
|
|
||||||
if (productType === 'granular') {
|
if (equipmentResult.rows.length > 0) {
|
||||||
// Get equipment ID
|
const equipmentId = equipmentResult.rows[0].id;
|
||||||
const equipmentResult = await pool.query(
|
spreaderSetting = await getSpreaderSettingsForEquipment(
|
||||||
'SELECT id FROM user_equipment WHERE custom_name = $1 AND user_id = $2',
|
equipmentId,
|
||||||
[plan.equipment_name, req.user.id]
|
firstProduct.product_id,
|
||||||
|
firstProduct.user_product_id,
|
||||||
|
req.user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (equipmentResult.rows.length > 0) {
|
|
||||||
const equipmentId = equipmentResult.rows[0].id;
|
|
||||||
spreaderSetting = await getSpreaderSettingsForEquipment(
|
|
||||||
equipmentId,
|
|
||||||
product.product_id,
|
|
||||||
product.user_product_id,
|
|
||||||
req.user.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +161,7 @@ router.get('/plans', async (req, res, next) => {
|
|||||||
totalWaterAmount: parseFloat(plan.total_water_amount || 0),
|
totalWaterAmount: parseFloat(plan.total_water_amount || 0),
|
||||||
avgSpeedMph: parseFloat(plan.avg_speed_mph || 0),
|
avgSpeedMph: parseFloat(plan.avg_speed_mph || 0),
|
||||||
spreaderSetting: spreaderSetting?.setting_value || null,
|
spreaderSetting: spreaderSetting?.setting_value || null,
|
||||||
|
productDetails: productDetails,
|
||||||
createdAt: plan.created_at,
|
createdAt: plan.created_at,
|
||||||
updatedAt: plan.updated_at
|
updatedAt: plan.updated_at
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -301,18 +301,38 @@ const Applications = () => {
|
|||||||
Products: {application.productCount}
|
Products: {application.productCount}
|
||||||
</p>
|
</p>
|
||||||
{/* Display calculated amounts */}
|
{/* Display calculated amounts */}
|
||||||
{application.totalProductAmount > 0 && (
|
{(application.totalProductAmount > 0 || (application.productDetails && application.productDetails.length > 0)) && (
|
||||||
<div className="text-sm text-green-600 mt-2 space-y-1">
|
<div className="text-sm text-green-600 mt-2 space-y-1">
|
||||||
<p className="font-medium">Calculated Requirements:</p>
|
<p className="font-medium">Calculated Requirements:</p>
|
||||||
<p>• Product: {application.totalProductAmount.toFixed(2)} {application.totalWaterAmount > 0 ? 'oz' : 'lbs'}</p>
|
|
||||||
{application.totalWaterAmount > 0 && (
|
{/* Show individual products for liquid tank mix */}
|
||||||
<p>• Water: {application.totalWaterAmount.toFixed(2)} gallons</p>
|
{application.productDetails && application.productDetails.length > 1 ? (
|
||||||
)}
|
<>
|
||||||
{application.avgSpeedMph > 0 && (
|
{application.productDetails.map((product, index) => (
|
||||||
<p>• Target Speed: {application.avgSpeedMph.toFixed(1)} mph</p>
|
<p key={index}>
|
||||||
)}
|
• {product.name}{product.brand ? ` (${product.brand})` : ''}: {product.calculatedAmount.toFixed(2)} oz
|
||||||
{application.spreaderSetting && (
|
</p>
|
||||||
<p>• Spreader Setting: {application.spreaderSetting}</p>
|
))}
|
||||||
|
{application.totalWaterAmount > 0 && (
|
||||||
|
<p>• Water: {application.totalWaterAmount.toFixed(2)} gallons</p>
|
||||||
|
)}
|
||||||
|
{application.avgSpeedMph > 0 && (
|
||||||
|
<p>• Target Speed: {application.avgSpeedMph.toFixed(1)} mph</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>• Product: {application.totalProductAmount.toFixed(2)} {application.totalWaterAmount > 0 ? 'oz' : 'lbs'}</p>
|
||||||
|
{application.totalWaterAmount > 0 && (
|
||||||
|
<p>• Water: {application.totalWaterAmount.toFixed(2)} gallons</p>
|
||||||
|
)}
|
||||||
|
{application.avgSpeedMph > 0 && (
|
||||||
|
<p>• Target Speed: {application.avgSpeedMph.toFixed(1)} mph</p>
|
||||||
|
)}
|
||||||
|
{application.spreaderSetting && (
|
||||||
|
<p>• Spreader Setting: {application.spreaderSetting}</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -398,15 +418,25 @@ const Applications = () => {
|
|||||||
sprayAngle: selectedNozzle.sprayAngle
|
sprayAngle: selectedNozzle.sprayAngle
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
products: [{
|
products: planData.applicationType === 'liquid'
|
||||||
...(planData.selectedProduct?.isShared
|
? planData.selectedProducts.map(item => ({
|
||||||
? { productId: parseInt(planData.selectedProduct.id) }
|
...(item.product?.isShared
|
||||||
: { userProductId: parseInt(planData.selectedProduct.id) }
|
? { productId: parseInt(item.product.id) }
|
||||||
),
|
: { userProductId: parseInt(item.product.id) }
|
||||||
rateAmount: parseFloat(planData.selectedProduct?.customRateAmount || planData.selectedProduct?.rateAmount || 1),
|
),
|
||||||
rateUnit: planData.selectedProduct?.customRateUnit || planData.selectedProduct?.rateUnit || 'per 1000sqft',
|
rateAmount: parseFloat(item.rateAmount || 1),
|
||||||
applicationType: planData.applicationType
|
rateUnit: item.rateUnit || 'oz/1000 sq ft',
|
||||||
}]
|
applicationType: planData.applicationType
|
||||||
|
}))
|
||||||
|
: [{
|
||||||
|
...(planData.selectedProduct?.isShared
|
||||||
|
? { productId: parseInt(planData.selectedProduct.id) }
|
||||||
|
: { userProductId: parseInt(planData.selectedProduct.id) }
|
||||||
|
),
|
||||||
|
rateAmount: parseFloat(planData.selectedProduct?.customRateAmount || planData.selectedProduct?.rateAmount || 1),
|
||||||
|
rateUnit: planData.selectedProduct?.customRateUnit || planData.selectedProduct?.rateUnit || 'per 1000sqft',
|
||||||
|
applicationType: planData.applicationType
|
||||||
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
await applicationsAPI.updatePlan(editingPlan.id, planPayload);
|
await applicationsAPI.updatePlan(editingPlan.id, planPayload);
|
||||||
@@ -442,15 +472,25 @@ const Applications = () => {
|
|||||||
sprayAngle: selectedNozzle.sprayAngle
|
sprayAngle: selectedNozzle.sprayAngle
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
products: [{
|
products: planData.applicationType === 'liquid'
|
||||||
...(planData.selectedProduct?.isShared
|
? planData.selectedProducts.map(item => ({
|
||||||
? { productId: parseInt(planData.selectedProduct.id) }
|
...(item.product?.isShared
|
||||||
: { userProductId: parseInt(planData.selectedProduct.id) }
|
? { productId: parseInt(item.product.id) }
|
||||||
),
|
: { userProductId: parseInt(item.product.id) }
|
||||||
rateAmount: parseFloat(planData.selectedProduct?.customRateAmount || planData.selectedProduct?.rateAmount || 1),
|
),
|
||||||
rateUnit: planData.selectedProduct?.customRateUnit || planData.selectedProduct?.rateUnit || 'per 1000sqft',
|
rateAmount: parseFloat(item.rateAmount || 1),
|
||||||
applicationType: planData.applicationType
|
rateUnit: item.rateUnit || 'oz/1000 sq ft',
|
||||||
}]
|
applicationType: planData.applicationType
|
||||||
|
}))
|
||||||
|
: [{
|
||||||
|
...(planData.selectedProduct?.isShared
|
||||||
|
? { productId: parseInt(planData.selectedProduct.id) }
|
||||||
|
: { userProductId: parseInt(planData.selectedProduct.id) }
|
||||||
|
),
|
||||||
|
rateAmount: parseFloat(planData.selectedProduct?.customRateAmount || planData.selectedProduct?.rateAmount || 1),
|
||||||
|
rateUnit: planData.selectedProduct?.customRateUnit || planData.selectedProduct?.rateUnit || 'per 1000sqft',
|
||||||
|
applicationType: planData.applicationType
|
||||||
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
return applicationsAPI.createPlan(planPayload);
|
return applicationsAPI.createPlan(planPayload);
|
||||||
@@ -493,6 +533,7 @@ const ApplicationPlanModal = ({
|
|||||||
selectedAreas: [],
|
selectedAreas: [],
|
||||||
productId: '',
|
productId: '',
|
||||||
selectedProduct: null,
|
selectedProduct: null,
|
||||||
|
selectedProducts: [], // For liquid tank mixing - array of {product, rate}
|
||||||
applicationType: '', // 'liquid' or 'granular'
|
applicationType: '', // 'liquid' or 'granular'
|
||||||
equipmentId: '',
|
equipmentId: '',
|
||||||
nozzleId: '',
|
nozzleId: '',
|
||||||
@@ -564,11 +605,30 @@ const ApplicationPlanModal = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!planData.productId) {
|
if (!planData.applicationType) {
|
||||||
toast.error('Please select a product');
|
toast.error('Please select an application type');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate product selection based on application type
|
||||||
|
if (planData.applicationType === 'granular') {
|
||||||
|
if (!planData.productId) {
|
||||||
|
toast.error('Please select a product');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (planData.applicationType === 'liquid') {
|
||||||
|
if (planData.selectedProducts.length === 0) {
|
||||||
|
toast.error('Please select at least one product for tank mixing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Validate that all selected products have rates
|
||||||
|
const missingRates = planData.selectedProducts.filter(p => !p.rateAmount || p.rateAmount <= 0);
|
||||||
|
if (missingRates.length > 0) {
|
||||||
|
toast.error('Please enter application rates for all selected products');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!planData.equipmentId) {
|
if (!planData.equipmentId) {
|
||||||
toast.error('Please select equipment');
|
toast.error('Please select equipment');
|
||||||
return;
|
return;
|
||||||
@@ -686,58 +746,192 @@ const ApplicationPlanModal = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Product Selection */}
|
{/* Application Type Selection */}
|
||||||
<div>
|
<div>
|
||||||
<label className="label flex items-center gap-2">
|
<label className="label flex items-center gap-2">
|
||||||
<BeakerIcon className="h-5 w-5" />
|
<BeakerIcon className="h-5 w-5" />
|
||||||
Product *
|
Application Type *
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
className="input"
|
className="input"
|
||||||
value={planData.productId}
|
value={planData.applicationType}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const selectedProduct = products.find(p => p.uniqueId === e.target.value);
|
|
||||||
console.log('Selected product:', selectedProduct);
|
|
||||||
|
|
||||||
// Determine application type from product type
|
|
||||||
let applicationType = '';
|
|
||||||
if (selectedProduct) {
|
|
||||||
const productType = selectedProduct.productType || selectedProduct.customProductType;
|
|
||||||
// Map product types to application types
|
|
||||||
if (productType && (productType.toLowerCase().includes('liquid') || productType.toLowerCase().includes('concentrate'))) {
|
|
||||||
applicationType = 'liquid';
|
|
||||||
} else if (productType && (productType.toLowerCase().includes('granular') || productType.toLowerCase().includes('granule'))) {
|
|
||||||
applicationType = 'granular';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlanData({
|
setPlanData({
|
||||||
...planData,
|
...planData,
|
||||||
productId: e.target.value,
|
applicationType: e.target.value,
|
||||||
selectedProduct: selectedProduct,
|
productId: '',
|
||||||
applicationType: applicationType
|
selectedProduct: null,
|
||||||
|
selectedProducts: []
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<option value="">Select a product...</option>
|
<option value="">Select application type...</option>
|
||||||
{products.map((product) => {
|
<option value="liquid">Liquid (Tank Mix)</option>
|
||||||
const displayName = product.customName || product.name;
|
<option value="granular">Granular</option>
|
||||||
const productType = product.productType || product.customProductType;
|
|
||||||
const brand = product.brand || product.customBrand;
|
|
||||||
const rateInfo = product.customRateAmount && product.customRateUnit
|
|
||||||
? ` (${product.customRateAmount} ${product.customRateUnit})`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<option key={product.uniqueId} value={product.uniqueId}>
|
|
||||||
{displayName}{brand ? ` - ${brand}` : ''}{productType ? ` (${productType})` : ''}{rateInfo}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Product Selection - Single for Granular */}
|
||||||
|
{planData.applicationType === 'granular' && (
|
||||||
|
<div>
|
||||||
|
<label className="label flex items-center gap-2">
|
||||||
|
<BeakerIcon className="h-5 w-5" />
|
||||||
|
Product *
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
className="input"
|
||||||
|
value={planData.productId}
|
||||||
|
onChange={(e) => {
|
||||||
|
const selectedProduct = products.find(p => p.uniqueId === e.target.value);
|
||||||
|
setPlanData({
|
||||||
|
...planData,
|
||||||
|
productId: e.target.value,
|
||||||
|
selectedProduct: selectedProduct
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">Select a product...</option>
|
||||||
|
{products.filter(product => {
|
||||||
|
const productType = product.productType || product.customProductType;
|
||||||
|
return productType && (productType.toLowerCase().includes('granular') || productType.toLowerCase().includes('granule'));
|
||||||
|
}).map((product) => {
|
||||||
|
const displayName = product.customName || product.name;
|
||||||
|
const brand = product.brand || product.customBrand;
|
||||||
|
const rateInfo = product.customRateAmount && product.customRateUnit
|
||||||
|
? ` (${product.customRateAmount} ${product.customRateUnit})`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<option key={product.uniqueId} value={product.uniqueId}>
|
||||||
|
{displayName}{brand ? ` - ${brand}` : ''}{rateInfo}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Product Selection - Multiple for Liquid Tank Mix */}
|
||||||
|
{planData.applicationType === 'liquid' && (
|
||||||
|
<div>
|
||||||
|
<label className="label flex items-center gap-2">
|
||||||
|
<BeakerIcon className="h-5 w-5" />
|
||||||
|
Tank Mix Products *
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{/* Selected Products List */}
|
||||||
|
{planData.selectedProducts.length > 0 && (
|
||||||
|
<div className="space-y-2 mb-3">
|
||||||
|
{planData.selectedProducts.map((item, index) => (
|
||||||
|
<div key={index} className="flex items-center gap-2 p-2 bg-blue-50 border border-blue-200 rounded">
|
||||||
|
<div className="flex-1">
|
||||||
|
<span className="font-medium">{item.product.customName || item.product.name}</span>
|
||||||
|
{item.product.brand || item.product.customBrand ? (
|
||||||
|
<span className="text-gray-600"> - {item.product.brand || item.product.customBrand}</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
value={item.rateAmount || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newProducts = [...planData.selectedProducts];
|
||||||
|
newProducts[index] = {
|
||||||
|
...item,
|
||||||
|
rateAmount: parseFloat(e.target.value) || 0
|
||||||
|
};
|
||||||
|
setPlanData({ ...planData, selectedProducts: newProducts });
|
||||||
|
}}
|
||||||
|
className="w-20 px-2 py-1 text-sm border rounded"
|
||||||
|
placeholder="Rate"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
value={item.rateUnit || 'oz/1000 sq ft'}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newProducts = [...planData.selectedProducts];
|
||||||
|
newProducts[index] = {
|
||||||
|
...item,
|
||||||
|
rateUnit: e.target.value
|
||||||
|
};
|
||||||
|
setPlanData({ ...planData, selectedProducts: newProducts });
|
||||||
|
}}
|
||||||
|
className="text-sm border rounded px-1 py-1"
|
||||||
|
>
|
||||||
|
<option value="oz/1000 sq ft">oz/1000 sq ft</option>
|
||||||
|
<option value="oz/acre">oz/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>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const newProducts = planData.selectedProducts.filter((_, i) => i !== index);
|
||||||
|
setPlanData({ ...planData, selectedProducts: newProducts });
|
||||||
|
}}
|
||||||
|
className="text-red-600 hover:text-red-800 p-1"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Add Product Dropdown */}
|
||||||
|
<select
|
||||||
|
className="input"
|
||||||
|
value=""
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value) {
|
||||||
|
const selectedProduct = products.find(p => p.uniqueId === e.target.value);
|
||||||
|
if (selectedProduct && !planData.selectedProducts.some(p => p.product.uniqueId === selectedProduct.uniqueId)) {
|
||||||
|
const newProduct = {
|
||||||
|
product: selectedProduct,
|
||||||
|
rateAmount: selectedProduct.customRateAmount || 1,
|
||||||
|
rateUnit: selectedProduct.customRateUnit || 'oz/1000 sq ft'
|
||||||
|
};
|
||||||
|
setPlanData({
|
||||||
|
...planData,
|
||||||
|
selectedProducts: [...planData.selectedProducts, newProduct]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="">Add a product to tank mix...</option>
|
||||||
|
{products.filter(product => {
|
||||||
|
const productType = product.productType || product.customProductType;
|
||||||
|
const isLiquid = productType && (productType.toLowerCase().includes('liquid') || productType.toLowerCase().includes('concentrate'));
|
||||||
|
const notAlreadySelected = !planData.selectedProducts.some(p => p.product.uniqueId === product.uniqueId);
|
||||||
|
return isLiquid && notAlreadySelected;
|
||||||
|
}).map((product) => {
|
||||||
|
const displayName = product.customName || product.name;
|
||||||
|
const brand = product.brand || product.customBrand;
|
||||||
|
const rateInfo = product.customRateAmount && product.customRateUnit
|
||||||
|
? ` (${product.customRateAmount} ${product.customRateUnit})`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<option key={product.uniqueId} value={product.uniqueId}>
|
||||||
|
{displayName}{brand ? ` - ${brand}` : ''}{rateInfo}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
{planData.selectedProducts.length === 0 && (
|
||||||
|
<p className="text-sm text-gray-600 mt-1">
|
||||||
|
Select liquid products to mix in the tank. You can add herbicides, surfactants, and other liquid products.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Equipment Selection */}
|
{/* Equipment Selection */}
|
||||||
{planData.applicationType && (
|
{planData.applicationType && (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user