This commit is contained in:
Jake Kasper
2025-08-29 08:41:43 -04:00
parent cb160f21cc
commit e5275398ea
3 changed files with 256 additions and 17 deletions

View File

@@ -29,6 +29,15 @@ const AdminProducts = () => {
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showDetailsModal, setShowDetailsModal] = useState(false);
const [productDetails, setProductDetails] = useState({ rates: [], spreaderSettings: [] });
const [showAddSettingForm, setShowAddSettingForm] = useState(false);
const [newSettingData, setNewSettingData] = useState({
equipmentId: '',
spreaderBrand: '',
spreaderModel: '',
settingValue: '',
rateDescription: '',
notes: ''
});
const [formData, setFormData] = useState({
name: '',
brand: '',
@@ -150,6 +159,40 @@ const AdminProducts = () => {
}
};
const handleAddSpreaderSetting = async (e) => {
e.preventDefault();
try {
await adminAPI.addUserProductSpreaderSetting(selectedProduct.id, newSettingData);
toast.success('Spreader setting added successfully');
setShowAddSettingForm(false);
setNewSettingData({
equipmentId: '',
spreaderBrand: '',
spreaderModel: '',
settingValue: '',
rateDescription: '',
notes: ''
});
// Refresh the settings
showProductDetails(selectedProduct);
} catch (error) {
console.error('Failed to add spreader setting:', error);
toast.error('Failed to add spreader setting');
}
};
const handleDeleteSpreaderSetting = async (settingId) => {
try {
await adminAPI.deleteUserProductSpreaderSetting(settingId);
toast.success('Spreader setting deleted successfully');
// Refresh the settings
showProductDetails(selectedProduct);
} catch (error) {
console.error('Failed to delete spreader setting:', error);
toast.error('Failed to delete spreader setting');
}
};
const resetForm = () => {
setFormData({
name: '',
@@ -697,29 +740,39 @@ const AdminProducts = () => {
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Equipment</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Spreader</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Setting</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Custom Rate</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Setting Value</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Rate Description</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Notes</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{productDetails.spreaderSettings.map((setting, index) => (
<tr key={index} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{setting.equipmentName}
{setting.equipmentName || 'Generic Equipment'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{setting.brandName} {setting.model}
{setting.spreaderBrand} {setting.spreaderModel}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{setting.settingNumber || 'N/A'}
{setting.settingValue || 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{setting.customRate ? `${setting.customRate} ${setting.customRateUnit}` : 'Standard'}
{setting.rateDescription || 'Not specified'}
</td>
<td className="px-6 py-4 text-sm text-gray-900">
{setting.notes || 'No notes'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button
onClick={() => handleDeleteSpreaderSetting(setting.id)}
className="text-red-600 hover:text-red-900"
title="Delete Setting"
>
<TrashIcon className="h-4 w-4" />
</button>
</td>
</tr>
))}
</tbody>
@@ -728,6 +781,105 @@ const AdminProducts = () => {
) : (
<p className="text-gray-500 text-center py-8">No spreader settings configured for this product.</p>
)}
{/* Add Setting Button */}
<div className="mt-4">
{!showAddSettingForm ? (
<button
onClick={() => setShowAddSettingForm(true)}
className="btn-primary flex items-center text-sm"
>
<PlusIcon className="h-4 w-4 mr-1" />
Add Spreader Setting
</button>
) : (
<div className="border rounded-lg p-4 bg-gray-50">
<h5 className="text-sm font-semibold text-gray-800 mb-3">Add New Spreader Setting</h5>
<form onSubmit={handleAddSpreaderSetting} className="space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Spreader Brand</label>
<input
type="text"
required
value={newSettingData.spreaderBrand}
onChange={(e) => setNewSettingData({ ...newSettingData, spreaderBrand: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
placeholder="e.g. Scotts, TruGreen"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Spreader Model</label>
<input
type="text"
value={newSettingData.spreaderModel}
onChange={(e) => setNewSettingData({ ...newSettingData, spreaderModel: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
placeholder="e.g. EdgeGuard, DLX"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Setting Value</label>
<input
type="text"
value={newSettingData.settingValue}
onChange={(e) => setNewSettingData({ ...newSettingData, settingValue: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
placeholder="e.g. 3.5, M, Large"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Rate Description</label>
<input
type="text"
value={newSettingData.rateDescription}
onChange={(e) => setNewSettingData({ ...newSettingData, rateDescription: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
placeholder="e.g. 2.5 lbs/1000 sq ft"
/>
</div>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Notes</label>
<textarea
value={newSettingData.notes}
onChange={(e) => setNewSettingData({ ...newSettingData, notes: e.target.value })}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
rows="2"
placeholder="Additional notes or instructions"
/>
</div>
<div className="flex justify-end space-x-2">
<button
type="button"
onClick={() => {
setShowAddSettingForm(false);
setNewSettingData({
equipmentId: '',
spreaderBrand: '',
spreaderModel: '',
settingValue: '',
rateDescription: '',
notes: ''
});
}}
className="px-3 py-1 text-xs text-gray-700 bg-gray-200 rounded hover:bg-gray-300"
>
Cancel
</button>
<button
type="submit"
className="px-3 py-1 text-xs text-white bg-blue-600 rounded hover:bg-blue-700"
>
Add Setting
</button>
</div>
</form>
</div>
)}
</div>
</div>
)}
@@ -737,6 +889,15 @@ const AdminProducts = () => {
setShowDetailsModal(false);
setSelectedProduct(null);
setProductDetails({ rates: [], spreaderSettings: [] });
setShowAddSettingForm(false);
setNewSettingData({
equipmentId: '',
spreaderBrand: '',
spreaderModel: '',
settingValue: '',
rateDescription: '',
notes: ''
});
}}
className="px-4 py-2 text-sm text-gray-700 bg-gray-200 rounded hover:bg-gray-300"
>

View File

@@ -216,6 +216,8 @@ export const adminAPI = {
promoteUserProduct: (id) => apiClient.post(`/admin/products/user/${id}/promote`),
getProductRates: (id) => apiClient.get(`/admin/products/${id}/rates`),
getUserProductSpreaderSettings: (id) => apiClient.get(`/admin/products/user/${id}/spreader-settings`),
addUserProductSpreaderSetting: (id, settingData) => apiClient.post(`/admin/products/user/${id}/spreader-settings`, settingData),
deleteUserProductSpreaderSetting: (id) => apiClient.delete(`/admin/products/user/spreader-settings/${id}`),
// Equipment management
getAllUserEquipment: (params) => apiClient.get('/admin/equipment/user', { params }),