update equipment stuff
This commit is contained in:
@@ -24,6 +24,10 @@ const Equipment = () => {
|
||||
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();
|
||||
@@ -106,6 +110,16 @@ const 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 === '' ||
|
||||
@@ -149,6 +163,24 @@ const Equipment = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1">
|
||||
{item.categoryName === 'Sprayer' && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => handleManagePumpAssignments(item)}
|
||||
className="p-1 text-gray-400 hover:text-purple-600"
|
||||
title="Manage pump assignments"
|
||||
>
|
||||
<WrenchScrewdriverIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleManageNozzleConfigs(item)}
|
||||
className="p-1 text-gray-400 hover:text-green-600"
|
||||
title="Configure nozzles"
|
||||
>
|
||||
<EyeIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleEditEquipment(item)}
|
||||
className="p-1 text-gray-400 hover:text-blue-600"
|
||||
@@ -412,6 +444,31 @@ const Equipment = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Pump Assignment Modal */}
|
||||
{showPumpAssignments && selectedSprayerForPump && (
|
||||
<PumpAssignmentModal
|
||||
sprayer={selectedSprayerForPump}
|
||||
equipment={equipment.filter(e => e.categoryName === 'Pump')}
|
||||
onClose={() => {
|
||||
setShowPumpAssignments(false);
|
||||
setSelectedSprayerForPump(null);
|
||||
fetchData();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Nozzle Configuration Modal */}
|
||||
{showNozzleConfigs && selectedSprayerForNozzles && (
|
||||
<NozzleConfigurationModal
|
||||
sprayer={selectedSprayerForNozzles}
|
||||
onClose={() => {
|
||||
setShowNozzleConfigs(false);
|
||||
setSelectedSprayerForNozzles(null);
|
||||
fetchData();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -936,4 +993,333 @@ const EquipmentFormModal = ({ isEdit, equipment, categories, equipmentTypes, onS
|
||||
);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
Pump Assignments - {sprayer.customName || sprayer.typeName}
|
||||
</h3>
|
||||
|
||||
{loading ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<>
|
||||
{/* Add New Pump Assignment */}
|
||||
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<h4 className="font-medium mb-3">Assign New Pump</h4>
|
||||
<div className="flex gap-3">
|
||||
<select
|
||||
className="input flex-1"
|
||||
value={selectedPump}
|
||||
onChange={(e) => setSelectedPump(e.target.value)}
|
||||
>
|
||||
<option value="">Select a pump...</option>
|
||||
{availablePumps.map(pump => (
|
||||
<option key={pump.id} value={pump.id}>
|
||||
{pump.customName || pump.typeName}
|
||||
{pump.manufacturer && ` - ${pump.manufacturer}`}
|
||||
{pump.maxGpm && ` (${pump.maxGpm} GPM)`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={handleAssignPump}
|
||||
disabled={!selectedPump}
|
||||
className="btn-primary"
|
||||
>
|
||||
Assign
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current Assignments */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="font-medium">Current Pump Assignments</h4>
|
||||
{assignments.length === 0 ? (
|
||||
<p className="text-gray-500">No pumps assigned to this sprayer</p>
|
||||
) : (
|
||||
assignments.map(assignment => (
|
||||
<div key={assignment.id} className="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{assignment.pump?.customName || assignment.pump?.typeName}
|
||||
</div>
|
||||
{assignment.pump?.manufacturer && (
|
||||
<div className="text-sm text-gray-600">
|
||||
{assignment.pump.manufacturer} {assignment.pump.model}
|
||||
</div>
|
||||
)}
|
||||
{assignment.pump?.maxGpm && (
|
||||
<div className="text-sm text-gray-600">
|
||||
Max Flow: {assignment.pump.maxGpm} GPM
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs text-gray-500">
|
||||
Assigned: {new Date(assignment.assignedDate).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleUnassignPump(assignment.id)}
|
||||
className="text-red-600 hover:text-red-800"
|
||||
>
|
||||
<TrashIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 pt-6 mt-6 border-t">
|
||||
<button onClick={onClose} className="btn-secondary flex-1">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
Nozzle Configuration - {sprayer.customName || sprayer.typeName}
|
||||
</h3>
|
||||
|
||||
{loading ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<>
|
||||
{/* Add Configuration Button */}
|
||||
<div className="mb-6">
|
||||
<button
|
||||
onClick={() => setShowAddConfig(true)}
|
||||
className="btn-primary flex items-center gap-2"
|
||||
>
|
||||
<PlusIcon className="h-5 w-5" />
|
||||
Add Nozzle Configuration
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Add Configuration Form */}
|
||||
{showAddConfig && (
|
||||
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<h4 className="font-medium mb-3">Add Nozzle Configuration</h4>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<select
|
||||
className="input"
|
||||
value={configForm.userNozzleId}
|
||||
onChange={(e) => setConfigForm({ ...configForm, userNozzleId: e.target.value })}
|
||||
>
|
||||
<option value="">Select nozzle...</option>
|
||||
{userNozzles.map(nozzle => (
|
||||
<option key={nozzle.id} value={nozzle.id}>
|
||||
{nozzle.nozzleType?.name} ({nozzle.quantity} available)
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
placeholder="Position (e.g., left, right, boom_1)"
|
||||
value={configForm.position}
|
||||
onChange={(e) => setConfigForm({ ...configForm, position: e.target.value })}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
className="input"
|
||||
placeholder="Quantity"
|
||||
min="1"
|
||||
value={configForm.quantityAssigned}
|
||||
onChange={(e) => setConfigForm({ ...configForm, quantityAssigned: parseInt(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-3 mt-3">
|
||||
<button onClick={handleAddConfiguration} className="btn-primary">
|
||||
Add Configuration
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowAddConfig(false)}
|
||||
className="btn-secondary"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Current Configurations */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="font-medium">Current Nozzle Configurations</h4>
|
||||
{configurations.length === 0 ? (
|
||||
<p className="text-gray-500">No nozzles configured for this sprayer</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{configurations.map(config => (
|
||||
<div key={config.id} className="p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<div className="font-medium">
|
||||
{config.userNozzle?.nozzleType?.name}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleRemoveConfiguration(config.id)}
|
||||
className="text-red-600 hover:text-red-800"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 space-y-1">
|
||||
<div><strong>Position:</strong> {config.position}</div>
|
||||
<div><strong>Quantity:</strong> {config.quantityAssigned}</div>
|
||||
{config.userNozzle?.nozzleType?.manufacturer && (
|
||||
<div><strong>Manufacturer:</strong> {config.userNozzle.nozzleType.manufacturer}</div>
|
||||
)}
|
||||
{config.userNozzle?.nozzleType?.orificeSize && (
|
||||
<div><strong>Orifice:</strong> {config.userNozzle.nozzleType.orificeSize}</div>
|
||||
)}
|
||||
{config.userNozzle?.nozzleType?.sprayAngle && (
|
||||
<div><strong>Spray Angle:</strong> {config.userNozzle.nozzleType.sprayAngle}°</div>
|
||||
)}
|
||||
{config.userNozzle?.nozzleType?.dropletSize && (
|
||||
<div><strong>Droplet Size:</strong> {config.userNozzle.nozzleType.dropletSize}</div>
|
||||
)}
|
||||
<div className="text-xs text-gray-500">
|
||||
Assigned: {new Date(config.assignedDate).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 pt-6 mt-6 border-t">
|
||||
<button onClick={onClose} className="btn-secondary flex-1">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Equipment;
|
||||
@@ -109,8 +109,22 @@ export const nozzlesAPI = {
|
||||
create: (nozzleData) => apiClient.post('/nozzles', nozzleData),
|
||||
update: (id, nozzleData) => apiClient.put(`/nozzles/${id}`, nozzleData),
|
||||
delete: (id) => apiClient.delete(`/nozzles/${id}`),
|
||||
getNozzleTypes: () => apiClient.get('/nozzles/types'),
|
||||
getUserNozzles: () => apiClient.get('/nozzles/user'),
|
||||
calculateFlowRate: (nozzleId, pressure) => apiClient.get(`/nozzles/${nozzleId}/flow-rate`, { params: { pressure } }),
|
||||
|
||||
// Pump assignments
|
||||
getPumpAssignments: (sprayerId) => apiClient.get(`/nozzles/sprayer/${sprayerId}/pump-assignments`),
|
||||
assignPump: (sprayerId, pumpId) => apiClient.post(`/nozzles/sprayer/${sprayerId}/pump-assignments`, { pumpId }),
|
||||
unassignPump: (assignmentId) => apiClient.delete(`/nozzles/pump-assignments/${assignmentId}`),
|
||||
|
||||
// Nozzle configurations
|
||||
getNozzleConfigurations: (sprayerId) => apiClient.get(`/nozzles/sprayer/${sprayerId}/nozzle-configurations`),
|
||||
addNozzleConfiguration: (sprayerId, configData) => apiClient.post(`/nozzles/sprayer/${sprayerId}/nozzle-configurations`, configData),
|
||||
removeNozzleConfiguration: (configId) => apiClient.delete(`/nozzles/nozzle-configurations/${configId}`),
|
||||
|
||||
// Legacy endpoints for compatibility
|
||||
getTypes: (params) => apiClient.get('/nozzles/types', { params }),
|
||||
// Equipment-nozzle assignments
|
||||
getAssignments: (equipmentId) => apiClient.get(`/nozzles/equipment/${equipmentId}/assignments`),
|
||||
assignToEquipment: (equipmentId, assignmentData) => apiClient.post(`/nozzles/equipment/${equipmentId}/assignments`, assignmentData),
|
||||
removeAssignment: (assignmentId) => apiClient.delete(`/nozzles/assignments/${assignmentId}`),
|
||||
|
||||
Reference in New Issue
Block a user