equipment stuff

This commit is contained in:
Jake Kasper
2025-08-22 09:17:22 -04:00
parent f95422325c
commit b7ceed70a5
7 changed files with 2137 additions and 260 deletions

View File

@@ -1,11 +1,936 @@
import React from 'react';
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');
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');
}
}
};
// 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 }) => (
<div className="card">
<div className="flex justify-between items-start mb-3">
<div className="flex items-start gap-3">
<div className="p-2 bg-blue-100 rounded-lg">
<WrenchScrewdriverIcon className="h-5 w-5 text-blue-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900">
{item.customName || item.typeName}
</h3>
{item.manufacturer && item.model && (
<p className="text-sm text-gray-600">{item.manufacturer} {item.model}</p>
)}
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium mt-1 ${
getCategoryColor(item.categoryName)
}`}>
{item.categoryName}
</span>
{!item.isActive && (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium mt-1 ml-2 bg-red-100 text-red-800">
Inactive
</span>
)}
</div>
</div>
<div className="flex gap-1">
<button
onClick={() => handleEditEquipment(item)}
className="p-1 text-gray-400 hover:text-blue-600"
title="Edit equipment"
>
<PencilIcon className="h-4 w-4" />
</button>
<button
onClick={() => handleDeleteEquipment(item.id)}
className="p-1 text-gray-400 hover:text-red-600"
title="Delete equipment"
>
<TrashIcon className="h-4 w-4" />
</button>
</div>
</div>
{/* Equipment-specific details */}
<div className="space-y-2 text-sm text-gray-600">
{renderEquipmentDetails(item)}
</div>
{item.notes && (
<p className="text-sm text-gray-600 mt-3">
<strong>Notes:</strong> {item.notes}
</p>
)}
{item.purchaseDate && (
<p className="text-xs text-gray-500 mt-2">
Purchased: {new Date(item.purchaseDate).toLocaleDateString()}
</p>
)}
</div>
);
const renderEquipmentDetails = (item) => {
switch (item.categoryName?.toLowerCase()) {
case 'mower':
return (
<>
{item.mowerStyle && <p><strong>Style:</strong> {item.mowerStyle.replace('_', ' ')}</p>}
{item.cuttingWidthInches && <p><strong>Cutting Width:</strong> {item.cuttingWidthInches}"</p>}
{item.engineHp && <p><strong>Engine:</strong> {item.engineHp} HP</p>}
{item.fuelType && <p><strong>Fuel:</strong> {item.fuelType}</p>}
</>
);
case 'spreader':
return (
<>
{item.spreaderType && <p><strong>Type:</strong> {item.spreaderType.replace('_', ' ')}</p>}
{item.capacityLbs && <p><strong>Capacity:</strong> {item.capacityLbs} lbs</p>}
{item.spreadWidth && <p><strong>Spread Width:</strong> {item.spreadWidth} ft</p>}
</>
);
case 'sprayer':
return (
<>
{item.sprayerType && <p><strong>Type:</strong> {item.sprayerType.replace('_', ' ')}</p>}
{item.tankSizeGallons && <p><strong>Tank Size:</strong> {item.tankSizeGallons} gal</p>}
{item.sprayWidthFeet && <p><strong>Spray Width:</strong> {item.sprayWidthFeet} ft</p>}
{item.pumpGpm && <p><strong>Pump:</strong> {item.pumpGpm} GPM</p>}
{item.boomSections && <p><strong>Boom Sections:</strong> {item.boomSections}</p>}
</>
);
case 'pump':
return (
<>
{item.pumpType && <p><strong>Type:</strong> {item.pumpType}</p>}
{item.maxGpm && <p><strong>Max Flow:</strong> {item.maxGpm} GPM</p>}
{item.maxPsi && <p><strong>Max Pressure:</strong> {item.maxPsi} PSI</p>}
{item.powerSource && <p><strong>Power:</strong> {item.powerSource}</p>}
</>
);
default:
return (
<>
{item.toolType && <p><strong>Type:</strong> {item.toolType.replace('_', ' ')}</p>}
{item.workingWidthInches && <p><strong>Working Width:</strong> {item.workingWidthInches}"</p>}
</>
);
}
};
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',
'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 (
<div className="p-6">
<div className="flex justify-center items-center h-64">
<LoadingSpinner size="lg" />
</div>
</div>
);
}
return (
<div className="p-6">
<h1 className="text-2xl font-bold text-gray-900 mb-6">Equipment</h1>
<div className="card">
<p className="text-gray-600">Equipment management coming soon...</p>
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-bold text-gray-900">Equipment</h1>
<p className="text-gray-600">Manage your lawn care equipment inventory</p>
</div>
<button
onClick={() => setShowCreateForm(true)}
className="btn-primary flex items-center gap-2"
>
<PlusIcon className="h-5 w-5" />
Add Equipment
</button>
</div>
{/* Search and Filters */}
<div className="card mb-6">
<div className="flex flex-wrap gap-4">
{/* Search */}
<div className="flex-1 min-w-64">
<div className="relative">
<MagnifyingGlassIcon className="h-5 w-5 absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
<input
type="text"
className="input pl-10"
placeholder="Search equipment..."
value={searchTerm}
onChange={handleSearch}
/>
</div>
</div>
{/* Category Filter */}
<div className="min-w-48">
<select
className="input"
value={selectedCategory}
onChange={(e) => {
setSelectedCategory(e.target.value);
handleFilterChange();
}}
>
<option value="">All Categories</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
{/* Show Inactive Toggle */}
<div className="flex items-center">
<label className="flex items-center">
<input
type="checkbox"
checked={showInactive}
onChange={(e) => {
setShowInactive(e.target.checked);
handleFilterChange();
}}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="ml-2 text-sm text-gray-700">Show inactive</span>
</label>
</div>
</div>
</div>
{/* Category Tabs */}
<div className="mb-6">
<div className="border-b border-gray-200">
<nav className="-mb-px flex overflow-x-auto">
{categoryTabs.map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`py-2 px-4 border-b-2 font-medium text-sm whitespace-nowrap ${
activeTab === tab
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
{tab === 'all' ? 'All Equipment' : tab}
{tab !== 'all' && (
<span className="ml-2 bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-xs">
{equipment.filter(item => item.categoryName === tab).length}
</span>
)}
{tab === 'all' && (
<span className="ml-2 bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-xs">
{equipment.length}
</span>
)}
</button>
))}
</nav>
</div>
</div>
{/* Equipment Grid */}
{filteredEquipment.length === 0 ? (
<div className="card text-center py-12">
<WrenchScrewdriverIcon className="h-16 w-16 text-gray-300 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No Equipment Found</h3>
<p className="text-gray-600 mb-6">
{searchTerm || selectedCategory
? 'Try adjusting your search or filters'
: 'Start building your equipment inventory'
}
</p>
{!searchTerm && !selectedCategory && (
<button
onClick={() => setShowCreateForm(true)}
className="btn-primary"
>
Add Your First Equipment
</button>
)}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredEquipment.map((item) => (
<EquipmentCard key={item.id} item={item} />
))}
</div>
)}
{/* Create Equipment Form Modal */}
{showCreateForm && (
<EquipmentFormModal
isEdit={false}
equipment={null}
categories={categories}
equipmentTypes={equipmentTypes}
onSubmit={handleCreateEquipment}
onCancel={() => setShowCreateForm(false)}
/>
)}
{/* Edit Equipment Form Modal */}
{showEditForm && editingEquipment && (
<EquipmentFormModal
isEdit={true}
equipment={editingEquipment}
categories={categories}
equipmentTypes={equipmentTypes}
onSubmit={handleUpdateEquipment}
onCancel={() => {
setShowEditForm(false);
setEditingEquipment(null);
}}
/>
)}
</div>
);
};
// 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 || '',
// 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,
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 (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Mower Style *</label>
<select
className="input"
value={formData.mowerStyle}
onChange={(e) => setFormData({ ...formData, mowerStyle: e.target.value })}
required
>
<option value="push">Push</option>
<option value="self_propelled">Self-Propelled</option>
<option value="zero_turn">Zero Turn</option>
<option value="lawn_tractor">Lawn Tractor</option>
<option value="riding">Riding Mower</option>
</select>
</div>
<div>
<label className="label">Cutting Width (inches)</label>
<input
type="number"
step="0.1"
className="input"
value={formData.cuttingWidthInches}
onChange={(e) => setFormData({ ...formData, cuttingWidthInches: e.target.value })}
placeholder="21"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Engine HP</label>
<input
type="number"
step="0.1"
className="input"
value={formData.engineHp}
onChange={(e) => setFormData({ ...formData, engineHp: e.target.value })}
placeholder="6.5"
/>
</div>
<div>
<label className="label">Fuel Type</label>
<select
className="input"
value={formData.fuelType}
onChange={(e) => setFormData({ ...formData, fuelType: e.target.value })}
>
<option value="">Select fuel type</option>
<option value="gasoline">Gasoline</option>
<option value="diesel">Diesel</option>
<option value="electric">Electric</option>
<option value="battery">Battery</option>
<option value="propane">Propane</option>
</select>
</div>
</div>
</>
);
case 'spreader':
return (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Spreader Type *</label>
<select
className="input"
value={formData.spreaderType}
onChange={(e) => setFormData({ ...formData, spreaderType: e.target.value })}
required
>
<option value="walk_behind">Walk Behind</option>
<option value="pull_behind">Pull Behind</option>
<option value="handheld">Handheld</option>
</select>
</div>
<div>
<label className="label">Capacity (lbs)</label>
<input
type="number"
step="0.1"
className="input"
value={formData.capacityLbs}
onChange={(e) => setFormData({ ...formData, capacityLbs: e.target.value })}
placeholder="50"
/>
</div>
</div>
<div>
<label className="label">Spread Width (feet)</label>
<input
type="number"
step="0.1"
className="input"
value={formData.spreadWidth}
onChange={(e) => setFormData({ ...formData, spreadWidth: e.target.value })}
placeholder="8"
/>
</div>
</>
);
case 'sprayer':
return (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Sprayer Type *</label>
<select
className="input"
value={formData.sprayerType}
onChange={(e) => setFormData({ ...formData, sprayerType: e.target.value })}
required
>
<option value="walk_behind">Walk Behind</option>
<option value="tow_behind">Tow Behind</option>
<option value="mower_mounted">Mower Mounted</option>
<option value="ride_on">Ride On</option>
<option value="hand_pump">Hand Pump</option>
</select>
</div>
<div>
<label className="label">Tank Size (gallons)</label>
<input
type="number"
step="0.1"
className="input"
value={formData.tankSizeGallons}
onChange={(e) => setFormData({ ...formData, tankSizeGallons: e.target.value })}
placeholder="25"
/>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="label">Spray Width (feet)</label>
<input
type="number"
step="0.1"
className="input"
value={formData.sprayWidthFeet}
onChange={(e) => setFormData({ ...formData, sprayWidthFeet: e.target.value })}
placeholder="10"
/>
</div>
<div>
<label className="label">Pump GPM</label>
<input
type="number"
step="0.1"
className="input"
value={formData.pumpGpm}
onChange={(e) => setFormData({ ...formData, pumpGpm: e.target.value })}
placeholder="2.5"
/>
</div>
<div>
<label className="label">Pump PSI</label>
<input
type="number"
className="input"
value={formData.pumpPsi}
onChange={(e) => setFormData({ ...formData, pumpPsi: e.target.value })}
placeholder="60"
/>
</div>
</div>
<div>
<label className="label">Boom Sections</label>
<input
type="number"
className="input"
value={formData.boomSections}
onChange={(e) => setFormData({ ...formData, boomSections: e.target.value })}
placeholder="3"
/>
</div>
</>
);
case 'pump':
return (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Pump Type</label>
<input
type="text"
className="input"
value={formData.pumpType}
onChange={(e) => setFormData({ ...formData, pumpType: e.target.value })}
placeholder="Centrifugal, Diaphragm, etc."
/>
</div>
<div>
<label className="label">Power Source</label>
<select
className="input"
value={formData.powerSource}
onChange={(e) => setFormData({ ...formData, powerSource: e.target.value })}
>
<option value="">Select power source</option>
<option value="electric">Electric</option>
<option value="gasoline">Gasoline</option>
<option value="diesel">Diesel</option>
<option value="pto">PTO</option>
<option value="hydraulic">Hydraulic</option>
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Max GPM</label>
<input
type="number"
step="0.1"
className="input"
value={formData.maxGpm}
onChange={(e) => setFormData({ ...formData, maxGpm: e.target.value })}
placeholder="10"
/>
</div>
<div>
<label className="label">Max PSI</label>
<input
type="number"
className="input"
value={formData.maxPsi}
onChange={(e) => setFormData({ ...formData, maxPsi: e.target.value })}
placeholder="100"
/>
</div>
</div>
</>
);
default:
return (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Tool Type</label>
<select
className="input"
value={formData.toolType}
onChange={(e) => setFormData({ ...formData, toolType: e.target.value })}
>
<option value="walk_behind">Walk Behind</option>
<option value="tow_behind">Tow Behind</option>
<option value="handheld">Handheld</option>
</select>
</div>
<div>
<label className="label">Working Width (inches)</label>
<input
type="number"
step="0.1"
className="input"
value={formData.workingWidthInches}
onChange={(e) => setFormData({ ...formData, workingWidthInches: e.target.value })}
placeholder="18"
/>
</div>
</div>
</>
);
}
};
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">
{isEdit ? 'Edit Equipment' : 'Add New Equipment'}
</h3>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Basic Information */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Equipment Type (Optional)</label>
<select
className="input"
value={formData.equipmentTypeId}
onChange={(e) => {
const selectedType = equipmentTypes.find(type => type.id === parseInt(e.target.value));
setFormData({
...formData,
equipmentTypeId: e.target.value,
categoryId: selectedType?.categoryId || formData.categoryId
});
}}
>
<option value="">Select from database...</option>
{equipmentTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.name} {type.manufacturer && `- ${type.manufacturer}`}
</option>
))}
</select>
</div>
<div>
<label className="label">Category *</label>
<select
className="input"
value={formData.categoryId}
onChange={(e) => setFormData({ ...formData, categoryId: e.target.value })}
required={!formData.equipmentTypeId}
>
<option value="">Select category...</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
</div>
<div>
<label className="label">Equipment Name *</label>
<input
type="text"
className="input"
value={formData.customName}
onChange={(e) => setFormData({ ...formData, customName: e.target.value })}
placeholder="My Lawn Mower, Office Spreader, etc."
required={!formData.equipmentTypeId}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Manufacturer</label>
<input
type="text"
className="input"
value={formData.manufacturer}
onChange={(e) => setFormData({ ...formData, manufacturer: e.target.value })}
placeholder="Toro, John Deere, etc."
/>
</div>
<div>
<label className="label">Model</label>
<input
type="text"
className="input"
value={formData.model}
onChange={(e) => setFormData({ ...formData, model: e.target.value })}
placeholder="Model number"
/>
</div>
</div>
{/* Category-specific fields */}
{formData.categoryId && renderCategorySpecificFields()}
{/* Purchase Information */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">Purchase Date</label>
<input
type="date"
className="input"
value={formData.purchaseDate}
onChange={(e) => setFormData({ ...formData, purchaseDate: e.target.value })}
/>
</div>
<div>
<label className="label">Purchase Price</label>
<input
type="number"
step="0.01"
className="input"
value={formData.purchasePrice}
onChange={(e) => setFormData({ ...formData, purchasePrice: e.target.value })}
placeholder="0.00"
/>
</div>
</div>
{/* Notes and Status */}
<div>
<label className="label">Notes</label>
<textarea
className="input"
rows="3"
value={formData.notes}
onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
placeholder="Maintenance notes, special instructions, etc."
/>
</div>
{isEdit && (
<div className="flex items-center">
<label className="flex items-center">
<input
type="checkbox"
checked={formData.isActive}
onChange={(e) => setFormData({ ...formData, isActive: e.target.checked })}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="ml-2 text-sm text-gray-700">Equipment is active</span>
</label>
</div>
)}
<div className="flex gap-3 pt-4">
<button type="submit" className="btn-primary flex-1">
{isEdit ? 'Update Equipment' : 'Create Equipment'}
</button>
<button
type="button"
onClick={onCancel}
className="btn-secondary flex-1"
>
Cancel
</button>
</div>
</form>
</div>
</div>
);

View File

@@ -92,15 +92,30 @@ export const propertiesAPI = {
// Equipment API endpoints
export const equipmentAPI = {
getAll: () => apiClient.get('/equipment'),
getAll: (params) => apiClient.get('/equipment', { params }),
getById: (id) => apiClient.get(`/equipment/${id}`),
create: (equipmentData) => apiClient.post('/equipment', equipmentData),
update: (id, equipmentData) => apiClient.put(`/equipment/${id}`, equipmentData),
delete: (id) => apiClient.delete(`/equipment/${id}`),
getTypes: () => apiClient.get('/equipment/types'),
getCategories: () => apiClient.get('/equipment/categories'),
getTypes: (params) => apiClient.get('/equipment/types', { params }),
getCalculations: (id, params) => apiClient.get(`/equipment/${id}/calculations`, { params }),
};
// Nozzles API endpoints
export const nozzlesAPI = {
getAll: () => apiClient.get('/nozzles'),
getById: (id) => apiClient.get(`/nozzles/${id}`),
create: (nozzleData) => apiClient.post('/nozzles', nozzleData),
update: (id, nozzleData) => apiClient.put(`/nozzles/${id}`, nozzleData),
delete: (id) => apiClient.delete(`/nozzles/${id}`),
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}`),
};
// Products API endpoints
export const productsAPI = {
getAll: (params) => apiClient.get('/products', { params }),