fixing frontend

This commit is contained in:
Jake Kasper
2025-08-25 09:02:56 -04:00
parent 080b095042
commit fc832bc66a
2 changed files with 187 additions and 109 deletions

View File

@@ -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

View File

@@ -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 }),
};