import React, { useState, useEffect } from 'react'; import { PlusIcon, MapPinIcon, BeakerIcon, WrenchScrewdriverIcon, CalculatorIcon, PencilIcon, TrashIcon, PlayIcon } from '@heroicons/react/24/outline'; import { propertiesAPI, productsAPI, equipmentAPI, applicationsAPI, nozzlesAPI } from '../../services/api'; import LoadingSpinner from '../../components/UI/LoadingSpinner'; import PropertyMap from '../../components/Maps/PropertyMap'; import toast from 'react-hot-toast'; const Applications = () => { const [showPlanForm, setShowPlanForm] = useState(false); const [applications, setApplications] = useState([]); const [loading, setLoading] = useState(true); const [properties, setProperties] = useState([]); const [products, setProducts] = useState([]); const [equipment, setEquipment] = useState([]); const [nozzles, setNozzles] = useState([]); const [selectedPropertyDetails, setSelectedPropertyDetails] = useState(null); const [editingPlan, setEditingPlan] = useState(null); const [propertyCache, setPropertyCache] = useState({}); const [spreaderRecommendation, setSpreaderRecommendation] = useState(null); const [loadingRecommendation, setLoadingRecommendation] = useState(false); // Application execution state const [executingApplication, setExecutingApplication] = useState(null); const [showExecutionModal, setShowExecutionModal] = useState(false); useEffect(() => { fetchApplications(); fetchPlanningData(); }, []); const fetchApplications = async () => { try { const response = await applicationsAPI.getPlans(); setApplications(response.data.data.plans || []); } catch (error) { console.error('Failed to fetch applications:', error); toast.error('Failed to load applications'); } }; const fetchPlanningData = async () => { try { setLoading(true); const [propertiesResponse, productsResponse, equipmentResponse, nozzlesResponse] = await Promise.all([ propertiesAPI.getAll(), productsAPI.getAll(), equipmentAPI.getAll(), nozzlesAPI.getAll() ]); setProperties(propertiesResponse.data.data.properties || []); // Combine shared and user products with unique IDs const sharedProducts = (productsResponse.data.data.sharedProducts || []).map(product => ({ ...product, uniqueId: `shared_${product.id}`, isShared: true })); const userProducts = (productsResponse.data.data.userProducts || []).map(product => ({ ...product, uniqueId: `user_${product.id}`, isShared: false })); const allProducts = [...sharedProducts, ...userProducts]; setProducts(allProducts); setEquipment(equipmentResponse.data.data.equipment || []); setNozzles(nozzlesResponse.data.data?.nozzles || nozzlesResponse.data || []); } catch (error) { console.error('Failed to fetch planning data:', error); toast.error('Failed to load planning data'); } finally { setLoading(false); } }; const fetchPropertyDetails = async (propertyId) => { // Check cache first if (propertyCache[propertyId]) { setSelectedPropertyDetails(propertyCache[propertyId]); return propertyCache[propertyId]; } try { const response = await propertiesAPI.getById(parseInt(propertyId)); const property = response.data.data.property; // Cache the result setPropertyCache(prev => ({ ...prev, [propertyId]: property })); setSelectedPropertyDetails(property); return property; } catch (error) { console.error('Failed to fetch property details:', error); toast.error('Failed to load property details'); setSelectedPropertyDetails(null); return null; } }; const handleDeletePlan = async (planId, planName) => { if (window.confirm(`Are you sure you want to delete the plan for "${planName}"?`)) { try { await applicationsAPI.deletePlan(planId); toast.success('Application plan deleted successfully'); fetchApplications(); // Refresh the list } catch (error) { console.error('Failed to delete plan:', error); toast.error('Failed to delete application plan'); } } }; const handleEditPlan = async (planId) => { try { // Fetch the full plan details const response = await applicationsAPI.getPlan(planId); const plan = response.data.data.plan; // Set up the editing plan data setEditingPlan(plan); setShowPlanForm(true); } catch (error) { console.error('Failed to fetch plan details:', error); toast.error('Failed to load plan details'); } }; const handleExecuteApplication = async (application) => { try { // Set the executing application and show the modal setExecutingApplication(application); // Also fetch the property details if we don't have them if (!selectedPropertyDetails || selectedPropertyDetails.id !== application.property?.id) { await fetchPropertyDetails(application.property?.id || application.section?.propertyId); } setShowExecutionModal(true); } catch (error) { console.error('Failed to start application execution:', error); toast.error('Failed to start application execution'); } }; if (loading) { return (
); } // 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); } }; return (

Applications

Plan, track, and log your lawn applications

{/* Applications List */} {applications.length === 0 ? (

No Applications Yet

Start by planning your first lawn application

) : (
{applications.map((application) => (

{application.propertyName} - {application.sectionName}

{application.status}

{application.propertyAddress}

Areas: {application.sectionNames} ({Math.round(application.totalSectionArea || application.sectionArea || 0).toLocaleString()} sq ft)

Equipment: {application.equipmentName}

Products: {application.productCount}

{/* Display calculated amounts */} {(application.totalProductAmount > 0 || (application.productDetails && application.productDetails.length > 0)) && (

Calculated Requirements:

{/* Show individual products for liquid tank mix */} {application.productDetails && application.productDetails.length > 1 ? ( <> {application.productDetails.map((product, index) => (

• {product.name}{product.brand ? ` (${product.brand})` : ''}: {product.calculatedAmount.toFixed(2)} oz

))} {application.totalWaterAmount > 0 && (

• Water: {application.totalWaterAmount.toFixed(2)} gallons

)} {application.avgSpeedMph > 0 && (

• Target Speed: {application.avgSpeedMph.toFixed(1)} mph

)} {application.spreaderSetting && (

• Spreader Setting: {application.spreaderSetting}

)} ) : ( <> {/* Show single product with name */} {application.productDetails && application.productDetails.length === 1 ? (

• {application.productDetails[0].name}{application.productDetails[0].brand ? ` (${application.productDetails[0].brand})` : ''}: {application.productDetails[0].calculatedAmount.toFixed(2)} {application.totalWaterAmount > 0 ? 'oz' : 'lbs'}

) : (

• Product: {application.totalProductAmount.toFixed(2)} {application.totalWaterAmount > 0 ? 'oz' : 'lbs'}

)} {application.totalWaterAmount > 0 && (

• Water: {application.totalWaterAmount.toFixed(2)} gallons

)} {application.avgSpeedMph > 0 && (

• Target Speed: {application.avgSpeedMph.toFixed(1)} mph

)} {application.spreaderSetting && (

• Spreader Setting: {application.spreaderSetting}

)} )}
)} {application.notes && (

"{application.notes}"

)}

{application.plannedDate ? new Date(application.plannedDate).toLocaleDateString() : 'No date set'}

Created {new Date(application.createdAt).toLocaleDateString()}

{application.status === 'planned' && ( )}
))}
)} {/* Plan Application Modal */} {showPlanForm && ( { setShowPlanForm(false); setEditingPlan(null); setSelectedPropertyDetails(null); }} properties={properties} products={products} equipment={equipment} nozzles={nozzles} selectedPropertyDetails={selectedPropertyDetails} onPropertySelect={fetchPropertyDetails} editingPlan={editingPlan} onSubmit={async (planData) => { try { if (editingPlan) { // Edit existing plan - backend now supports multiple areas natively const selectedEquipment = equipment.find(eq => eq.id === parseInt(planData.equipmentId)); const selectedNozzle = planData.nozzleId ? nozzles.find(n => n.id === parseInt(planData.nozzleId)) : null; // Calculate total area for all selected areas const totalAreaSquareFeet = planData.selectedAreas.reduce((total, areaId) => { const area = selectedPropertyDetails.sections.find(s => s.id === areaId); return total + (area?.area || 0); }, 0); const planPayload = { lawnSectionIds: planData.selectedAreas.map(id => parseInt(id)), // Send multiple areas equipmentId: parseInt(planData.equipmentId), ...(planData.applicationType === 'liquid' && planData.nozzleId && { nozzleId: parseInt(planData.nozzleId) }), plannedDate: planData.plannedDate || new Date().toISOString().split('T')[0], notes: planData.notes || '', areaSquareFeet: totalAreaSquareFeet, equipment: { id: selectedEquipment?.id, categoryName: selectedEquipment?.categoryName, tankSizeGallons: selectedEquipment?.tankSizeGallons, pumpGpm: selectedEquipment?.pumpGpm, sprayWidthFeet: selectedEquipment?.sprayWidthFeet, capacityLbs: selectedEquipment?.capacityLbs, spreadWidth: selectedEquipment?.spreadWidth }, ...(planData.applicationType === 'liquid' && selectedNozzle && { nozzle: { id: selectedNozzle.id, flowRateGpm: selectedNozzle.flowRateGpm, sprayAngle: selectedNozzle.sprayAngle } }), 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); toast.success(`Application plan updated successfully for ${planData.selectedAreas.length} area(s)`); } else { // Create new plan - backend now supports multiple areas in single plan const selectedEquipment = equipment.find(eq => eq.id === parseInt(planData.equipmentId)); const selectedNozzle = planData.nozzleId ? nozzles.find(n => n.id === parseInt(planData.nozzleId)) : null; // Calculate total area for all selected areas const totalAreaSquareFeet = planData.selectedAreas.reduce((total, areaId) => { const area = selectedPropertyDetails.sections.find(s => s.id === areaId); return total + (area?.area || 0); }, 0); const planPayload = { lawnSectionIds: planData.selectedAreas.map(id => parseInt(id)), // Send multiple areas equipmentId: parseInt(planData.equipmentId), ...(planData.applicationType === 'liquid' && planData.nozzleId && { nozzleId: parseInt(planData.nozzleId) }), plannedDate: new Date().toISOString().split('T')[0], notes: planData.notes || '', areaSquareFeet: totalAreaSquareFeet, equipment: { id: selectedEquipment?.id, categoryName: selectedEquipment?.categoryName, tankSizeGallons: selectedEquipment?.tankSizeGallons, pumpGpm: selectedEquipment?.pumpGpm, sprayWidthFeet: selectedEquipment?.sprayWidthFeet, capacityLbs: selectedEquipment?.capacityLbs, spreadWidth: selectedEquipment?.spreadWidth }, ...(planData.applicationType === 'liquid' && selectedNozzle && { nozzle: { id: selectedNozzle.id, flowRateGpm: selectedNozzle.flowRateGpm, sprayAngle: selectedNozzle.sprayAngle } }), 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.createPlan(planPayload); toast.success(`Created application plan for ${planData.selectedAreas.length} area(s) successfully`); } setShowPlanForm(false); setEditingPlan(null); fetchApplications(); } catch (error) { console.error('Failed to save application plan:', error); toast.error('Failed to save application plan'); } }} /> )}
); }; // Application Planning Modal Component const ApplicationPlanModal = ({ onClose, onSubmit, properties, products, equipment, nozzles, selectedPropertyDetails, onPropertySelect, editingPlan }) => { const [loadingProperty, setLoadingProperty] = useState(false); const [showSpreaderSettingPrompt, setShowSpreaderSettingPrompt] = useState(false); const [missingSpreaderSetting, setMissingSpreaderSetting] = useState({ productId: null, userProductId: null, equipmentId: null, productName: '', equipmentName: '' }); const [newSpreaderSetting, setNewSpreaderSetting] = useState({ settingValue: '', rateDescription: '', notes: '' }); const [planData, setPlanData] = useState({ propertyId: '', selectedAreas: [], productId: '', selectedProduct: null, selectedProducts: [], // For liquid tank mixing - array of {product, rate} applicationType: '', // 'liquid' or 'granular' equipmentId: '', nozzleId: '', plannedDate: '', notes: '' }); // Reset form when modal opens fresh (not editing) useEffect(() => { if (!editingPlan) { setPlanData({ propertyId: '', selectedAreas: [], productId: '', selectedProduct: null, selectedProducts: [], applicationType: '', equipmentId: '', nozzleId: '', plannedDate: '', notes: '' }); } }, [editingPlan]); // Check spreader settings when both product and equipment are selected for granular applications useEffect(() => { console.log('Spreader setting useEffect triggered:', { applicationType: planData.applicationType, hasSelectedProduct: !!planData.selectedProduct, hasEquipmentId: !!planData.equipmentId, productName: planData.selectedProduct?.customName || planData.selectedProduct?.name, equipmentId: planData.equipmentId, isEditing: !!editingPlan }); if (planData.applicationType === 'granular' && planData.selectedProduct && planData.equipmentId) { console.log('Triggering spreader setting check from useEffect'); checkSpreaderSetting(planData.selectedProduct, planData.equipmentId); } }, [planData.applicationType, planData.selectedProduct, planData.equipmentId]); // Initialize form with editing data useEffect(() => { if (editingPlan && products.length > 0) { const propertyId = editingPlan.section?.propertyId || editingPlan.property?.id; // Determine application type from the first product const firstProduct = editingPlan.products?.[0]; let applicationType = 'liquid'; // Default to liquid if (firstProduct) { const productType = firstProduct.productType || firstProduct.customProductType; // Use flexible matching like elsewhere in the codebase const isGranular = productType && ( productType.toLowerCase().includes('granular') || productType.toLowerCase().includes('granule') ); // Also check equipment type as fallback for granular detection const equipmentType = editingPlan.equipment?.categoryName?.toLowerCase() || ''; const isGranularByEquipment = equipmentType.includes('spreader') || equipmentType.includes('granular'); // If we can't determine from product type, use equipment type applicationType = (isGranular || (!productType && isGranularByEquipment)) ? 'granular' : 'liquid'; console.log('Edit plan - application type detection:', { productType: productType, isGranular: isGranular, equipmentType: equipmentType, isGranularByEquipment: isGranularByEquipment, applicationType: applicationType, productName: firstProduct.productName, fullProduct: firstProduct }); } // Handle different application types if (applicationType === 'granular') { // Granular - single product let selectedProduct = null; if (firstProduct) { if (firstProduct.productId) { selectedProduct = products.find(p => p.uniqueId === `shared_${firstProduct.productId}`); } else if (firstProduct.userProductId) { selectedProduct = products.find(p => p.uniqueId === `user_${firstProduct.userProductId}`); } } setPlanData({ propertyId: propertyId?.toString() || '', selectedAreas: editingPlan.sections?.map(s => s.id) || [], // Handle multiple areas productId: selectedProduct?.uniqueId || '', selectedProduct: selectedProduct, selectedProducts: [], applicationType: 'granular', equipmentId: editingPlan.equipment?.id?.toString() || '', nozzleId: editingPlan.nozzle?.id?.toString() || '', plannedDate: editingPlan.plannedDate ? new Date(editingPlan.plannedDate).toISOString().split('T')[0] : '', notes: editingPlan.notes || '' }); } else { // Liquid - multiple products (tank mix) const selectedProducts = editingPlan.products?.map(product => { let foundProduct = null; if (product.productId) { foundProduct = products.find(p => p.uniqueId === `shared_${product.productId}`); } else if (product.userProductId) { foundProduct = products.find(p => p.uniqueId === `user_${product.userProductId}`); } return { product: foundProduct, rateAmount: product.rateAmount || 1, rateUnit: product.rateUnit || 'oz/1000 sq ft' }; }).filter(item => item.product) || []; setPlanData({ propertyId: propertyId?.toString() || '', selectedAreas: editingPlan.sections?.map(s => s.id) || [], // Handle multiple areas productId: '', selectedProduct: null, selectedProducts: selectedProducts, applicationType: 'liquid', equipmentId: editingPlan.equipment?.id?.toString() || '', nozzleId: editingPlan.nozzle?.id?.toString() || '', plannedDate: editingPlan.plannedDate ? new Date(editingPlan.plannedDate).toISOString().split('T')[0] : '', notes: editingPlan.notes || '' }); } // Only fetch property details if we don't already have them if (propertyId && (!selectedPropertyDetails || selectedPropertyDetails.id !== propertyId)) { onPropertySelect(propertyId); } } }, [editingPlan, products]); const handlePropertyChange = async (propertyId) => { setPlanData({ ...planData, propertyId, selectedAreas: [] }); if (propertyId && propertyId !== selectedPropertyDetails?.id?.toString()) { setLoadingProperty(true); await onPropertySelect(propertyId); setLoadingProperty(false); } }; // Check for spreader settings when granular product + equipment selected const checkSpreaderSetting = async (product, equipmentId) => { if (!product || !equipmentId) { return; } // Check if product is granular (more flexible matching like the product filter) const productType = product.productType || product.customProductType; const isGranular = productType && ( productType.toLowerCase().includes('granular') || productType.toLowerCase().includes('granule') ); if (!isGranular) { return; } const selectedEquipment = equipment.find(eq => eq.id === parseInt(equipmentId)); if (!selectedEquipment) { console.log('Spreader setting check skipped - equipment not found:', { equipmentId: equipmentId }); return; } // Check if equipment is spreader-related (more flexible matching) const categoryName = selectedEquipment.categoryName?.toLowerCase() || ''; const isSpreaderEquipment = categoryName.includes('spreader') || categoryName.includes('granular'); if (!isSpreaderEquipment) { console.log('Spreader setting check skipped - equipment not a spreader:', { selectedEquipment: selectedEquipment?.customName, category: selectedEquipment?.categoryName }); return; } console.log('Checking spreader setting for:', { product: product.customName || product.name, equipment: selectedEquipment.customName, productType: productType }); try { // Check if spreader setting exists const endpoint = product.isShared ? `/api/product-spreader-settings/product/${product.productId || product.id}` : `/api/product-spreader-settings/user-product/${product.id}`; const response = await fetch(endpoint, { headers: { 'Authorization': `Bearer ${localStorage.getItem('authToken')}` } }); if (response.ok) { const data = await response.json(); const settings = data.data?.settings || []; // Check if there's a setting for this specific equipment const existingSetting = settings.find(setting => setting.equipmentId === selectedEquipment.id ); if (!existingSetting) { // No setting found - prompt user to create one setMissingSpreaderSetting({ productId: product.isShared ? (product.productId || product.id) : null, userProductId: product.isShared ? null : product.id, equipmentId: selectedEquipment.id, productName: product.customName || product.name, equipmentName: selectedEquipment.customName }); setShowSpreaderSettingPrompt(true); } } } catch (error) { console.error('Failed to check spreader settings:', error); } }; // Save new spreader setting const saveSpreaderSetting = async () => { try { const settingData = { ...(missingSpreaderSetting.productId && { productId: missingSpreaderSetting.productId }), ...(missingSpreaderSetting.userProductId && { userProductId: missingSpreaderSetting.userProductId }), equipmentId: missingSpreaderSetting.equipmentId, settingValue: newSpreaderSetting.settingValue, rateDescription: newSpreaderSetting.rateDescription || null, notes: newSpreaderSetting.notes || null }; const response = await fetch('/api/product-spreader-settings', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('authToken')}` }, body: JSON.stringify(settingData) }); if (response.ok) { toast.success('Spreader setting saved successfully'); setShowSpreaderSettingPrompt(false); setNewSpreaderSetting({ settingValue: '', rateDescription: '', notes: '' }); } else { toast.error('Failed to save spreader setting'); } } catch (error) { console.error('Failed to save spreader setting:', error); toast.error('Failed to save spreader setting'); } }; // Filter equipment based on application type const availableEquipment = equipment.filter(eq => { if (planData.applicationType === 'liquid') { return eq.categoryName === 'Sprayer'; } else if (planData.applicationType === 'granular') { return eq.categoryName === 'Spreader'; } return false; }); const handleSubmit = (e) => { e.preventDefault(); if (!planData.propertyId || planData.selectedAreas.length === 0) { toast.error('Please select a property and at least one area'); return; } 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; } onSubmit(planData); }; const handleAreaToggle = (areaId) => { setPlanData(prev => ({ ...prev, selectedAreas: prev.selectedAreas.includes(areaId) ? prev.selectedAreas.filter(id => id !== areaId) : [...prev.selectedAreas, areaId] })); }; return (

{editingPlan ? 'Edit Application Plan' : 'Plan Application'}

{/* Property Selection */}
{/* Area Selection with Map */} {loadingProperty && (
Loading property details...
)} {selectedPropertyDetails && (
{/* Property Map */} {selectedPropertyDetails.latitude && selectedPropertyDetails.longitude && (
handleAreaToggle(section.id)} />

Click sections to select

{planData.selectedAreas.length > 0 && (

{planData.selectedAreas.length} selected

)}
)} {selectedPropertyDetails.sections && selectedPropertyDetails.sections.length > 0 ? (
{selectedPropertyDetails.sections.map((section) => ( ))}
{planData.selectedAreas.length > 0 && (

Total area: {selectedPropertyDetails.sections .filter(s => planData.selectedAreas.includes(s.id)) .reduce((total, s) => total + (s.area || 0), 0).toFixed(0)} sq ft

)}
) : (

This property has no lawn sections defined. Please add lawn sections to the property first.

)}
)} {/* Application Type Selection */}
{/* Product Selection - Single for Granular */} {planData.applicationType === 'granular' && (
)} {/* Product Selection - Multiple for Liquid Tank Mix */} {planData.applicationType === 'liquid' && (
{/* Selected Products List */} {planData.selectedProducts.length > 0 && (
{planData.selectedProducts.map((item, index) => (
{item.product.customName || item.product.name} {item.product.brand || item.product.customBrand ? ( - {item.product.brand || item.product.customBrand} ) : null}
{ const newProducts = [...planData.selectedProducts]; newProducts[index] = { ...item, rateAmount: e.target.value === '' ? '' : parseFloat(e.target.value) }; setPlanData({ ...planData, selectedProducts: newProducts }); }} className="w-24 px-2 py-1 text-sm border rounded" placeholder="Rate" />
))}
)} {/* Add Product Dropdown */} {planData.selectedProducts.length === 0 && (

Select liquid products to mix in the tank. You can add herbicides, surfactants, and other liquid products.

)}
)} {/* Equipment Selection */} {planData.applicationType && (
{availableEquipment.length === 0 && (

No {planData.applicationType === 'liquid' ? 'sprayers' : 'spreaders'} found. Please add equipment first.

)}
)} {/* Spreader Recommendation for Granular Applications */} {/* Nozzle Selection for Liquid Applications */} {planData.applicationType === 'liquid' && (
{nozzles.length === 0 && (

No nozzles found. Go to Equipment → Add Equipment → Select "Nozzle" category to add nozzles first.

)}
)} {/* Planned Date */}
setPlanData({ ...planData, plannedDate: e.target.value })} />
{/* Notes */}