import React, { useState, useEffect } from 'react';
import {
PlusIcon,
MagnifyingGlassIcon,
FunnelIcon,
WrenchScrewdriverIcon,
TrashIcon,
PencilIcon,
EyeIcon
} from '@heroicons/react/24/outline';
import { equipmentAPI, nozzlesAPI } from '../../services/api';
import LoadingSpinner from '../../components/UI/LoadingSpinner';
import toast from 'react-hot-toast';
const Equipment = () => {
const [equipment, setEquipment] = useState([]);
const [categories, setCategories] = useState([]);
const [equipmentTypes, setEquipmentTypes] = useState([]);
const [loading, setLoading] = useState(true);
const [showCreateForm, setShowCreateForm] = useState(false);
const [showEditForm, setShowEditForm] = useState(false);
const [editingEquipment, setEditingEquipment] = useState(null);
const [selectedCategory, setSelectedCategory] = useState('');
const [searchTerm, setSearchTerm] = useState('');
const [showInactive, setShowInactive] = useState(false);
const [activeTab, setActiveTab] = useState('all');
const [showPumpAssignments, setShowPumpAssignments] = useState(false);
const [showNozzleConfigs, setShowNozzleConfigs] = useState(false);
const [selectedSprayerForPump, setSelectedSprayerForPump] = useState(null);
const [selectedSprayerForNozzles, setSelectedSprayerForNozzles] = useState(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
const [equipmentResponse, categoriesResponse, typesResponse] = await Promise.all([
equipmentAPI.getAll({
category_id: selectedCategory,
is_active: !showInactive
}),
equipmentAPI.getCategories(),
equipmentAPI.getTypes()
]);
setEquipment(equipmentResponse.data.data.equipment || []);
setCategories(categoriesResponse.data.data.categories || []);
setEquipmentTypes(typesResponse.data.data.equipmentTypes || []);
} catch (error) {
console.error('Failed to fetch equipment:', error);
toast.error('Failed to load equipment');
setEquipment([]);
setCategories([]);
setEquipmentTypes([]);
} finally {
setLoading(false);
}
};
const handleSearch = (e) => {
setSearchTerm(e.target.value);
};
const handleFilterChange = () => {
fetchData();
};
const handleCreateEquipment = async (equipmentData) => {
try {
await equipmentAPI.create(equipmentData);
toast.success('Equipment created successfully!');
setShowCreateForm(false);
fetchData();
} catch (error) {
console.error('Failed to create equipment:', error);
toast.error('Failed to create equipment');
}
};
const handleEditEquipment = (equipment) => {
setEditingEquipment(equipment);
setShowEditForm(true);
};
const handleUpdateEquipment = async (equipmentData) => {
try {
await equipmentAPI.update(editingEquipment.id, equipmentData);
toast.success('Equipment updated successfully!');
setShowEditForm(false);
setEditingEquipment(null);
fetchData();
} catch (error) {
console.error('Failed to update equipment:', error);
toast.error('Failed to update equipment');
}
};
const handleDeleteEquipment = async (equipmentId) => {
if (window.confirm('Are you sure you want to delete this equipment?')) {
try {
await equipmentAPI.delete(equipmentId);
toast.success('Equipment deleted successfully');
fetchData();
} catch (error) {
console.error('Failed to delete equipment:', error);
toast.error('Failed to delete equipment');
}
}
};
const handleManagePumpAssignments = (sprayer) => {
setSelectedSprayerForPump(sprayer);
setShowPumpAssignments(true);
};
const handleManageNozzleConfigs = (sprayer) => {
setSelectedSprayerForNozzles(sprayer);
setShowNozzleConfigs(true);
};
// Filter equipment based on search term and active tab
const filteredEquipment = equipment.filter(item => {
const matchesSearch = searchTerm === '' ||
item.customName?.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.typeName?.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.manufacturer?.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.model?.toLowerCase().includes(searchTerm.toLowerCase());
if (activeTab === 'all') return matchesSearch;
return matchesSearch && item.categoryName?.toLowerCase() === activeTab.toLowerCase();
});
// Get unique category names for tabs
const categoryTabs = ['all', ...new Set(equipment.map(item => item.categoryName).filter(Boolean))];
const EquipmentCard = ({ item }) => (
{item.customName || item.typeName}
{item.manufacturer && item.model && (
{item.manufacturer} {item.model}
)}
{item.categoryName}
{!item.isActive && (
Inactive
)}
{item.categoryName === 'Sprayer' && (
<>
>
)}
{/* Equipment-specific details */}
{renderEquipmentDetails(item)}
{item.notes && (
Notes: {item.notes}
)}
{item.purchaseDate && (
Purchased: {new Date(item.purchaseDate).toLocaleDateString()}
)}
);
const renderEquipmentDetails = (item) => {
switch (item.categoryName?.toLowerCase()) {
case 'mower':
return (
<>
{item.mowerStyle && Style: {item.mowerStyle.replace('_', ' ')}
}
{item.cuttingWidthInches && Cutting Width: {item.cuttingWidthInches}"
}
{item.engineHp && Engine: {item.engineHp} HP
}
{item.fuelType && Fuel: {item.fuelType}
}
>
);
case 'spreader':
return (
<>
{item.spreaderType && Type: {item.spreaderType.replace('_', ' ')}
}
{item.capacityLbs && Capacity: {item.capacityLbs} lbs
}
{item.spreadWidth && Spread Width: {item.spreadWidth} ft
}
>
);
case 'sprayer':
return (
<>
{item.sprayerType && Type: {item.sprayerType.replace('_', ' ')}
}
{item.tankSizeGallons && Tank Size: {item.tankSizeGallons} gal
}
{item.sprayWidthFeet && Spray Width: {item.sprayWidthFeet} ft
}
{item.pumpGpm && Pump: {item.pumpGpm} GPM
}
{item.boomSections && Boom Sections: {item.boomSections}
}
>
);
case 'pump':
return (
<>
{item.pumpType && Type: {item.pumpType}
}
{item.maxGpm && Max Flow: {item.maxGpm} GPM
}
{item.maxPsi && Max Pressure: {item.maxPsi} PSI
}
{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 (
<>
{item.toolType && Type: {item.toolType.replace('_', ' ')}
}
{item.workingWidthInches && Working Width: {item.workingWidthInches}"
}
>
);
}
};
const getCategoryColor = (categoryName) => {
const colors = {
'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',
'Scarifier': 'bg-pink-100 text-pink-800',
'Trimmer': 'bg-indigo-100 text-indigo-800',
};
return colors[categoryName] || 'bg-gray-100 text-gray-800';
};
if (loading) {
return (
);
}
return (
Equipment
Manage your lawn care equipment inventory
{/* Search and Filters */}
{/* Search */}
{/* Category Filter */}
{/* Show Inactive Toggle */}
{/* Category Tabs */}
{/* Equipment Grid */}
{filteredEquipment.length === 0 ? (
No Equipment Found
{searchTerm || selectedCategory
? 'Try adjusting your search or filters'
: 'Start building your equipment inventory'
}
{!searchTerm && !selectedCategory && (
)}
) : (
{filteredEquipment.map((item) => (
))}
)}
{/* Create Equipment Form Modal */}
{showCreateForm && (
setShowCreateForm(false)}
/>
)}
{/* Edit Equipment Form Modal */}
{showEditForm && editingEquipment && (
{
setShowEditForm(false);
setEditingEquipment(null);
}}
/>
)}
{/* Pump Assignment Modal */}
{showPumpAssignments && selectedSprayerForPump && (
e.categoryName === 'Pump')}
onClose={() => {
setShowPumpAssignments(false);
setSelectedSprayerForPump(null);
fetchData();
}}
/>
)}
{/* Nozzle Configuration Modal */}
{showNozzleConfigs && selectedSprayerForNozzles && (
{
setShowNozzleConfigs(false);
setSelectedSprayerForNozzles(null);
fetchData();
}}
/>
)}
);
};
// Equipment Form Modal Component (Create/Edit)
const EquipmentFormModal = ({ isEdit, equipment, categories, equipmentTypes, onSubmit, onCancel }) => {
const [formData, setFormData] = useState({
equipmentTypeId: equipment?.equipmentTypeId || '',
categoryId: equipment?.categoryId || '',
customName: equipment?.customName || '',
manufacturer: equipment?.manufacturer || '',
model: equipment?.model || '',
// Spreader fields
capacityLbs: equipment?.capacityLbs || '',
spreaderType: equipment?.spreaderType || 'walk_behind',
spreadWidth: equipment?.spreadWidth || '',
// Sprayer fields
tankSizeGallons: equipment?.tankSizeGallons || '',
sprayerType: equipment?.sprayerType || 'walk_behind',
sprayWidthFeet: equipment?.sprayWidthFeet || '',
pumpGpm: equipment?.pumpGpm || '',
pumpPsi: equipment?.pumpPsi || '',
boomSections: equipment?.boomSections || '',
// Mower fields
mowerStyle: equipment?.mowerStyle || 'push',
cuttingWidthInches: equipment?.cuttingWidthInches || '',
engineHp: equipment?.engineHp || '',
fuelType: equipment?.fuelType || '',
// Tool fields
toolType: equipment?.toolType || 'walk_behind',
workingWidthInches: equipment?.workingWidthInches || '',
// Pump fields
pumpType: equipment?.pumpType || '',
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 || '',
notes: equipment?.notes || '',
isActive: equipment?.isActive !== undefined ? equipment.isActive : true
});
const selectedCategory = categories.find(cat => cat.id === parseInt(formData.categoryId));
const handleSubmit = (e) => {
e.preventDefault();
if (!formData.categoryId && !formData.equipmentTypeId) {
toast.error('Please select a category or equipment type');
return;
}
if (!formData.customName && !formData.equipmentTypeId) {
toast.error('Please enter a name or select an equipment type');
return;
}
const submitData = {
equipmentTypeId: formData.equipmentTypeId ? parseInt(formData.equipmentTypeId) : null,
categoryId: formData.categoryId ? parseInt(formData.categoryId) : null,
customName: formData.customName || null,
manufacturer: formData.manufacturer || null,
model: formData.model || null,
capacityLbs: formData.capacityLbs ? parseFloat(formData.capacityLbs) : null,
spreaderType: formData.spreaderType || null,
spreadWidth: formData.spreadWidth ? parseFloat(formData.spreadWidth) : null,
tankSizeGallons: formData.tankSizeGallons ? parseFloat(formData.tankSizeGallons) : null,
sprayerType: formData.sprayerType || null,
sprayWidthFeet: formData.sprayWidthFeet ? parseFloat(formData.sprayWidthFeet) : null,
pumpGpm: formData.pumpGpm ? parseFloat(formData.pumpGpm) : null,
pumpPsi: formData.pumpPsi ? parseFloat(formData.pumpPsi) : null,
boomSections: formData.boomSections ? parseInt(formData.boomSections) : null,
mowerStyle: formData.mowerStyle || null,
cuttingWidthInches: formData.cuttingWidthInches ? parseFloat(formData.cuttingWidthInches) : null,
engineHp: formData.engineHp ? parseFloat(formData.engineHp) : null,
fuelType: formData.fuelType || null,
toolType: formData.toolType || null,
workingWidthInches: formData.workingWidthInches ? parseFloat(formData.workingWidthInches) : null,
pumpType: formData.pumpType || null,
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,
isActive: formData.isActive
};
onSubmit(submitData);
};
const renderCategorySpecificFields = () => {
const categoryName = selectedCategory?.name?.toLowerCase();
switch (categoryName) {
case 'mower':
return (
<>
>
);
case 'spreader':
return (
<>
setFormData({ ...formData, spreadWidth: e.target.value })}
placeholder="8"
/>
>
);
case 'sprayer':
return (
<>
setFormData({ ...formData, boomSections: e.target.value })}
placeholder="3"
/>
>
);
case 'pump':
return (
<>
>
);
case 'nozzle':
return (
<>
>
);
default:
return (
<>
>
);
}
};
return (
{isEdit ? 'Edit Equipment' : 'Add New Equipment'}
);
};
// Pump Assignment Modal Component
const PumpAssignmentModal = ({ sprayer, equipment, onClose }) => {
const [assignments, setAssignments] = useState([]);
const [availablePumps, setAvailablePumps] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedPump, setSelectedPump] = useState('');
useEffect(() => {
fetchPumpAssignments();
}, [sprayer.id]);
const fetchPumpAssignments = async () => {
try {
setLoading(true);
const [assignmentsResponse, availablePumpsResponse] = await Promise.all([
nozzlesAPI.getPumpAssignments(sprayer.id),
Promise.resolve({ data: { data: { pumps: equipment } } })
]);
setAssignments(assignmentsResponse.data.data.assignments || []);
setAvailablePumps(availablePumpsResponse.data.data.pumps || []);
} catch (error) {
console.error('Failed to fetch pump assignments:', error);
toast.error('Failed to load pump assignments');
} finally {
setLoading(false);
}
};
const handleAssignPump = async () => {
if (!selectedPump) return;
try {
await nozzlesAPI.assignPump(sprayer.id, selectedPump);
toast.success('Pump assigned successfully');
setSelectedPump('');
fetchPumpAssignments();
} catch (error) {
console.error('Failed to assign pump:', error);
toast.error('Failed to assign pump');
}
};
const handleUnassignPump = async (assignmentId) => {
try {
await nozzlesAPI.unassignPump(assignmentId);
toast.success('Pump unassigned successfully');
fetchPumpAssignments();
} catch (error) {
console.error('Failed to unassign pump:', error);
toast.error('Failed to unassign pump');
}
};
return (
Pump Assignments - {sprayer.customName || sprayer.typeName}
{loading ? (
) : (
<>
{/* Add New Pump Assignment */}
Assign New Pump
{/* Current Assignments */}
Current Pump Assignments
{assignments.length === 0 ? (
No pumps assigned to this sprayer
) : (
assignments.map(assignment => (
{assignment.pump?.customName || assignment.pump?.typeName}
{assignment.pump?.manufacturer && (
{assignment.pump.manufacturer} {assignment.pump.model}
)}
{assignment.pump?.maxGpm && (
Max Flow: {assignment.pump.maxGpm} GPM
)}
Assigned: {new Date(assignment.assignedDate).toLocaleDateString()}
))
)}
>
)}
);
};
// Nozzle Configuration Modal Component
const NozzleConfigurationModal = ({ sprayer, onClose }) => {
const [configurations, setConfigurations] = useState([]);
const [nozzleTypes, setNozzleTypes] = useState([]);
const [userNozzles, setUserNozzles] = useState([]);
const [loading, setLoading] = useState(true);
const [showAddConfig, setShowAddConfig] = useState(false);
const [configForm, setConfigForm] = useState({
userNozzleId: '',
position: '',
quantityAssigned: 1
});
useEffect(() => {
fetchNozzleConfigs();
}, [sprayer.id]);
const fetchNozzleConfigs = async () => {
try {
setLoading(true);
const [configsResponse, typesResponse, userNozzlesResponse] = await Promise.all([
nozzlesAPI.getNozzleConfigurations(sprayer.id),
nozzlesAPI.getNozzleTypes(),
nozzlesAPI.getUserNozzles()
]);
setConfigurations(configsResponse.data.data.configurations || []);
setNozzleTypes(typesResponse.data.data.nozzleTypes || []);
setUserNozzles(userNozzlesResponse.data.data.userNozzles || []);
} catch (error) {
console.error('Failed to fetch nozzle configurations:', error);
toast.error('Failed to load nozzle configurations');
} finally {
setLoading(false);
}
};
const handleAddConfiguration = async () => {
try {
await nozzlesAPI.addNozzleConfiguration(sprayer.id, configForm);
toast.success('Nozzle configuration added');
setShowAddConfig(false);
setConfigForm({ userNozzleId: '', position: '', quantityAssigned: 1 });
fetchNozzleConfigs();
} catch (error) {
console.error('Failed to add configuration:', error);
toast.error('Failed to add configuration');
}
};
const handleRemoveConfiguration = async (configId) => {
try {
await nozzlesAPI.removeNozzleConfiguration(configId);
toast.success('Nozzle configuration removed');
fetchNozzleConfigs();
} catch (error) {
console.error('Failed to remove configuration:', error);
toast.error('Failed to remove configuration');
}
};
return (
Nozzle Configuration - {sprayer.customName || sprayer.typeName}
{loading ? (
) : (
<>
{/* Add Configuration Button */}
{/* Add Configuration Form */}
{showAddConfig && (
)}
{/* Current Configurations */}
Current Nozzle Configurations
{configurations.length === 0 ? (
No nozzles configured for this sprayer
) : (
{configurations.map(config => (
{config.userNozzle?.nozzleType?.name}
Position: {config.position}
Quantity: {config.quantityAssigned}
{config.userNozzle?.nozzleType?.manufacturer && (
Manufacturer: {config.userNozzle.nozzleType.manufacturer}
)}
{config.userNozzle?.nozzleType?.orificeSize && (
Orifice: {config.userNozzle.nozzleType.orificeSize}
)}
{config.userNozzle?.nozzleType?.sprayAngle && (
Spray Angle: {config.userNozzle.nozzleType.sprayAngle}°
)}
{config.userNozzle?.nozzleType?.dropletSize && (
Droplet Size: {config.userNozzle.nozzleType.dropletSize}
)}
Assigned: {new Date(config.assignedDate).toLocaleDateString()}
))}
)}
>
)}
);
};
export default Equipment;