fixing frontend
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
TrashIcon,
|
||||
PencilIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { productsAPI, productSpreaderSettingsAPI } from '../../services/api';
|
||||
import { productsAPI, productSpreaderSettingsAPI, equipmentAPI } from '../../services/api';
|
||||
import LoadingSpinner from '../../components/UI/LoadingSpinner';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@@ -76,7 +76,9 @@ const Products = () => {
|
||||
const settingPromises = productData.spreaderSettings.map(setting =>
|
||||
productSpreaderSettingsAPI.create({
|
||||
userProductId: createdProduct.id,
|
||||
spreaderBrand: setting.spreaderBrand,
|
||||
equipmentId: setting.equipmentId ? parseInt(setting.equipmentId) : null,
|
||||
// Legacy fields for backward compatibility
|
||||
spreaderBrand: setting.spreaderBrand || null,
|
||||
spreaderModel: setting.spreaderModel || null,
|
||||
settingValue: setting.settingValue,
|
||||
rateDescription: setting.rateDescription || null,
|
||||
@@ -417,24 +419,51 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
|
||||
});
|
||||
|
||||
const [spreaderSettings, setSpreaderSettings] = useState([]);
|
||||
const [availableSpreaders, setAvailableSpreaders] = useState([]);
|
||||
const [loadingSpreaders, setLoadingSpreaders] = useState(false);
|
||||
const [newSpreaderSetting, setNewSpreaderSetting] = useState({
|
||||
spreaderBrand: '',
|
||||
spreaderModel: '',
|
||||
equipmentId: '',
|
||||
settingValue: '',
|
||||
rateDescription: '',
|
||||
notes: ''
|
||||
});
|
||||
|
||||
// Load user's spreader equipment
|
||||
useEffect(() => {
|
||||
const loadSpreaders = async () => {
|
||||
setLoadingSpreaders(true);
|
||||
try {
|
||||
const response = await equipmentAPI.getSpreaders();
|
||||
setAvailableSpreaders(response.data.data.spreaders || []);
|
||||
} catch (error) {
|
||||
console.error('Failed to load spreaders:', error);
|
||||
toast.error('Failed to load spreader equipment');
|
||||
} finally {
|
||||
setLoadingSpreaders(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadSpreaders();
|
||||
}, []);
|
||||
|
||||
const addSpreaderSetting = () => {
|
||||
if (!newSpreaderSetting.spreaderBrand || !newSpreaderSetting.settingValue) {
|
||||
toast.error('Please enter spreader brand and setting value');
|
||||
if (!newSpreaderSetting.equipmentId || !newSpreaderSetting.settingValue) {
|
||||
toast.error('Please select spreader equipment and enter setting value');
|
||||
return;
|
||||
}
|
||||
|
||||
setSpreaderSettings([...spreaderSettings, { ...newSpreaderSetting, id: Date.now() }]);
|
||||
const selectedSpreader = availableSpreaders.find(s => s.id === parseInt(newSpreaderSetting.equipmentId));
|
||||
const settingWithSpreaderInfo = {
|
||||
...newSpreaderSetting,
|
||||
id: Date.now(),
|
||||
equipmentName: selectedSpreader?.name,
|
||||
equipmentManufacturer: selectedSpreader?.manufacturer,
|
||||
equipmentModel: selectedSpreader?.model
|
||||
};
|
||||
|
||||
setSpreaderSettings([...spreaderSettings, settingWithSpreaderInfo]);
|
||||
setNewSpreaderSetting({
|
||||
spreaderBrand: '',
|
||||
spreaderModel: '',
|
||||
equipmentId: '',
|
||||
settingValue: '',
|
||||
rateDescription: '',
|
||||
notes: ''
|
||||
@@ -612,7 +641,7 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
|
||||
<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}
|
||||
{setting.equipmentName || `${setting.equipmentManufacturer} ${setting.equipmentModel}`.trim()} - Setting: {setting.settingValue}
|
||||
</div>
|
||||
{setting.rateDescription && (
|
||||
<div className="text-sm text-gray-600">{setting.rateDescription}</div>
|
||||
@@ -633,54 +662,70 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
|
||||
{/* 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">
|
||||
{availableSpreaders.length === 0 ? (
|
||||
<div className="text-sm text-blue-800 bg-blue-100 p-3 rounded">
|
||||
<p>No spreader equipment found. Please add spreader equipment to your inventory first.</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<label className="label text-xs">Select Spreader Equipment *</label>
|
||||
<select
|
||||
className="input text-sm"
|
||||
value={newSpreaderSetting.equipmentId}
|
||||
onChange={(e) => setNewSpreaderSetting({ ...newSpreaderSetting, equipmentId: e.target.value })}
|
||||
>
|
||||
<option value="">Choose spreader...</option>
|
||||
{availableSpreaders.map((spreader) => (
|
||||
<option key={spreader.id} value={spreader.id}>
|
||||
{spreader.name} {spreader.spreaderType && `(${spreader.spreaderType})`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{availableSpreaders.length > 0 && (
|
||||
<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>
|
||||
)}
|
||||
{availableSpreaders.length > 0 && (
|
||||
<div>
|
||||
<label className="label text-xs">Spreader Brand *</label>
|
||||
<input
|
||||
type="text"
|
||||
<label className="label text-xs">Notes (Optional)</label>
|
||||
<textarea
|
||||
className="input text-sm"
|
||||
value={newSpreaderSetting.spreaderBrand}
|
||||
onChange={(e) => setNewSpreaderSetting({ ...newSpreaderSetting, spreaderBrand: e.target.value })}
|
||||
placeholder="LESCO, PermaGreen, Cyclone, etc."
|
||||
rows="2"
|
||||
value={newSpreaderSetting.notes}
|
||||
onChange={(e) => setNewSpreaderSetting({ ...newSpreaderSetting, notes: e.target.value })}
|
||||
placeholder="Additional notes about this setting..."
|
||||
/>
|
||||
</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"
|
||||
disabled={!newSpreaderSetting.equipmentId || !newSpreaderSetting.settingValue || availableSpreaders.length === 0}
|
||||
className="btn-primary text-sm px-3 py-1 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Add Setting
|
||||
</button>
|
||||
@@ -735,15 +780,34 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
||||
|
||||
const [editMode, setEditMode] = useState('basic'); // 'basic' or 'advanced'
|
||||
const [editSpreaderSettings, setEditSpreaderSettings] = useState([]);
|
||||
const [availableEditSpreaders, setAvailableEditSpreaders] = useState([]);
|
||||
const [loadingEditSpreaders, setLoadingEditSpreaders] = useState(false);
|
||||
const [newEditSpreaderSetting, setNewEditSpreaderSetting] = useState({
|
||||
spreaderBrand: '',
|
||||
spreaderModel: '',
|
||||
equipmentId: '',
|
||||
settingValue: '',
|
||||
rateDescription: '',
|
||||
notes: ''
|
||||
});
|
||||
const [loadingSettings, setLoadingSettings] = useState(false);
|
||||
|
||||
// Load user's spreader equipment
|
||||
useEffect(() => {
|
||||
const loadEditSpreaders = async () => {
|
||||
setLoadingEditSpreaders(true);
|
||||
try {
|
||||
const response = await equipmentAPI.getSpreaders();
|
||||
setAvailableEditSpreaders(response.data.data.spreaders || []);
|
||||
} catch (error) {
|
||||
console.error('Failed to load spreaders:', error);
|
||||
toast.error('Failed to load spreader equipment');
|
||||
} finally {
|
||||
setLoadingEditSpreaders(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadEditSpreaders();
|
||||
}, []);
|
||||
|
||||
// Load existing spreader settings when modal opens
|
||||
useEffect(() => {
|
||||
const loadSpreaderSettings = async () => {
|
||||
@@ -820,11 +884,13 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
||||
// Include spreader settings for granular products
|
||||
...(formData.productType === 'granular' && editSpreaderSettings.length > 0 && {
|
||||
spreaderSettings: editSpreaderSettings.map(setting => ({
|
||||
spreaderBrand: setting.spreaderBrand,
|
||||
spreaderModel: setting.spreaderModel && setting.spreaderModel.trim() ? setting.spreaderModel.trim() : '',
|
||||
equipmentId: setting.equipmentId || null,
|
||||
// Legacy fields for backward compatibility
|
||||
spreaderBrand: setting.spreaderBrand || null,
|
||||
spreaderModel: setting.spreaderModel && setting.spreaderModel.trim() ? setting.spreaderModel.trim() : null,
|
||||
settingValue: setting.settingValue,
|
||||
rateDescription: setting.rateDescription && setting.rateDescription.trim() ? setting.rateDescription.trim() : '',
|
||||
notes: setting.notes && setting.notes.trim() ? setting.notes.trim() : ''
|
||||
rateDescription: setting.rateDescription && setting.rateDescription.trim() ? setting.rateDescription.trim() : null,
|
||||
notes: setting.notes && setting.notes.trim() ? setting.notes.trim() : null
|
||||
}))
|
||||
})
|
||||
};
|
||||
@@ -835,14 +901,21 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
||||
|
||||
// Handler functions for edit spreader settings
|
||||
const handleAddEditSpreaderSetting = () => {
|
||||
if (!newEditSpreaderSetting.spreaderBrand.trim() || !newEditSpreaderSetting.settingValue.trim()) {
|
||||
if (!newEditSpreaderSetting.equipmentId || !newEditSpreaderSetting.settingValue.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEditSpreaderSettings([...editSpreaderSettings, { ...newEditSpreaderSetting }]);
|
||||
const selectedSpreader = availableEditSpreaders.find(s => s.id === parseInt(newEditSpreaderSetting.equipmentId));
|
||||
const settingWithSpreaderInfo = {
|
||||
...newEditSpreaderSetting,
|
||||
equipmentName: selectedSpreader?.name,
|
||||
equipmentManufacturer: selectedSpreader?.manufacturer,
|
||||
equipmentModel: selectedSpreader?.model
|
||||
};
|
||||
|
||||
setEditSpreaderSettings([...editSpreaderSettings, settingWithSpreaderInfo]);
|
||||
setNewEditSpreaderSetting({
|
||||
spreaderBrand: '',
|
||||
spreaderModel: '',
|
||||
equipmentId: '',
|
||||
settingValue: '',
|
||||
rateDescription: '',
|
||||
notes: ''
|
||||
@@ -1092,7 +1165,7 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
||||
<div key={index} className="bg-gray-50 p-3 rounded border text-sm">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<div><strong>{setting.spreaderBrand}</strong> {setting.spreaderModel && `(${setting.spreaderModel})`}</div>
|
||||
<div><strong>{setting.equipmentName || `${setting.spreaderBrand}${setting.spreaderModel ? ` (${setting.spreaderModel})` : ''}`}</strong></div>
|
||||
<div>Setting: <span className="font-medium">{setting.settingValue}</span></div>
|
||||
{setting.rateDescription && <div className="text-gray-600">{setting.rateDescription}</div>}
|
||||
{setting.notes && <div className="text-gray-600 italic">{setting.notes}</div>}
|
||||
@@ -1113,59 +1186,63 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
||||
{/* Add new spreader setting */}
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-2">Add New Setting:</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
placeholder="Spreader Brand (e.g., LESCO, PermaGreen)"
|
||||
value={newEditSpreaderSetting.spreaderBrand}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, spreaderBrand: e.target.value })}
|
||||
/>
|
||||
{availableEditSpreaders.length === 0 ? (
|
||||
<div className="text-sm text-blue-800 bg-blue-100 p-3 rounded mb-3">
|
||||
<p>No spreader equipment found. Please add spreader equipment to your inventory first.</p>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
) : (
|
||||
<div className="mb-3">
|
||||
<select
|
||||
className="input text-sm"
|
||||
placeholder="Model (optional)"
|
||||
value={newEditSpreaderSetting.spreaderModel}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, spreaderModel: e.target.value })}
|
||||
/>
|
||||
value={newEditSpreaderSetting.equipmentId}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, equipmentId: e.target.value })}
|
||||
>
|
||||
<option value="">Choose spreader...</option>
|
||||
{availableEditSpreaders.map((spreader) => (
|
||||
<option key={spreader.id} value={spreader.id}>
|
||||
{spreader.name} {spreader.spreaderType && `(${spreader.spreaderType})`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
placeholder="Setting (e.g., #14, 2.5)"
|
||||
value={newEditSpreaderSetting.settingValue}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, settingValue: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
placeholder="Rate description (optional)"
|
||||
value={newEditSpreaderSetting.rateDescription}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, rateDescription: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<textarea
|
||||
className="input text-sm"
|
||||
rows="2"
|
||||
placeholder="Notes (optional)"
|
||||
value={newEditSpreaderSetting.notes}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, notes: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{availableEditSpreaders.length > 0 && (
|
||||
<>
|
||||
<div className="mt-3 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
placeholder="Setting (e.g., #14, 2.5)"
|
||||
value={newEditSpreaderSetting.settingValue}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, settingValue: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
className="input text-sm"
|
||||
placeholder="Rate description (optional)"
|
||||
value={newEditSpreaderSetting.rateDescription}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, rateDescription: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<textarea
|
||||
className="input text-sm"
|
||||
rows="2"
|
||||
placeholder="Notes (optional)"
|
||||
value={newEditSpreaderSetting.notes}
|
||||
onChange={(e) => setNewEditSpreaderSetting({ ...newEditSpreaderSetting, notes: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddEditSpreaderSetting}
|
||||
disabled={!newEditSpreaderSetting.spreaderBrand.trim() || !newEditSpreaderSetting.settingValue.trim()}
|
||||
disabled={!newEditSpreaderSetting.equipmentId || !newEditSpreaderSetting.settingValue.trim() || availableEditSpreaders.length === 0}
|
||||
className="mt-3 px-3 py-1.5 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Add Setting
|
||||
|
||||
@@ -117,6 +117,7 @@ export const equipmentAPI = {
|
||||
delete: (id) => apiClient.delete(`/equipment/${id}`),
|
||||
getCategories: () => apiClient.get('/equipment/categories'),
|
||||
getTypes: (params) => apiClient.get('/equipment/types', { params }),
|
||||
getSpreaders: () => apiClient.get('/equipment/spreaders'), // Get user's spreader equipment
|
||||
getCalculations: (id, params) => apiClient.get(`/equipment/${id}/calculations`, { params }),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user