admin
This commit is contained in:
@@ -7,7 +7,8 @@ import {
|
||||
TrashIcon,
|
||||
ExclamationTriangleIcon,
|
||||
TagIcon,
|
||||
ArrowUpIcon
|
||||
ArrowUpIcon,
|
||||
InformationCircleIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { adminAPI, productsAPI } from '../../services/api';
|
||||
import LoadingSpinner from '../../components/UI/LoadingSpinner';
|
||||
@@ -26,6 +27,8 @@ const AdminProducts = () => {
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showDetailsModal, setShowDetailsModal] = useState(false);
|
||||
const [productDetails, setProductDetails] = useState({ rates: [], spreaderSettings: [] });
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
brand: '',
|
||||
@@ -123,6 +126,30 @@ const AdminProducts = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const showProductDetails = async (product) => {
|
||||
try {
|
||||
setSelectedProduct(product);
|
||||
let rates = [];
|
||||
let spreaderSettings = [];
|
||||
|
||||
if (product.isShared) {
|
||||
// Fetch application rates for shared products
|
||||
const ratesResponse = await adminAPI.getProductRates(product.id);
|
||||
rates = ratesResponse.data.data.rates || [];
|
||||
} else {
|
||||
// Fetch spreader settings for user products
|
||||
const settingsResponse = await adminAPI.getUserProductSpreaderSettings(product.id);
|
||||
spreaderSettings = settingsResponse.data.data.spreaderSettings || [];
|
||||
}
|
||||
|
||||
setProductDetails({ rates, spreaderSettings });
|
||||
setShowDetailsModal(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch product details:', error);
|
||||
toast.error('Failed to load product details');
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
name: '',
|
||||
@@ -508,6 +535,13 @@ const AdminProducts = () => {
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => showProductDetails(product)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
title="View Rates & Settings"
|
||||
>
|
||||
<InformationCircleIcon className="h-4 w-4" />
|
||||
</button>
|
||||
{!product.isShared && (
|
||||
<button
|
||||
onClick={() => handlePromoteToShared(product)}
|
||||
@@ -605,6 +639,114 @@ const AdminProducts = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Product Details Modal */}
|
||||
{showDetailsModal && selectedProduct && (
|
||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div className="relative top-10 mx-auto p-5 border w-full max-w-4xl shadow-lg rounded-md bg-white">
|
||||
<div className="mt-3">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
||||
{selectedProduct.isShared ? 'Application Rates' : 'Spreader Settings'} - {selectedProduct.name || selectedProduct.customName}
|
||||
</h3>
|
||||
|
||||
{selectedProduct.isShared ? (
|
||||
<div>
|
||||
<h4 className="text-md font-semibold text-gray-800 mb-3">Application Rates ({productDetails.rates.length})</h4>
|
||||
{productDetails.rates.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Rate</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Unit</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{productDetails.rates.map((rate, index) => (
|
||||
<tr key={index} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
{rate.applicationType}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{rate.rateAmount}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{rate.rateUnit}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-900">
|
||||
{rate.notes || 'No notes'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500 text-center py-8">No application rates defined for this product.</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<h4 className="text-md font-semibold text-gray-800 mb-3">Spreader Settings ({productDetails.spreaderSettings.length})</h4>
|
||||
{productDetails.spreaderSettings.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<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">Notes</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}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{setting.brandName} {setting.model}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{setting.settingNumber || 'N/A'}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{setting.customRate ? `${setting.customRate} ${setting.customRateUnit}` : 'Standard'}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-900">
|
||||
{setting.notes || 'No notes'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500 text-center py-8">No spreader settings configured for this product.</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end mt-6">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowDetailsModal(false);
|
||||
setSelectedProduct(null);
|
||||
setProductDetails({ rates: [], spreaderSettings: [] });
|
||||
}}
|
||||
className="px-4 py-2 text-sm text-gray-700 bg-gray-200 rounded hover:bg-gray-300"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -214,6 +214,8 @@ export const adminAPI = {
|
||||
updateProduct: (id, productData) => apiClient.put(`/admin/products/${id}`, productData),
|
||||
deleteProduct: (id) => apiClient.delete(`/admin/products/${id}`),
|
||||
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`),
|
||||
|
||||
// Equipment management
|
||||
getAllUserEquipment: (params) => apiClient.get('/admin/equipment/user', { params }),
|
||||
|
||||
Reference in New Issue
Block a user