import React, { useState, useEffect } from 'react'; import { PlusIcon, MapPinIcon, BeakerIcon, WrenchScrewdriverIcon, CalculatorIcon } 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); useEffect(() => { fetchApplications(); }, []); const fetchApplications = async () => { try { setLoading(true); 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'); } finally { setLoading(false); } }; 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}

{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)} onSubmit={async (planData) => { try { // Create a plan for each selected area const planPromises = planData.selectedAreas.map(async (areaId) => { const planPayload = { lawnSectionId: parseInt(areaId), equipmentId: parseInt(planData.equipmentId), plannedDate: new Date().toISOString().split('T')[0], // Default to today notes: planData.notes || '', // Ensure notes is never null/undefined 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' }] }; return applicationsAPI.createPlan(planPayload); }); await Promise.all(planPromises); toast.success(`Created ${planData.selectedAreas.length} application plan(s) successfully`); setShowPlanForm(false); fetchApplications(); } catch (error) { console.error('Failed to create application plans:', error); toast.error('Failed to create application plans'); } }} /> )}
); }; // Application Planning Modal Component const ApplicationPlanModal = ({ onClose, onSubmit }) => { const [properties, setProperties] = useState([]); const [products, setProducts] = useState([]); const [equipment, setEquipment] = useState([]); const [nozzles, setNozzles] = useState([]); const [selectedPropertyDetails, setSelectedPropertyDetails] = useState(null); const [loading, setLoading] = useState(true); const [loadingProperty, setLoadingProperty] = useState(false); const [planData, setPlanData] = useState({ propertyId: '', selectedAreas: [], productId: '', selectedProduct: null, applicationType: '', // 'liquid' or 'granular' equipmentId: '', nozzleId: '', notes: '' }); useEffect(() => { fetchPlanningData(); }, []); const fetchPlanningData = async () => { try { setLoading(true); const [propertiesResponse, productsResponse, equipmentResponse, nozzlesResponse] = await Promise.all([ propertiesAPI.getAll(), productsAPI.getAll(), equipmentAPI.getAll(), nozzlesAPI.getAll() ]); console.log('Properties response:', propertiesResponse.data); console.log('Products response:', productsResponse.data); console.log('Equipment response:', equipmentResponse.data); console.log('Nozzles response:', nozzlesResponse.data); 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 { setLoadingProperty(true); const response = await propertiesAPI.getById(parseInt(propertyId)); console.log('Property details response:', response.data); const property = response.data.data.property; console.log('Property sections with polygon data:', property.sections?.map(s => ({ id: s.id, name: s.name, hasPolygonData: !!s.polygonData, polygonDataType: typeof s.polygonData, coordinates: s.polygonData?.coordinates?.[0]?.length || 0 }))); setSelectedPropertyDetails(property); } catch (error) { console.error('Failed to fetch property details:', error); toast.error('Failed to load property details'); setSelectedPropertyDetails(null); } finally { setLoadingProperty(false); } }; const handlePropertyChange = (propertyId) => { setPlanData({ ...planData, propertyId, selectedAreas: [] }); setSelectedPropertyDetails(null); if (propertyId) { fetchPropertyDetails(propertyId); } }; // 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 (

Plan Application

{loading ? ( ) : (
{/* 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.

)}
)} {/* Notes */}