fix some stuff
This commit is contained in:
@@ -181,7 +181,7 @@ router.post('/', validateRequest(spreaderSettingSchema), async (req, res, next)
|
|||||||
router.put('/:id', validateParams(idParamSchema), validateRequest(spreaderSettingSchema), async (req, res, next) => {
|
router.put('/:id', validateParams(idParamSchema), validateRequest(spreaderSettingSchema), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const settingId = req.params.id;
|
const settingId = req.params.id;
|
||||||
const { productId, userProductId, spreaderBrand, spreaderModel, settingValue, rateDescription, notes } = req.body;
|
const { productId, userProductId, equipmentId, spreaderBrand, spreaderModel, settingValue, rateDescription, notes } = req.body;
|
||||||
|
|
||||||
// Check if setting exists and user has permission to edit it
|
// Check if setting exists and user has permission to edit it
|
||||||
let checkQuery;
|
let checkQuery;
|
||||||
@@ -206,13 +206,25 @@ router.put('/:id', validateParams(idParamSchema), validateRequest(spreaderSettin
|
|||||||
throw new AppError('Spreader setting not found', 404);
|
throw new AppError('Spreader setting not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If equipment ID is provided, verify it belongs to the user
|
||||||
|
if (equipmentId) {
|
||||||
|
const equipmentCheck = await pool.query(
|
||||||
|
'SELECT id FROM user_equipment WHERE id = $1 AND user_id = $2',
|
||||||
|
[equipmentId, req.user.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (equipmentCheck.rows.length === 0) {
|
||||||
|
throw new AppError('Equipment not found', 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
`UPDATE product_spreader_settings
|
`UPDATE product_spreader_settings
|
||||||
SET spreader_brand = $1, spreader_model = $2, setting_value = $3,
|
SET equipment_id = $1, spreader_brand = $2, spreader_model = $3, setting_value = $4,
|
||||||
rate_description = $4, notes = $5
|
rate_description = $5, notes = $6
|
||||||
WHERE id = $6
|
WHERE id = $7
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[spreaderBrand, spreaderModel, settingValue, rateDescription, notes, settingId]
|
[equipmentId, spreaderBrand, spreaderModel, settingValue, rateDescription, notes, settingId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setting = result.rows[0];
|
const setting = result.rows[0];
|
||||||
@@ -223,6 +235,7 @@ router.put('/:id', validateParams(idParamSchema), validateRequest(spreaderSettin
|
|||||||
data: {
|
data: {
|
||||||
setting: {
|
setting: {
|
||||||
id: setting.id,
|
id: setting.id,
|
||||||
|
equipmentId: setting.equipment_id,
|
||||||
spreaderBrand: setting.spreader_brand,
|
spreaderBrand: setting.spreader_brand,
|
||||||
spreaderModel: setting.spreader_model,
|
spreaderModel: setting.spreader_model,
|
||||||
settingValue: setting.setting_value,
|
settingValue: setting.setting_value,
|
||||||
|
|||||||
@@ -502,6 +502,12 @@ const CreateProductModal = ({ onSubmit, onCancel, sharedProducts, categories })
|
|||||||
customRateAmount: formData.customRateAmount ? parseFloat(formData.customRateAmount) : null,
|
customRateAmount: formData.customRateAmount ? parseFloat(formData.customRateAmount) : null,
|
||||||
customRateUnit: formData.customRateUnit || null,
|
customRateUnit: formData.customRateUnit || null,
|
||||||
notes: formData.notes || null,
|
notes: formData.notes || null,
|
||||||
|
// Advanced fields for custom products
|
||||||
|
brand: formData.brand || null,
|
||||||
|
categoryId: formData.categoryId ? parseInt(formData.categoryId) : null,
|
||||||
|
productType: formData.productType || null,
|
||||||
|
activeIngredients: formData.activeIngredients || null,
|
||||||
|
description: formData.description || null,
|
||||||
spreaderSettings: formData.productType === 'granular' ? spreaderSettings : []
|
spreaderSettings: formData.productType === 'granular' ? spreaderSettings : []
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -790,6 +796,7 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
|||||||
const [editSpreaderSettings, setEditSpreaderSettings] = useState([]);
|
const [editSpreaderSettings, setEditSpreaderSettings] = useState([]);
|
||||||
const [availableEditSpreaders, setAvailableEditSpreaders] = useState([]);
|
const [availableEditSpreaders, setAvailableEditSpreaders] = useState([]);
|
||||||
const [loadingEditSpreaders, setLoadingEditSpreaders] = useState(false);
|
const [loadingEditSpreaders, setLoadingEditSpreaders] = useState(false);
|
||||||
|
const [editingSettingIndex, setEditingSettingIndex] = useState(null);
|
||||||
const [newEditSpreaderSetting, setNewEditSpreaderSetting] = useState({
|
const [newEditSpreaderSetting, setNewEditSpreaderSetting] = useState({
|
||||||
equipmentId: '',
|
equipmentId: '',
|
||||||
settingValue: '',
|
settingValue: '',
|
||||||
@@ -829,7 +836,7 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
|||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setEditSpreaderSettings(data.data || []);
|
setEditSpreaderSettings(data.data?.settings || []);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load spreader settings:', error);
|
console.error('Failed to load spreader settings:', error);
|
||||||
@@ -936,6 +943,123 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
|||||||
setEditSpreaderSettings(updated);
|
setEditSpreaderSettings(updated);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleStartEditSetting = (index) => {
|
||||||
|
setEditingSettingIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelEditSetting = () => {
|
||||||
|
setEditingSettingIndex(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveEditSetting = (index, updatedSetting) => {
|
||||||
|
const updated = [...editSpreaderSettings];
|
||||||
|
updated[index] = updatedSetting;
|
||||||
|
setEditSpreaderSettings(updated);
|
||||||
|
setEditingSettingIndex(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inline edit component for existing spreader settings
|
||||||
|
const EditSpreaderSettingForm = ({ setting, availableSpreaders, onSave, onCancel }) => {
|
||||||
|
const [editValues, setEditValues] = useState({
|
||||||
|
equipmentId: setting.equipmentId || '',
|
||||||
|
settingValue: setting.settingValue || '',
|
||||||
|
rateDescription: setting.rateDescription || '',
|
||||||
|
notes: setting.notes || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (!editValues.settingValue.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedSpreader = availableSpreaders.find(s => s.id.toString() === editValues.equipmentId);
|
||||||
|
|
||||||
|
const updatedSetting = {
|
||||||
|
...setting,
|
||||||
|
equipmentId: editValues.equipmentId ? parseInt(editValues.equipmentId) : null,
|
||||||
|
equipmentName: selectedSpreader?.name || setting.equipmentName,
|
||||||
|
equipmentManufacturer: selectedSpreader?.manufacturer || setting.equipmentManufacturer,
|
||||||
|
equipmentModel: selectedSpreader?.model || setting.equipmentModel,
|
||||||
|
settingValue: editValues.settingValue.trim(),
|
||||||
|
rateDescription: editValues.rateDescription.trim() || null,
|
||||||
|
notes: editValues.notes.trim() || null
|
||||||
|
};
|
||||||
|
|
||||||
|
onSave(updatedSetting);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Spreader</label>
|
||||||
|
<select
|
||||||
|
value={editValues.equipmentId}
|
||||||
|
onChange={(e) => setEditValues({ ...editValues, equipmentId: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
||||||
|
>
|
||||||
|
<option value="">Select equipment...</option>
|
||||||
|
{availableSpreaders.map(spreader => (
|
||||||
|
<option key={spreader.id} value={spreader.id}>
|
||||||
|
{spreader.name} ({spreader.manufacturer} {spreader.model})
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Setting Value *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editValues.settingValue}
|
||||||
|
onChange={(e) => setEditValues({ ...editValues, settingValue: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
||||||
|
placeholder="e.g., 3.5, H, 15"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Rate Description</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editValues.rateDescription}
|
||||||
|
onChange={(e) => setEditValues({ ...editValues, rateDescription: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
||||||
|
placeholder="e.g., 1 lb nitrogen per 1000 sq ft"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Notes</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editValues.notes}
|
||||||
|
onChange={(e) => setEditValues({ ...editValues, notes: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
||||||
|
placeholder="Additional notes..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 pt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={!editValues.settingValue.trim()}
|
||||||
|
className="px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 disabled:bg-gray-300"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
className="px-3 py-1 bg-gray-500 text-white rounded text-sm hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<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-lg max-h-[90vh] overflow-y-auto">
|
<div className="bg-white rounded-lg p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto">
|
||||||
@@ -1172,21 +1296,39 @@ const EditProductModal = ({ product, onSubmit, onCancel, sharedProducts, categor
|
|||||||
<h4 className="text-sm font-medium text-gray-700 mb-2">Current Settings:</h4>
|
<h4 className="text-sm font-medium text-gray-700 mb-2">Current Settings:</h4>
|
||||||
{editSpreaderSettings.map((setting, index) => (
|
{editSpreaderSettings.map((setting, index) => (
|
||||||
<div key={index} className="bg-gray-50 p-3 rounded border text-sm">
|
<div key={index} className="bg-gray-50 p-3 rounded border text-sm">
|
||||||
<div className="flex justify-between items-start">
|
{editingSettingIndex === index ? (
|
||||||
<div>
|
<EditSpreaderSettingForm
|
||||||
<div><strong>{setting.equipmentName || `${setting.spreaderBrand}${setting.spreaderModel ? ` (${setting.spreaderModel})` : ''}`}</strong></div>
|
setting={setting}
|
||||||
<div>Setting: <span className="font-medium">{setting.settingValue}</span></div>
|
availableSpreaders={availableEditSpreaders}
|
||||||
{setting.rateDescription && <div className="text-gray-600">{setting.rateDescription}</div>}
|
onSave={(updatedSetting) => handleSaveEditSetting(index, updatedSetting)}
|
||||||
{setting.notes && <div className="text-gray-600 italic">{setting.notes}</div>}
|
onCancel={handleCancelEditSetting}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<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>}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleStartEditSetting(index)}
|
||||||
|
className="text-blue-600 hover:text-blue-800 text-sm"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveExistingSpreaderSetting(index)}
|
||||||
|
className="text-red-600 hover:text-red-800 text-sm"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
)}
|
||||||
type="button"
|
|
||||||
onClick={() => handleRemoveExistingSpreaderSetting(index)}
|
|
||||||
className="text-red-600 hover:text-red-800 text-sm"
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user