This commit is contained in:
Jake Kasper
2025-08-25 14:02:50 -04:00
parent fefd22f696
commit d400a84653

View File

@@ -24,6 +24,8 @@ const Applications = () => {
const [selectedPropertyDetails, setSelectedPropertyDetails] = useState(null);
const [editingPlan, setEditingPlan] = useState(null);
const [propertyCache, setPropertyCache] = useState({});
const [spreaderRecommendation, setSpreaderRecommendation] = useState(null);
const [loadingRecommendation, setLoadingRecommendation] = useState(false);
useEffect(() => {
fetchApplications();
@@ -432,6 +434,11 @@ const ApplicationPlanModal = ({
}
}, [editingPlan, products]);
// Load spreader recommendation when relevant fields change
useEffect(() => {
loadSpreaderRecommendation(planData.selectedProduct, planData.equipmentId, planData.selectedAreas);
}, [planData.selectedProduct, planData.equipmentId, planData.selectedAreas, selectedPropertyDetails, equipment]);
const handlePropertyChange = async (propertyId) => {
setPlanData({ ...planData, propertyId, selectedAreas: [] });
if (propertyId && propertyId !== selectedPropertyDetails?.id?.toString()) {
@@ -441,6 +448,104 @@ const ApplicationPlanModal = ({
}
};
// Load spreader recommendations when granular product and spreader are selected
const loadSpreaderRecommendation = async (product, equipmentId, selectedAreas) => {
if (!product || !equipmentId || !selectedAreas.length || product.productType !== 'granular') {
setSpreaderRecommendation(null);
return;
}
setLoadingRecommendation(true);
try {
// Find the selected equipment details
const selectedEquipment = equipment.find(eq => eq.id === parseInt(equipmentId));
if (!selectedEquipment) {
setSpreaderRecommendation(null);
return;
}
// Load spreader settings for this product
const productApiId = product.isShared ? product.id : product.id; // Use the actual 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) {
setSpreaderRecommendation(null);
return;
}
const data = await response.json();
const settings = data.data?.settings || [];
// Find a matching setting for this equipment
let matchingSetting = null;
// First try to find exact equipment match
matchingSetting = settings.find(setting =>
setting.equipmentId === selectedEquipment.id
);
// If no exact match, try to find by equipment brand/manufacturer
if (!matchingSetting && selectedEquipment.manufacturer) {
matchingSetting = settings.find(setting =>
setting.spreaderBrand &&
setting.spreaderBrand.toLowerCase().includes(selectedEquipment.manufacturer.toLowerCase())
);
}
// If still no match, use any available setting as fallback
if (!matchingSetting && settings.length > 0) {
matchingSetting = settings[0];
}
if (matchingSetting) {
// Calculate total area and product amount needed
const totalArea = selectedAreas.reduce((sum, areaId) => {
const area = selectedPropertyDetails?.sections?.find(s => s.id === areaId);
return sum + (area?.area || 0);
}, 0);
// Calculate product amount based on rate
const rateAmount = product.customRateAmount || product.rateAmount || 1;
const rateUnit = product.customRateUnit || product.rateUnit || 'lbs/1000 sq ft';
let productAmountLbs = 0;
if (rateUnit.includes('1000')) {
// Rate per 1000 sq ft
productAmountLbs = (rateAmount * totalArea) / 1000;
} else if (rateUnit.includes('acre')) {
// Rate per acre (43,560 sq ft)
productAmountLbs = (rateAmount * totalArea) / 43560;
} else {
// Assume rate per sq ft
productAmountLbs = rateAmount * totalArea;
}
setSpreaderRecommendation({
setting: matchingSetting,
equipment: selectedEquipment,
totalArea,
productAmountLbs: Math.round(productAmountLbs * 100) / 100, // Round to 2 decimal places
isExactMatch: settings.some(s => s.equipmentId === selectedEquipment.id)
});
} else {
setSpreaderRecommendation(null);
}
} catch (error) {
console.error('Failed to load spreader recommendation:', error);
setSpreaderRecommendation(null);
} finally {
setLoadingRecommendation(false);
}
};
// Filter equipment based on application type
const availableEquipment = equipment.filter(eq => {
if (planData.applicationType === 'liquid') {
@@ -665,6 +770,63 @@ const ApplicationPlanModal = ({
</div>
)}
{/* Spreader Recommendation for Granular Applications */}
{planData.applicationType === 'granular' && (spreaderRecommendation || loadingRecommendation) && (
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
<div className="flex items-center gap-2 mb-3">
<CalculatorIcon className="h-5 w-5 text-green-600" />
<h3 className="font-semibold text-green-800">Spreader Recommendation</h3>
</div>
{loadingRecommendation ? (
<div className="text-sm text-green-700">Loading recommendation...</div>
) : spreaderRecommendation ? (
<div className="space-y-2">
<div className="flex items-start justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-green-800">
Spreader Setting: <span className="text-lg font-bold">{spreaderRecommendation.setting.settingValue}</span>
</p>
<p className="text-sm text-green-700">
Product Amount: <span className="font-semibold">{spreaderRecommendation.productAmountLbs} lbs</span>
</p>
<p className="text-xs text-green-600">
Total Area: {spreaderRecommendation.totalArea.toLocaleString()} sq ft
</p>
</div>
<div className="text-right">
<div className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
spreaderRecommendation.isExactMatch
? 'bg-green-100 text-green-800'
: 'bg-yellow-100 text-yellow-800'
}`}>
{spreaderRecommendation.isExactMatch ? 'Exact Match' : 'Approximate'}
</div>
</div>
</div>
{spreaderRecommendation.setting.rateDescription && (
<p className="text-xs text-green-600 italic">
{spreaderRecommendation.setting.rateDescription}
</p>
)}
{spreaderRecommendation.setting.notes && (
<p className="text-xs text-green-600">
Note: {spreaderRecommendation.setting.notes}
</p>
)}
{!spreaderRecommendation.isExactMatch && (
<p className="text-xs text-yellow-700 bg-yellow-50 rounded p-2 mt-2">
This setting is based on a similar spreader. Please verify the setting is appropriate for your specific equipment.
</p>
)}
</div>
) : null}
</div>
)}
{/* Nozzle Selection for Liquid Applications */}
{planData.applicationType === 'liquid' && (
<div>