import React, { useState, useEffect } from 'react'; import { PlusIcon, MapPinIcon, BeakerIcon, WrenchScrewdriverIcon, CalculatorIcon, PencilIcon, TrashIcon } 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); 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) => { try { const response = await propertiesAPI.getById(parseInt(propertyId)); const property = response.data.data.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'); } }; if (loading) { return (
); } 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}

Area: {Math.round(application.sectionArea).toLocaleString()} sq ft

Equipment: {application.equipmentName}

Products: {application.productCount}

{/* Display calculated amounts */} {application.totalProductAmount > 0 && (

Calculated Requirements:

• 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.notes && (

"{application.notes}"

)}

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

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

))}
)} {/* Plan Application Modal */} {showPlanForm && ( { setShowPlanForm(false); setEditingPlan(null); }} properties={properties} products={products} equipment={equipment} nozzles={nozzles} selectedPropertyDetails={selectedPropertyDetails} onPropertySelect={fetchPropertyDetails} editingPlan={editingPlan} onSubmit={async (planData) => { try { if (editingPlan) { // Edit existing plan const selectedArea = selectedPropertyDetails.sections.find(s => s.id === planData.selectedAreas[0]); const areaSquareFeet = selectedArea?.area || 0; const selectedEquipment = equipment.find(eq => eq.id === parseInt(planData.equipmentId)); const selectedNozzle = planData.nozzleId ? nozzles.find(n => n.id === parseInt(planData.nozzleId)) : null; const planPayload = { lawnSectionId: parseInt(planData.selectedAreas[0]), equipmentId: parseInt(planData.equipmentId), nozzleId: planData.nozzleId ? parseInt(planData.nozzleId) : null, plannedDate: planData.plannedDate || new Date().toISOString().split('T')[0], notes: planData.notes || '', areaSquareFeet: areaSquareFeet, equipment: { id: selectedEquipment?.id, categoryName: selectedEquipment?.categoryName, tankSizeGallons: selectedEquipment?.tankSizeGallons, pumpGpm: selectedEquipment?.pumpGpm, sprayWidthFeet: selectedEquipment?.sprayWidthFeet, capacityLbs: selectedEquipment?.capacityLbs, spreadWidth: selectedEquipment?.spreadWidth }, nozzle: selectedNozzle ? { id: selectedNozzle.id, flowRateGpm: selectedNozzle.flowRateGpm, sprayAngle: selectedNozzle.sprayAngle } : null, 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 }] }; await applicationsAPI.updatePlan(editingPlan.id, planPayload); toast.success('Application plan updated successfully'); } else { // Create new plan(s) const planPromises = planData.selectedAreas.map(async (areaId) => { const selectedArea = selectedPropertyDetails.sections.find(s => s.id === areaId); const areaSquareFeet = selectedArea?.area || 0; const selectedEquipment = equipment.find(eq => eq.id === parseInt(planData.equipmentId)); const selectedNozzle = planData.nozzleId ? nozzles.find(n => n.id === parseInt(planData.nozzleId)) : null; const planPayload = { lawnSectionId: parseInt(areaId), equipmentId: parseInt(planData.equipmentId), nozzleId: planData.nozzleId ? parseInt(planData.nozzleId) : null, plannedDate: new Date().toISOString().split('T')[0], notes: planData.notes || '', areaSquareFeet: areaSquareFeet, equipment: { id: selectedEquipment?.id, categoryName: selectedEquipment?.categoryName, tankSizeGallons: selectedEquipment?.tankSizeGallons, pumpGpm: selectedEquipment?.pumpGpm, sprayWidthFeet: selectedEquipment?.sprayWidthFeet, capacityLbs: selectedEquipment?.capacityLbs, spreadWidth: selectedEquipment?.spreadWidth }, nozzle: selectedNozzle ? { id: selectedNozzle.id, flowRateGpm: selectedNozzle.flowRateGpm, sprayAngle: selectedNozzle.sprayAngle } : null, 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 }] }; return applicationsAPI.createPlan(planPayload); }); await Promise.all(planPromises); toast.success(`Created ${planData.selectedAreas.length} application plan(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 [planData, setPlanData] = useState({ propertyId: '', selectedAreas: [], productId: '', selectedProduct: null, applicationType: '', // 'liquid' or 'granular' equipmentId: '', nozzleId: '', plannedDate: '', notes: '' }); // Initialize form with editing data useEffect(() => { if (editingPlan) { const propertyId = editingPlan.section?.propertyId || editingPlan.property?.id; if (propertyId && propertyId !== planData.propertyId) { onPropertySelect(propertyId); } // Find the product from the plans products array const planProduct = editingPlan.products?.[0]; let selectedProduct = null; if (planProduct) { if (planProduct.productId) { selectedProduct = products.find(p => p.uniqueId === `shared_${planProduct.productId}`); } else if (planProduct.userProductId) { selectedProduct = products.find(p => p.uniqueId === `user_${planProduct.userProductId}`); } } setPlanData({ propertyId: propertyId?.toString() || '', selectedAreas: [editingPlan.section?.id], productId: selectedProduct?.uniqueId || '', selectedProduct: selectedProduct, applicationType: planProduct?.applicationType || '', equipmentId: editingPlan.equipment?.id?.toString() || '', nozzleId: editingPlan.nozzle?.id?.toString() || '', plannedDate: editingPlan.plannedDate ? new Date(editingPlan.plannedDate).toISOString().split('T')[0] : '', notes: editingPlan.notes || '' }); } }, [editingPlan, products, onPropertySelect]); const handlePropertyChange = async (propertyId) => { setPlanData({ ...planData, propertyId, selectedAreas: [] }); if (propertyId) { setLoadingProperty(true); await onPropertySelect(propertyId); setLoadingProperty(false); } }; // 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.productId) { toast.error('Please select a product'); 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.

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

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

)}
)} {/* 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 */}