diff --git a/frontend/src/components/Maps/PropertyMap.js b/frontend/src/components/Maps/PropertyMap.js index 7969fdf..a2f53b5 100644 --- a/frontend/src/components/Maps/PropertyMap.js +++ b/frontend/src/components/Maps/PropertyMap.js @@ -61,6 +61,8 @@ const PropertyMap = ({ onSectionUpdate, onSectionDelete, onPropertyUpdate, + onSectionClick, + selectedSections = [], editable = false, className = "h-96 w-full" }) => { @@ -136,7 +138,13 @@ const PropertyMap = ({ // Handle section click const handleSectionClick = (section) => { - setSelectedSection(selectedSection?.id === section.id ? null : section); + if (onSectionClick) { + // For application planning mode - use the provided callback + onSectionClick(section); + } else { + // For editing mode - use the internal state + setSelectedSection(selectedSection?.id === section.id ? null : section); + } }; // Delete selected section @@ -350,18 +358,20 @@ const PropertyMap = ({ if (!section.polygonData?.coordinates?.[0]) return null; const coordinates = section.polygonData.coordinates[0].map(([lng, lat]) => [lat, lng]); - const isSelected = selectedSection?.id === section.id; + const isInternallySelected = selectedSection?.id === section.id; + const isExternallySelected = selectedSections.includes(section.id); + const isSelected = isInternallySelected || isExternallySelected; return ( handleSectionClick(section) diff --git a/frontend/src/pages/Applications/Applications.js b/frontend/src/pages/Applications/Applications.js index 7f00284..78fdcf6 100644 --- a/frontend/src/pages/Applications/Applications.js +++ b/frontend/src/pages/Applications/Applications.js @@ -6,8 +6,9 @@ import { WrenchScrewdriverIcon, CalculatorIcon } from '@heroicons/react/24/outline'; -import { propertiesAPI, productsAPI, equipmentAPI } from '../../services/api'; +import { propertiesAPI, productsAPI, equipmentAPI, applicationsAPI } from '../../services/api'; import LoadingSpinner from '../../components/UI/LoadingSpinner'; +import PropertyMap from '../../components/Maps/PropertyMap'; import toast from 'react-hot-toast'; const Applications = () => { @@ -22,8 +23,8 @@ const Applications = () => { const fetchApplications = async () => { try { setLoading(true); - // TODO: Implement applications API endpoint - setApplications([]); + 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'); @@ -77,8 +78,49 @@ const Applications = () => {
{applications.map((application) => (
- {/* Application card content will go here */} -

Application: {application.id}

+
+
+
+

+ {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()} +

+
+
))}
@@ -88,10 +130,34 @@ const Applications = () => { {showPlanForm && ( setShowPlanForm(false)} - onSubmit={(planData) => { - console.log('Plan submitted:', planData); - setShowPlanForm(false); - fetchApplications(); + 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, + products: [{ + productId: planData.selectedProduct?.isShared ? parseInt(planData.selectedProduct.id) : null, + userProductId: !planData.selectedProduct?.isShared ? parseInt(planData.selectedProduct.id) : null, + 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'); + } }} /> )} @@ -253,7 +319,7 @@ const ApplicationPlanModal = ({ onClose, onSubmit }) => { - {/* Area Selection */} + {/* Area Selection with Map */} {loadingProperty && (
@@ -261,39 +327,66 @@ const ApplicationPlanModal = ({ onClose, onSubmit }) => {
)} - {selectedPropertyDetails && selectedPropertyDetails.sections && selectedPropertyDetails.sections.length > 0 && ( + {selectedPropertyDetails && (
-
- {selectedPropertyDetails.sections.map((section) => ( -
+ )} + + {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. +

+
)} -
- )} - - {selectedPropertyDetails && (!selectedPropertyDetails.sections || selectedPropertyDetails.sections.length === 0) && ( -
-

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

)}