spreader stuff
This commit is contained in:
@@ -275,7 +275,7 @@ const Applications = () => {
|
||||
const planPayload = {
|
||||
lawnSectionId: parseInt(planData.selectedAreas[0]),
|
||||
equipmentId: parseInt(planData.equipmentId),
|
||||
nozzleId: planData.nozzleId ? parseInt(planData.nozzleId) : null,
|
||||
...(planData.applicationType === 'liquid' && planData.nozzleId && { nozzleId: parseInt(planData.nozzleId) }),
|
||||
plannedDate: planData.plannedDate || new Date().toISOString().split('T')[0],
|
||||
notes: planData.notes || '',
|
||||
areaSquareFeet: areaSquareFeet,
|
||||
@@ -288,11 +288,13 @@ const Applications = () => {
|
||||
capacityLbs: selectedEquipment?.capacityLbs,
|
||||
spreadWidth: selectedEquipment?.spreadWidth
|
||||
},
|
||||
nozzle: selectedNozzle ? {
|
||||
id: selectedNozzle.id,
|
||||
flowRateGpm: selectedNozzle.flowRateGpm,
|
||||
sprayAngle: selectedNozzle.sprayAngle
|
||||
} : null,
|
||||
...(planData.applicationType === 'liquid' && selectedNozzle && {
|
||||
nozzle: {
|
||||
id: selectedNozzle.id,
|
||||
flowRateGpm: selectedNozzle.flowRateGpm,
|
||||
sprayAngle: selectedNozzle.sprayAngle
|
||||
}
|
||||
}),
|
||||
products: [{
|
||||
...(planData.selectedProduct?.isShared
|
||||
? { productId: parseInt(planData.selectedProduct.id) }
|
||||
@@ -317,7 +319,7 @@ const Applications = () => {
|
||||
const planPayload = {
|
||||
lawnSectionId: parseInt(areaId),
|
||||
equipmentId: parseInt(planData.equipmentId),
|
||||
nozzleId: planData.nozzleId ? parseInt(planData.nozzleId) : null,
|
||||
...(planData.applicationType === 'liquid' && planData.nozzleId && { nozzleId: parseInt(planData.nozzleId) }),
|
||||
plannedDate: new Date().toISOString().split('T')[0],
|
||||
notes: planData.notes || '',
|
||||
areaSquareFeet: areaSquareFeet,
|
||||
@@ -330,11 +332,13 @@ const Applications = () => {
|
||||
capacityLbs: selectedEquipment?.capacityLbs,
|
||||
spreadWidth: selectedEquipment?.spreadWidth
|
||||
},
|
||||
nozzle: selectedNozzle ? {
|
||||
id: selectedNozzle.id,
|
||||
flowRateGpm: selectedNozzle.flowRateGpm,
|
||||
sprayAngle: selectedNozzle.sprayAngle
|
||||
} : null,
|
||||
...(planData.applicationType === 'liquid' && selectedNozzle && {
|
||||
nozzle: {
|
||||
id: selectedNozzle.id,
|
||||
flowRateGpm: selectedNozzle.flowRateGpm,
|
||||
sprayAngle: selectedNozzle.sprayAngle
|
||||
}
|
||||
}),
|
||||
products: [{
|
||||
...(planData.selectedProduct?.isShared
|
||||
? { productId: parseInt(planData.selectedProduct.id) }
|
||||
|
||||
@@ -493,6 +493,7 @@ const EquipmentFormModal = ({ isEdit, equipment, categories, equipmentTypes, onS
|
||||
customName: equipment?.customName || '',
|
||||
manufacturer: equipment?.manufacturer || '',
|
||||
model: equipment?.model || '',
|
||||
notes: equipment?.notes || '',
|
||||
// Spreader fields
|
||||
capacityLbs: equipment?.capacityLbs || '',
|
||||
spreaderType: equipment?.spreaderType || 'walk_behind',
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
TrashIcon,
|
||||
PencilIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { productsAPI } from '../../services/api';
|
||||
import { productsAPI, productSpreaderSettingsAPI } from '../../services/api';
|
||||
import LoadingSpinner from '../../components/UI/LoadingSpinner';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -67,8 +67,27 @@ const Products = () => {
|
||||
|
||||
const handleCreateProduct = async (productData) => {
|
||||
try {
|
||||
await productsAPI.createUserProduct(productData);
|
||||
toast.success('Custom product created successfully!');
|
||||
// Create the product first
|
||||
const response = await productsAPI.createUserProduct(productData);
|
||||
const createdProduct = response.data.data.product;
|
||||
|
||||
// Save spreader settings if any
|
||||
if (productData.spreaderSettings && productData.spreaderSettings.length > 0) {
|
||||
const settingPromises = productData.spreaderSettings.map(setting =>
|
||||
productSpreaderSettingsAPI.create({
|
||||
userProductId: createdProduct.id,
|
||||
spreaderBrand: setting.spreaderBrand,
|
||||
spreaderModel: setting.spreaderModel || null,
|
||||
settingValue: setting.settingValue,
|
||||
rateDescription: setting.rateDescription || null,
|
||||
notes: setting.notes || null
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(settingPromises);
|
||||
}
|
||||
|
||||
toast.success(`Custom product created successfully${productData.spreaderSettings?.length ? ` with ${productData.spreaderSettings.length} spreader setting(s)` : ''}!`);
|
||||
setShowCreateForm(false);
|
||||
fetchData();
|
||||
} catch (error) {
|
||||
@@ -397,6 +416,35 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
|
||||
notes: ''
|
||||
});
|
||||
|
||||
const [spreaderSettings, setSpreaderSettings] = useState([]);
|
||||
const [newSpreaderSetting, setNewSpreaderSetting] = useState({
|
||||
spreaderBrand: '',
|
||||
spreaderModel: '',
|
||||
settingValue: '',
|
||||
rateDescription: '',
|
||||
notes: ''
|
||||
});
|
||||
|
||||
const addSpreaderSetting = () => {
|
||||
if (!newSpreaderSetting.spreaderBrand || !newSpreaderSetting.settingValue) {
|
||||
toast.error('Please enter spreader brand and setting value');
|
||||
return;
|
||||
}
|
||||
|
||||
setSpreaderSettings([...spreaderSettings, { ...newSpreaderSetting, id: Date.now() }]);
|
||||
setNewSpreaderSetting({
|
||||
spreaderBrand: '',
|
||||
spreaderModel: '',
|
||||
settingValue: '',
|
||||
rateDescription: '',
|
||||
notes: ''
|
||||
});
|
||||
};
|
||||
|
||||
const removeSpreaderSetting = (id) => {
|
||||
setSpreaderSettings(spreaderSettings.filter(setting => setting.id !== id));
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -416,7 +464,8 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
|
||||
customName: formData.customName || null,
|
||||
customRateAmount: formData.customRateAmount ? parseFloat(formData.customRateAmount) : null,
|
||||
customRateUnit: formData.customRateUnit || null,
|
||||
notes: formData.notes || null
|
||||
notes: formData.notes || null,
|
||||
spreaderSettings: formData.productType === 'granular' ? spreaderSettings : []
|
||||
};
|
||||
|
||||
onSubmit(submitData);
|
||||
@@ -547,6 +596,98 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Spreader Settings for Granular Products */}
|
||||
{formData.productType === 'granular' && (
|
||||
<div className="border-t pt-4">
|
||||
<h4 className="text-md font-semibold text-gray-900 mb-3">Spreader Settings</h4>
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
Add spreader settings for different brands/models. This helps determine the correct spreader dial setting when applying this product.
|
||||
</p>
|
||||
|
||||
{/* List existing spreader settings */}
|
||||
{spreaderSettings.length > 0 && (
|
||||
<div className="space-y-2 mb-4">
|
||||
<label className="label">Added Settings:</label>
|
||||
{spreaderSettings.map((setting) => (
|
||||
<div key={setting.id} className="flex items-center justify-between bg-gray-50 p-3 rounded-lg">
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">
|
||||
{setting.spreaderBrand} {setting.spreaderModel && `${setting.spreaderModel}`} - Setting: {setting.settingValue}
|
||||
</div>
|
||||
{setting.rateDescription && (
|
||||
<div className="text-sm text-gray-600">{setting.rateDescription}</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeSpreaderSetting(setting.id)}
|
||||
className="text-red-600 hover:text-red-800"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add new spreader setting form */}
|
||||
<div className="bg-blue-50 p-4 rounded-lg space-y-3">
|
||||
<h5 className="text-sm font-semibold text-blue-900">Add Spreader Setting</h5>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="label text-xs">Spreader Brand *</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
value={newSpreaderSetting.spreaderBrand}
|
||||
onChange={(e) => setNewSpreaderSetting({ ...newSpreaderSetting, spreaderBrand: e.target.value })}
|
||||
placeholder="LESCO, PermaGreen, Cyclone, etc."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label text-xs">Model (Optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
value={newSpreaderSetting.spreaderModel}
|
||||
onChange={(e) => setNewSpreaderSetting({ ...newSpreaderSetting, spreaderModel: e.target.value })}
|
||||
placeholder="Model name/number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="label text-xs">Setting Value *</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
value={newSpreaderSetting.settingValue}
|
||||
onChange={(e) => setNewSpreaderSetting({ ...newSpreaderSetting, settingValue: e.target.value })}
|
||||
placeholder="#14, 4, 20, etc."
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label text-xs">Rate Description</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
value={newSpreaderSetting.rateDescription}
|
||||
onChange={(e) => setNewSpreaderSetting({ ...newSpreaderSetting, rateDescription: e.target.value })}
|
||||
placeholder="e.g., 1 lb N per 1000 sq ft"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={addSpreaderSetting}
|
||||
className="btn-primary text-sm px-3 py-1"
|
||||
>
|
||||
Add Setting
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="label">Notes</label>
|
||||
<textarea
|
||||
|
||||
@@ -181,6 +181,22 @@ export const applicationsAPI = {
|
||||
getStats: (params) => apiClient.get('/applications/stats', { params }),
|
||||
};
|
||||
|
||||
// Spreader Settings API endpoints
|
||||
export const spreaderSettingsAPI = {
|
||||
getAll: () => apiClient.get('/spreader-settings'),
|
||||
getBrands: () => apiClient.get('/spreader-settings/brands'),
|
||||
getByBrand: (brand) => apiClient.get(`/spreader-settings/${brand}`),
|
||||
};
|
||||
|
||||
// Product Spreader Settings API endpoints
|
||||
export const productSpreaderSettingsAPI = {
|
||||
getByProduct: (productId) => apiClient.get(`/product-spreader-settings/product/${productId}`),
|
||||
getByUserProduct: (userProductId) => apiClient.get(`/product-spreader-settings/user-product/${userProductId}`),
|
||||
create: (settingData) => apiClient.post('/product-spreader-settings', settingData),
|
||||
update: (id, settingData) => apiClient.put(`/product-spreader-settings/${id}`, settingData),
|
||||
delete: (id) => apiClient.delete(`/product-spreader-settings/${id}`),
|
||||
};
|
||||
|
||||
// Weather API endpoints
|
||||
export const weatherAPI = {
|
||||
getCurrent: (propertyId) => apiClient.get(`/weather/${propertyId}`),
|
||||
|
||||
Reference in New Issue
Block a user