From ea81018f80e9d1af430945d7d59990a94d437f23 Mon Sep 17 00:00:00 2001 From: Jake Kasper Date: Fri, 22 Aug 2025 10:49:41 -0400 Subject: [PATCH] changey --- frontend/src/App.js | 11 - frontend/src/components/Layout/Layout.js | 8 - frontend/src/pages/Equipment/Equipment.js | 168 +++++++ frontend/src/pages/Nozzles/Nozzles.js | 515 ---------------------- 4 files changed, 168 insertions(+), 534 deletions(-) delete mode 100644 frontend/src/pages/Nozzles/Nozzles.js diff --git a/frontend/src/App.js b/frontend/src/App.js index cca3e2a..8355a8c 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -20,7 +20,6 @@ import Dashboard from './pages/Dashboard/Dashboard'; import Properties from './pages/Properties/Properties'; import PropertyDetail from './pages/Properties/PropertyDetail'; import Equipment from './pages/Equipment/Equipment'; -import Nozzles from './pages/Nozzles/Nozzles'; import Products from './pages/Products/Products'; import Applications from './pages/Applications/Applications'; import ApplicationPlan from './pages/Applications/ApplicationPlan'; @@ -180,16 +179,6 @@ function App() { } /> - - - - - - } - /> { icon: WrenchScrewdriverIcon, iconSolid: WrenchIconSolid, }, - { - name: 'Nozzles', - href: '/nozzles', - icon: SparklesIcon, - iconSolid: SparklesIconSolid, - }, { name: 'Products', href: '/products', diff --git a/frontend/src/pages/Equipment/Equipment.js b/frontend/src/pages/Equipment/Equipment.js index b662c94..ecac7f1 100644 --- a/frontend/src/pages/Equipment/Equipment.js +++ b/frontend/src/pages/Equipment/Equipment.js @@ -255,6 +255,17 @@ const Equipment = () => { {item.powerSource &&

Power: {item.powerSource}

} ); + case 'nozzle': + return ( + <> + {item.orificeSize &&

Orifice: {item.orificeSize}

} + {item.sprayAngle &&

Spray Angle: {item.sprayAngle}°

} + {item.flowRateGpm &&

Flow Rate: {item.flowRateGpm} GPM

} + {item.dropletSize &&

Droplet Size: {item.dropletSize}

} + {item.sprayPattern &&

Pattern: {item.sprayPattern.replace('_', ' ')}

} + {item.quantityOwned &&

Quantity: {item.quantityOwned}

} + + ); default: return ( <> @@ -270,6 +281,7 @@ const Equipment = () => { 'Mower': 'bg-green-100 text-green-800', 'Spreader': 'bg-orange-100 text-orange-800', 'Sprayer': 'bg-blue-100 text-blue-800', + 'Nozzle': 'bg-teal-100 text-teal-800', 'Pump': 'bg-purple-100 text-purple-800', 'Aerator': 'bg-yellow-100 text-yellow-800', 'Dethatcher': 'bg-red-100 text-red-800', @@ -505,6 +517,17 @@ const EquipmentFormModal = ({ isEdit, equipment, categories, equipmentTypes, onS maxGpm: equipment?.maxGpm || '', maxPsi: equipment?.maxPsi || '', powerSource: equipment?.powerSource || '', + // Nozzle fields + orificeSize: equipment?.orificeSize || '', + sprayAngle: equipment?.sprayAngle || '', + flowRateGpm: equipment?.flowRateGpm || '', + dropletSize: equipment?.dropletSize || '', + sprayPattern: equipment?.sprayPattern || '', + pressureRangePsi: equipment?.pressureRangePsi || '', + threadSize: equipment?.threadSize || '', + material: equipment?.material || '', + colorCode: equipment?.colorCode || '', + quantityOwned: equipment?.quantityOwned || 1, // General fields purchaseDate: equipment?.purchaseDate || '', purchasePrice: equipment?.purchasePrice || '', @@ -552,6 +575,17 @@ const EquipmentFormModal = ({ isEdit, equipment, categories, equipmentTypes, onS maxGpm: formData.maxGpm ? parseFloat(formData.maxGpm) : null, maxPsi: formData.maxPsi ? parseFloat(formData.maxPsi) : null, powerSource: formData.powerSource || null, + // Nozzle fields + orificeSize: formData.orificeSize || null, + sprayAngle: formData.sprayAngle ? parseInt(formData.sprayAngle) : null, + flowRateGpm: formData.flowRateGpm ? parseFloat(formData.flowRateGpm) : null, + dropletSize: formData.dropletSize || null, + sprayPattern: formData.sprayPattern || null, + pressureRangePsi: formData.pressureRangePsi || null, + threadSize: formData.threadSize || null, + material: formData.material || null, + colorCode: formData.colorCode || null, + quantityOwned: formData.quantityOwned ? parseInt(formData.quantityOwned) : null, purchaseDate: formData.purchaseDate || null, purchasePrice: formData.purchasePrice ? parseFloat(formData.purchasePrice) : null, notes: formData.notes || null, @@ -804,6 +838,140 @@ const EquipmentFormModal = ({ isEdit, equipment, categories, equipmentTypes, onS ); + case 'nozzle': + return ( + <> +
+
+ + setFormData({ ...formData, orificeSize: e.target.value })} + placeholder="02, 03, 04..." + required + /> +
+
+ + setFormData({ ...formData, sprayAngle: e.target.value })} + placeholder="80, 110..." + required + /> +
+
+ + setFormData({ ...formData, flowRateGpm: e.target.value })} + placeholder="0.20, 0.30..." + required + /> +
+
+
+
+ + +
+
+ + +
+
+
+
+ + setFormData({ ...formData, pressureRangePsi: e.target.value })} + placeholder="15-60" + /> +
+
+ + setFormData({ ...formData, threadSize: e.target.value })} + placeholder="1/4", 3/8"..." + /> +
+
+
+
+ + +
+
+ + setFormData({ ...formData, colorCode: e.target.value })} + placeholder="Yellow, Blue, Red..." + /> +
+
+ + setFormData({ ...formData, quantityOwned: e.target.value })} + placeholder="1" + /> +
+
+ + ); + default: return ( <> diff --git a/frontend/src/pages/Nozzles/Nozzles.js b/frontend/src/pages/Nozzles/Nozzles.js deleted file mode 100644 index 507f749..0000000 --- a/frontend/src/pages/Nozzles/Nozzles.js +++ /dev/null @@ -1,515 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - PlusIcon, - MagnifyingGlassIcon, - TrashIcon, - PencilIcon, - BeakerIcon -} from '@heroicons/react/24/outline'; -import { nozzlesAPI } from '../../services/api'; -import LoadingSpinner from '../../components/UI/LoadingSpinner'; -import toast from 'react-hot-toast'; - -const Nozzles = () => { - const [userNozzles, setUserNozzles] = useState([]); - const [nozzleTypes, setNozzleTypes] = useState([]); - const [loading, setLoading] = useState(true); - const [showCreateForm, setShowCreateForm] = useState(false); - const [showEditForm, setShowEditForm] = useState(false); - const [editingNozzle, setEditingNozzle] = useState(null); - const [searchTerm, setSearchTerm] = useState(''); - const [selectedManufacturer, setSelectedManufacturer] = useState(''); - const [selectedDropletSize, setSelectedDropletSize] = useState(''); - - useEffect(() => { - fetchData(); - }, []); - - const fetchData = async () => { - try { - setLoading(true); - const [userNozzlesResponse, nozzleTypesResponse] = await Promise.all([ - nozzlesAPI.getUserNozzles(), - nozzlesAPI.getNozzleTypes() - ]); - - setUserNozzles(userNozzlesResponse.data.data.userNozzles || []); - setNozzleTypes(nozzleTypesResponse.data.data.nozzleTypes || []); - } catch (error) { - console.error('Failed to fetch nozzles:', error); - toast.error('Failed to load nozzles'); - setUserNozzles([]); - setNozzleTypes([]); - } finally { - setLoading(false); - } - }; - - const handleCreateNozzle = async (nozzleData) => { - try { - await nozzlesAPI.create(nozzleData); - toast.success('Nozzle added successfully!'); - setShowCreateForm(false); - fetchData(); - } catch (error) { - console.error('Failed to create nozzle:', error); - toast.error('Failed to add nozzle'); - } - }; - - const handleEditNozzle = (nozzle) => { - setEditingNozzle(nozzle); - setShowEditForm(true); - }; - - const handleUpdateNozzle = async (nozzleData) => { - try { - await nozzlesAPI.update(editingNozzle.id, nozzleData); - toast.success('Nozzle updated successfully!'); - setShowEditForm(false); - setEditingNozzle(null); - fetchData(); - } catch (error) { - console.error('Failed to update nozzle:', error); - toast.error('Failed to update nozzle'); - } - }; - - const handleDeleteNozzle = async (nozzleId) => { - if (window.confirm('Are you sure you want to delete this nozzle?')) { - try { - await nozzlesAPI.delete(nozzleId); - toast.success('Nozzle deleted successfully'); - fetchData(); - } catch (error) { - console.error('Failed to delete nozzle:', error); - toast.error('Failed to delete nozzle'); - } - } - }; - - // Filter nozzles based on search and filters - const filteredNozzles = userNozzles.filter(nozzle => { - const matchesSearch = searchTerm === '' || - nozzle.nozzleType?.name?.toLowerCase().includes(searchTerm.toLowerCase()) || - nozzle.customName?.toLowerCase().includes(searchTerm.toLowerCase()) || - nozzle.nozzleType?.manufacturer?.toLowerCase().includes(searchTerm.toLowerCase()); - - const matchesManufacturer = selectedManufacturer === '' || - nozzle.nozzleType?.manufacturer === selectedManufacturer; - - const matchesDropletSize = selectedDropletSize === '' || - nozzle.nozzleType?.dropletSize === selectedDropletSize; - - return matchesSearch && matchesManufacturer && matchesDropletSize; - }); - - // Get unique manufacturers and droplet sizes for filters - const manufacturers = [...new Set(nozzleTypes.map(type => type.manufacturer).filter(Boolean))]; - const dropletSizes = [...new Set(nozzleTypes.map(type => type.dropletSize).filter(Boolean))]; - - const NozzleCard = ({ nozzle }) => ( -
-
-
-
- -
-
-

- {nozzle.customName || nozzle.nozzleType?.name} -

- {nozzle.nozzleType?.manufacturer && ( -

{nozzle.nozzleType.manufacturer} {nozzle.nozzleType.model}

- )} -
- - Qty: {nozzle.quantity} - - - {nozzle.condition} - -
-
-
- -
- - -
-
- - {/* Nozzle Specifications */} -
- {nozzle.nozzleType?.orificeSize && ( -
Orifice: {nozzle.nozzleType.orificeSize}
- )} - {nozzle.nozzleType?.sprayAngle && ( -
Spray Angle: {nozzle.nozzleType.sprayAngle}°
- )} - {nozzle.nozzleType?.dropletSize && ( -
Droplet Size: {nozzle.nozzleType.dropletSize}
- )} - {nozzle.nozzleType?.sprayPattern && ( -
Pattern: {nozzle.nozzleType.sprayPattern.replace('_', ' ')}
- )} - {nozzle.nozzleType?.flowRateGpm && ( -
Flow Rate: {nozzle.nozzleType.flowRateGpm} GPM @ rated PSI
- )} - {nozzle.nozzleType?.pressureRangePsi && ( -
Pressure Range: {nozzle.nozzleType.pressureRangePsi} PSI
- )} -
- - {nozzle.notes && ( -

- Notes: {nozzle.notes} -

- )} - - {nozzle.purchaseDate && ( -

- Purchased: {new Date(nozzle.purchaseDate).toLocaleDateString()} -

- )} -
- ); - - const getConditionColor = (condition) => { - const colors = { - 'excellent': 'bg-green-100 text-green-800', - 'good': 'bg-blue-100 text-blue-800', - 'fair': 'bg-yellow-100 text-yellow-800', - 'poor': 'bg-orange-100 text-orange-800', - 'needs_replacement': 'bg-red-100 text-red-800', - }; - return colors[condition] || 'bg-gray-100 text-gray-800'; - }; - - if (loading) { - return ( -
-
- -
-
- ); - } - - return ( -
-
-
-

Nozzles

-

Manage your spray nozzle inventory with specifications

-
- -
- - {/* Search and Filters */} -
-
- {/* Search */} -
-
- - setSearchTerm(e.target.value)} - /> -
-
- - {/* Manufacturer Filter */} -
- -
- - {/* Droplet Size Filter */} -
- -
-
-
- - {/* Nozzles Grid */} - {filteredNozzles.length === 0 ? ( -
- -

No Nozzles Found

-

- {searchTerm || selectedManufacturer || selectedDropletSize - ? 'Try adjusting your search or filters' - : 'Start building your nozzle inventory' - } -

- {!searchTerm && !selectedManufacturer && !selectedDropletSize && ( - - )} -
- ) : ( -
- {filteredNozzles.map((nozzle) => ( - - ))} -
- )} - - {/* Create Nozzle Form Modal */} - {showCreateForm && ( - setShowCreateForm(false)} - /> - )} - - {/* Edit Nozzle Form Modal */} - {showEditForm && editingNozzle && ( - { - setShowEditForm(false); - setEditingNozzle(null); - }} - /> - )} -
- ); -}; - -// Nozzle Form Modal Component -const NozzleFormModal = ({ isEdit, nozzle, nozzleTypes, onSubmit, onCancel }) => { - const [formData, setFormData] = useState({ - nozzleTypeId: nozzle?.nozzleTypeId || '', - customName: nozzle?.customName || '', - quantity: nozzle?.quantity || 1, - condition: nozzle?.condition || 'good', - purchaseDate: nozzle?.purchaseDate || '', - notes: nozzle?.notes || '' - }); - - const [selectedNozzleType, setSelectedNozzleType] = useState( - nozzle?.nozzleType || null - ); - - useEffect(() => { - if (formData.nozzleTypeId) { - const nozzleType = nozzleTypes.find(type => type.id === parseInt(formData.nozzleTypeId)); - setSelectedNozzleType(nozzleType); - } - }, [formData.nozzleTypeId, nozzleTypes]); - - const handleSubmit = (e) => { - e.preventDefault(); - - if (!formData.nozzleTypeId) { - toast.error('Please select a nozzle type'); - return; - } - - const submitData = { - nozzleTypeId: parseInt(formData.nozzleTypeId), - customName: formData.customName || null, - quantity: parseInt(formData.quantity), - condition: formData.condition, - purchaseDate: formData.purchaseDate || null, - notes: formData.notes || null - }; - - onSubmit(submitData); - }; - - // Group nozzle types by manufacturer and droplet size for better organization - const groupedNozzleTypes = nozzleTypes.reduce((groups, type) => { - const key = `${type.manufacturer || 'Other'} - ${type.dropletSize || 'Unknown'}`; - if (!groups[key]) { - groups[key] = []; - } - groups[key].push(type); - return groups; - }, {}); - - return ( -
-
-

- {isEdit ? 'Edit Nozzle' : 'Add New Nozzle'} -

- -
- {/* Nozzle Type Selection */} -
- - -
- - {/* Nozzle Specifications Preview */} - {selectedNozzleType && ( -
-

Nozzle Specifications

-
-
Manufacturer: {selectedNozzleType.manufacturer}
-
Model: {selectedNozzleType.model}
-
Orifice Size: {selectedNozzleType.orificeSize}
-
Spray Angle: {selectedNozzleType.sprayAngle}°
-
Flow Rate: {selectedNozzleType.flowRateGpm} GPM
-
Droplet Size: {selectedNozzleType.dropletSize}
-
Spray Pattern: {selectedNozzleType.sprayPattern?.replace('_', ' ')}
-
Pressure Range: {selectedNozzleType.pressureRangePsi} PSI
-
-
- )} - - {/* Custom Name */} -
- - setFormData({ ...formData, customName: e.target.value })} - placeholder="e.g., Backyard Sprayer Nozzles" - /> -
- - {/* Quantity and Condition */} -
-
- - setFormData({ ...formData, quantity: e.target.value })} - required - /> -
-
- - -
-
- - {/* Purchase Date */} -
- - setFormData({ ...formData, purchaseDate: e.target.value })} - /> -
- - {/* Notes */} -
- -