tank mix 1
This commit is contained in:
@@ -301,18 +301,38 @@ const Applications = () => {
|
||||
Products: {application.productCount}
|
||||
</p>
|
||||
{/* 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">
|
||||
<p className="font-medium">Calculated Requirements:</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>
|
||||
|
||||
{/* Show individual products for liquid tank mix */}
|
||||
{application.productDetails && application.productDetails.length > 1 ? (
|
||||
<>
|
||||
{application.productDetails.map((product, index) => (
|
||||
<p key={index}>
|
||||
• {product.name}{product.brand ? ` (${product.brand})` : ''}: {product.calculatedAmount.toFixed(2)} oz
|
||||
</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>
|
||||
)}
|
||||
@@ -398,15 +418,25 @@ const Applications = () => {
|
||||
sprayAngle: selectedNozzle.sprayAngle
|
||||
}
|
||||
}),
|
||||
products: [{
|
||||
...(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
|
||||
}]
|
||||
products: planData.applicationType === 'liquid'
|
||||
? planData.selectedProducts.map(item => ({
|
||||
...(item.product?.isShared
|
||||
? { productId: parseInt(item.product.id) }
|
||||
: { userProductId: parseInt(item.product.id) }
|
||||
),
|
||||
rateAmount: parseFloat(item.rateAmount || 1),
|
||||
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);
|
||||
@@ -442,15 +472,25 @@ const Applications = () => {
|
||||
sprayAngle: selectedNozzle.sprayAngle
|
||||
}
|
||||
}),
|
||||
products: [{
|
||||
...(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
|
||||
}]
|
||||
products: planData.applicationType === 'liquid'
|
||||
? planData.selectedProducts.map(item => ({
|
||||
...(item.product?.isShared
|
||||
? { productId: parseInt(item.product.id) }
|
||||
: { userProductId: parseInt(item.product.id) }
|
||||
),
|
||||
rateAmount: parseFloat(item.rateAmount || 1),
|
||||
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);
|
||||
@@ -493,6 +533,7 @@ const ApplicationPlanModal = ({
|
||||
selectedAreas: [],
|
||||
productId: '',
|
||||
selectedProduct: null,
|
||||
selectedProducts: [], // For liquid tank mixing - array of {product, rate}
|
||||
applicationType: '', // 'liquid' or 'granular'
|
||||
equipmentId: '',
|
||||
nozzleId: '',
|
||||
@@ -564,11 +605,30 @@ const ApplicationPlanModal = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!planData.productId) {
|
||||
toast.error('Please select a product');
|
||||
if (!planData.applicationType) {
|
||||
toast.error('Please select an application type');
|
||||
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) {
|
||||
toast.error('Please select equipment');
|
||||
return;
|
||||
@@ -686,58 +746,192 @@ const ApplicationPlanModal = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Product Selection */}
|
||||
{/* Application Type Selection */}
|
||||
<div>
|
||||
<label className="label flex items-center gap-2">
|
||||
<BeakerIcon className="h-5 w-5" />
|
||||
Product *
|
||||
Application Type *
|
||||
</label>
|
||||
<select
|
||||
className="input"
|
||||
value={planData.productId}
|
||||
value={planData.applicationType}
|
||||
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({
|
||||
...planData,
|
||||
productId: e.target.value,
|
||||
selectedProduct: selectedProduct,
|
||||
applicationType: applicationType
|
||||
applicationType: e.target.value,
|
||||
productId: '',
|
||||
selectedProduct: null,
|
||||
selectedProducts: []
|
||||
});
|
||||
}}
|
||||
required
|
||||
>
|
||||
<option value="">Select a product...</option>
|
||||
{products.map((product) => {
|
||||
const displayName = product.customName || product.name;
|
||||
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>
|
||||
);
|
||||
})}
|
||||
<option value="">Select application type...</option>
|
||||
<option value="liquid">Liquid (Tank Mix)</option>
|
||||
<option value="granular">Granular</option>
|
||||
</select>
|
||||
</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 */}
|
||||
{planData.applicationType && (
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user