From f6ce0e7da2dbbe87deccd9b526abf1a410cac67e Mon Sep 17 00:00:00 2001 From: Jake Kasper Date: Fri, 22 Aug 2025 14:28:56 -0400 Subject: [PATCH] applications page --- .../src/pages/Applications/Applications.js | 329 +++++++++++++++++- 1 file changed, 325 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/Applications/Applications.js b/frontend/src/pages/Applications/Applications.js index 5e84f4b..f506121 100644 --- a/frontend/src/pages/Applications/Applications.js +++ b/frontend/src/pages/Applications/Applications.js @@ -1,11 +1,332 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { + PlusIcon, + MapPinIcon, + BeakerIcon, + WrenchScrewdriverIcon, + CalculatorIcon +} from '@heroicons/react/24/outline'; +import { propertiesAPI, productsAPI, equipmentAPI } from '../../services/api'; +import LoadingSpinner from '../../components/UI/LoadingSpinner'; +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); + // TODO: Implement applications API endpoint + setApplications([]); + } catch (error) { + console.error('Failed to fetch applications:', error); + toast.error('Failed to load applications'); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ( +
+
+ +
+
+ ); + } + return (
-

Applications

-
-

Application management coming soon...

+
+
+

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 card content will go here */} +

Application: {application.id}

+
+ ))} +
+ )} + + {/* Plan Application Modal */} + {showPlanForm && ( + setShowPlanForm(false)} + onSubmit={(planData) => { + console.log('Plan submitted:', planData); + setShowPlanForm(false); + fetchApplications(); + }} + /> + )} +
+ ); +}; + +// Application Planning Modal Component +const ApplicationPlanModal = ({ onClose, onSubmit }) => { + const [properties, setProperties] = useState([]); + const [products, setProducts] = useState([]); + const [equipment, setEquipment] = useState([]); + const [loading, setLoading] = useState(true); + + const [planData, setPlanData] = useState({ + propertyId: '', + selectedAreas: [], + productId: '', + applicationType: '', // 'liquid' or 'granular' + equipmentId: '', + notes: '' + }); + + useEffect(() => { + fetchPlanningData(); + }, []); + + const fetchPlanningData = async () => { + try { + setLoading(true); + const [propertiesResponse, productsResponse, equipmentResponse] = await Promise.all([ + propertiesAPI.getAll(), + productsAPI.getUserProducts(), + equipmentAPI.getAll() + ]); + + setProperties(propertiesResponse.data.data.properties || []); + setProducts(productsResponse.data.data.products || []); + setEquipment(equipmentResponse.data.data.equipment || []); + } catch (error) { + console.error('Failed to fetch planning data:', error); + toast.error('Failed to load planning data'); + } finally { + setLoading(false); + } + }; + + const selectedProperty = properties.find(p => p.id === parseInt(planData.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 */} + {selectedProperty && selectedProperty.sections && selectedProperty.sections.length > 0 && ( +
+ +
+ {selectedProperty.sections.map((section) => ( + + ))} +
+ {planData.selectedAreas.length > 0 && ( +

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

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

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

+ )} +
+ )} + + {/* Notes */} +
+ +