asdfa
This commit is contained in:
@@ -23,6 +23,9 @@ const ApplicationPlanModal = ({
|
|||||||
const [plannedDate, setPlannedDate] = useState('');
|
const [plannedDate, setPlannedDate] = useState('');
|
||||||
const [notes, setNotes] = useState('');
|
const [notes, setNotes] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [spreaderSettings, setSpreaderSettings] = useState({});
|
||||||
|
const [showSpreaderSettingsForm, setShowSpreaderSettingsForm] = useState(false);
|
||||||
|
const [currentProductForSettings, setCurrentProductForSettings] = useState(null);
|
||||||
|
|
||||||
// Calculate map center from property or sections
|
// Calculate map center from property or sections
|
||||||
const mapCenter = React.useMemo(() => {
|
const mapCenter = React.useMemo(() => {
|
||||||
@@ -223,6 +226,93 @@ const ApplicationPlanModal = ({
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check spreader settings for granular products
|
||||||
|
const checkSpreaderSettings = async (product, equipmentId) => {
|
||||||
|
try {
|
||||||
|
const productApiId = product.isShared ? product.id : product.id;
|
||||||
|
const endpoint = product.isShared
|
||||||
|
? `/api/product-spreader-settings/product/${productApiId}`
|
||||||
|
: `/api/product-spreader-settings/user-product/${productApiId}`;
|
||||||
|
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('authToken')}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const settings = data.data?.settings || [];
|
||||||
|
const equipmentSetting = settings.find(s => s.equipmentId === parseInt(equipmentId));
|
||||||
|
|
||||||
|
if (!equipmentSetting) {
|
||||||
|
// No spreader setting found, prompt user to add one
|
||||||
|
setCurrentProductForSettings(product);
|
||||||
|
setShowSpreaderSettingsForm(true);
|
||||||
|
} else {
|
||||||
|
// Store the spreader setting for calculations
|
||||||
|
setSpreaderSettings(prev => ({
|
||||||
|
...prev,
|
||||||
|
[`${product.id}_${equipmentId}`]: equipmentSetting
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check spreader settings:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate application amounts
|
||||||
|
const calculateApplicationAmounts = () => {
|
||||||
|
if (!selectedPropertyDetails?.sections || selectedAreas.length === 0) return null;
|
||||||
|
|
||||||
|
const totalArea = selectedAreas.reduce((total, areaId) => {
|
||||||
|
const area = selectedPropertyDetails.sections.find(s => s.id === areaId);
|
||||||
|
return total + (area?.area || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const calculations = selectedProducts.map(product => {
|
||||||
|
const rateAmount = parseFloat(product.rateAmount) || 0;
|
||||||
|
const areaIn1000s = totalArea / 1000;
|
||||||
|
|
||||||
|
if (product.productType === 'granular') {
|
||||||
|
// Granular calculations
|
||||||
|
const totalProductNeeded = rateAmount * areaIn1000s;
|
||||||
|
return {
|
||||||
|
productName: product.productName,
|
||||||
|
totalProductNeeded: totalProductNeeded.toFixed(2),
|
||||||
|
unit: product.rateUnit,
|
||||||
|
waterNeeded: 0 // No water for granular
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Liquid calculations
|
||||||
|
const totalProductNeeded = rateAmount * areaIn1000s;
|
||||||
|
|
||||||
|
// Calculate water needed based on nozzle and equipment settings
|
||||||
|
let waterNeeded = 0;
|
||||||
|
if (selectedNozzleId) {
|
||||||
|
const selectedNozzle = nozzles.find(n => n.id === parseInt(selectedNozzleId));
|
||||||
|
if (selectedNozzle && selectedNozzle.flowRateGpm) {
|
||||||
|
// Basic calculation - this would need equipment speed settings for accuracy
|
||||||
|
waterNeeded = (selectedNozzle.flowRateGpm * 60 * areaIn1000s) / 1000; // Rough estimate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
productName: product.productName,
|
||||||
|
totalProductNeeded: totalProductNeeded.toFixed(2),
|
||||||
|
unit: product.rateUnit,
|
||||||
|
waterNeeded: waterNeeded.toFixed(1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalArea: totalArea.toLocaleString(),
|
||||||
|
calculations
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -397,15 +487,15 @@ const ApplicationPlanModal = ({
|
|||||||
if (!applicationType) return true;
|
if (!applicationType) return true;
|
||||||
// Filter by application type - granular needs Spreader, liquid needs Sprayer
|
// Filter by application type - granular needs Spreader, liquid needs Sprayer
|
||||||
if (applicationType === 'granular') {
|
if (applicationType === 'granular') {
|
||||||
return eq.category_name === 'Spreader';
|
return eq.categoryName === 'Spreader';
|
||||||
} else if (applicationType === 'liquid') {
|
} else if (applicationType === 'liquid') {
|
||||||
return eq.category_name === 'Sprayer';
|
return eq.categoryName === 'Sprayer';
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map(eq => (
|
.map(eq => (
|
||||||
<option key={eq.id} value={eq.id}>
|
<option key={eq.id} value={eq.id}>
|
||||||
{eq.custom_name} - {eq.type_name}
|
{eq.customName} - {eq.typeName}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -427,9 +517,9 @@ const ApplicationPlanModal = ({
|
|||||||
<option value="">Select nozzle</option>
|
<option value="">Select nozzle</option>
|
||||||
{nozzles.map(nozzle => (
|
{nozzles.map(nozzle => (
|
||||||
<option key={nozzle.id} value={nozzle.id}>
|
<option key={nozzle.id} value={nozzle.id}>
|
||||||
{nozzle.custom_name} - {nozzle.manufacturer}
|
{nozzle.customName} - {nozzle.manufacturer}
|
||||||
{nozzle.flow_rate_gpm && ` (${nozzle.flow_rate_gpm} GPM)`}
|
{nozzle.flowRateGpm && ` (${nozzle.flowRateGpm} GPM)`}
|
||||||
{nozzle.orifice_size && ` - ${nozzle.orifice_size}`}
|
{nozzle.orificeSize && ` - ${nozzle.orificeSize}`}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -468,25 +558,27 @@ const ApplicationPlanModal = ({
|
|||||||
updateProduct(index, 'productId', selectedProduct.isShared ? selectedProduct.id : null);
|
updateProduct(index, 'productId', selectedProduct.isShared ? selectedProduct.id : null);
|
||||||
updateProduct(index, 'userProductId', !selectedProduct.isShared ? selectedProduct.id : null);
|
updateProduct(index, 'userProductId', !selectedProduct.isShared ? selectedProduct.id : null);
|
||||||
updateProduct(index, 'isUserProduct', !selectedProduct.isShared);
|
updateProduct(index, 'isUserProduct', !selectedProduct.isShared);
|
||||||
updateProduct(index, 'productName', selectedProduct.name || selectedProduct.product_name);
|
updateProduct(index, 'productName', selectedProduct.name);
|
||||||
updateProduct(index, 'productBrand', selectedProduct.brand || selectedProduct.product_brand);
|
updateProduct(index, 'productBrand', selectedProduct.brand);
|
||||||
updateProduct(index, 'productType', selectedProduct.productType || selectedProduct.product_type);
|
updateProduct(index, 'productType', selectedProduct.productType);
|
||||||
|
|
||||||
// Pre-populate application rate if available
|
// Pre-populate application rate if available
|
||||||
if (selectedProduct.applicationRate) {
|
if (selectedProduct.rates && selectedProduct.rates.length > 0) {
|
||||||
updateProduct(index, 'rateAmount', selectedProduct.applicationRate);
|
const defaultRate = selectedProduct.rates[0];
|
||||||
} else if (selectedProduct.application_rate) {
|
updateProduct(index, 'rateAmount', defaultRate.amount);
|
||||||
updateProduct(index, 'rateAmount', selectedProduct.application_rate);
|
updateProduct(index, 'rateUnit', defaultRate.unit);
|
||||||
} else if (selectedProduct.recommendedRate) {
|
} else {
|
||||||
updateProduct(index, 'rateAmount', selectedProduct.recommendedRate);
|
// Set default rate unit based on product type
|
||||||
|
if (selectedProduct.productType === 'granular') {
|
||||||
|
updateProduct(index, 'rateUnit', 'lb/1000sqft');
|
||||||
|
} else if (selectedProduct.productType === 'liquid') {
|
||||||
|
updateProduct(index, 'rateUnit', 'oz/1000sqft');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set appropriate rate unit based on product type
|
// For granular products, check if spreader settings exist
|
||||||
const productType = selectedProduct.productType || selectedProduct.product_type;
|
if (selectedProduct.productType === 'granular' && selectedEquipmentId) {
|
||||||
if (productType === 'granular') {
|
checkSpreaderSettings(selectedProduct, selectedEquipmentId);
|
||||||
updateProduct(index, 'rateUnit', 'lb/1000sqft');
|
|
||||||
} else if (productType === 'liquid') {
|
|
||||||
updateProduct(index, 'rateUnit', 'oz/1000sqft');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -495,15 +587,10 @@ const ApplicationPlanModal = ({
|
|||||||
>
|
>
|
||||||
<option value="">Select product</option>
|
<option value="">Select product</option>
|
||||||
{products
|
{products
|
||||||
.filter(prod => !applicationType ||
|
.filter(prod => !applicationType || prod.productType === applicationType)
|
||||||
(prod.productType === applicationType) ||
|
|
||||||
(prod.product_type === applicationType) ||
|
|
||||||
(prod.type === applicationType)
|
|
||||||
)
|
|
||||||
.map(prod => (
|
.map(prod => (
|
||||||
<option key={prod.uniqueId || prod.id} value={prod.uniqueId || prod.id}>
|
<option key={prod.uniqueId || prod.id} value={prod.uniqueId || prod.id}>
|
||||||
{prod.name || prod.product_name || prod.productName || 'Unknown Product'}
|
{prod.name} ({prod.brand})
|
||||||
{(prod.brand || prod.product_brand || prod.productBrand) && ` (${prod.brand || prod.product_brand || prod.productBrand})`}
|
|
||||||
{prod.isShared === false && ' - Custom'}
|
{prod.isShared === false && ' - Custom'}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
@@ -571,6 +658,26 @@ const ApplicationPlanModal = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Application Calculations */}
|
||||||
|
{selectedProducts.length > 0 && selectedAreas.length > 0 && (() => {
|
||||||
|
const calculations = calculateApplicationAmounts();
|
||||||
|
return calculations ? (
|
||||||
|
<div className="border rounded-lg p-4 bg-blue-50">
|
||||||
|
<h3 className="font-medium text-blue-900 mb-2">Application Calculations</h3>
|
||||||
|
<div className="text-sm text-blue-800">
|
||||||
|
<p className="mb-2">Total Area: {calculations.totalArea} sq ft</p>
|
||||||
|
{calculations.calculations.map((calc, index) => (
|
||||||
|
<div key={index} className="mb-2">
|
||||||
|
<p className="font-medium">{calc.productName}:</p>
|
||||||
|
<p className="ml-2">Product needed: {calc.totalProductNeeded} {calc.unit}</p>
|
||||||
|
{calc.waterNeeded > 0 && <p className="ml-2">Water needed: {calc.waterNeeded} gallons</p>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
})()}
|
||||||
|
|
||||||
{/* Notes */}
|
{/* Notes */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
@@ -670,6 +777,43 @@ const ApplicationPlanModal = ({
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Spreader Settings Form Modal */}
|
||||||
|
{showSpreaderSettingsForm && currentProductForSettings && (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-60">
|
||||||
|
<div className="bg-white rounded-lg p-6 w-full max-w-md mx-4">
|
||||||
|
<h3 className="text-lg font-semibold mb-4">Add Spreader Settings</h3>
|
||||||
|
<p className="text-sm text-gray-600 mb-4">
|
||||||
|
No spreader settings found for "{currentProductForSettings.name}".
|
||||||
|
Please add the spreader setting for accurate calculations.
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setShowSpreaderSettingsForm(false);
|
||||||
|
setCurrentProductForSettings(null);
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 text-gray-600 hover:text-gray-800"
|
||||||
|
>
|
||||||
|
Skip for Now
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
// Open spreader settings page in new tab
|
||||||
|
window.open('/spreader-settings', '_blank');
|
||||||
|
setShowSpreaderSettingsForm(false);
|
||||||
|
setCurrentProductForSettings(null);
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
Add Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user