import React, { useState, useEffect } from 'react'; import { BeakerIcon, MagnifyingGlassIcon, PlusIcon, PencilIcon, TrashIcon, ExclamationTriangleIcon, TagIcon, ArrowUpIcon, InformationCircleIcon } from '@heroicons/react/24/outline'; import { adminAPI, productsAPI } from '../../services/api'; import LoadingSpinner from '../../components/UI/LoadingSpinner'; import toast from 'react-hot-toast'; const AdminProducts = () => { const [products, setProducts] = useState([]); const [userProducts, setUserProducts] = useState([]); const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [categoryFilter, setCategoryFilter] = useState('all'); const [typeFilter, setTypeFilter] = useState('all'); const [productTypeFilter, setProductTypeFilter] = useState('all'); const [selectedProduct, setSelectedProduct] = useState(null); 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: '', categoryId: '', productType: 'granular', activeIngredients: '', description: '', rates: [{ applicationType: 'granular', rateAmount: '', rateUnit: 'lbs/1000 sq ft', notes: '' }] }); useEffect(() => { fetchData(); }, [searchTerm, categoryFilter, typeFilter]); const fetchData = async () => { try { setLoading(true); const [productsResponse, userProductsResponse, categoriesResponse] = await Promise.all([ adminAPI.getProducts({ search: searchTerm, category: categoryFilter !== 'all' ? categoryFilter : '', type: typeFilter !== 'all' ? typeFilter : '' }), adminAPI.getAllUserProducts({ search: searchTerm, category: categoryFilter !== 'all' ? categoryFilter : '' }), productsAPI.getCategories() ]); setProducts(productsResponse.data.data.products || []); setUserProducts(userProductsResponse.data.data.userProducts || []); setCategories(categoriesResponse.data.data.categories || []); } catch (error) { console.error('Failed to fetch products:', error); toast.error('Failed to load products'); } finally { setLoading(false); } }; const handleCreate = async (e) => { e.preventDefault(); try { await adminAPI.createProduct(formData); toast.success('Product created successfully'); setShowCreateModal(false); resetForm(); fetchData(); } catch (error) { console.error('Failed to create product:', error); toast.error('Failed to create product'); } }; const handleUpdate = async (e) => { e.preventDefault(); try { await adminAPI.updateProduct(selectedProduct.id, formData); toast.success('Product updated successfully'); setShowEditModal(false); resetForm(); fetchData(); } catch (error) { console.error('Failed to update product:', error); toast.error('Failed to update product'); } }; const handleDelete = async () => { try { if (selectedProduct.isShared) { await adminAPI.deleteProduct(selectedProduct.id); } else { await productsAPI.deleteUserProduct(selectedProduct.id); } toast.success('Product deleted successfully'); setShowDeleteModal(false); setSelectedProduct(null); fetchData(); } catch (error) { console.error('Failed to delete product:', error); toast.error('Failed to delete product'); } }; const handlePromoteToShared = async (userProduct) => { try { await adminAPI.promoteUserProduct(userProduct.id); toast.success(`"${userProduct.customName}" promoted to shared product`); fetchData(); // Refresh the data } catch (error) { console.error('Failed to promote product:', error); toast.error('Failed to promote product to shared'); } }; 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: '', brand: '', categoryId: '', productType: 'granular', activeIngredients: '', description: '', rates: [{ applicationType: 'granular', rateAmount: '', rateUnit: 'lbs/1000 sq ft', notes: '' }] }); setSelectedProduct(null); }; const openEditModal = (product) => { setSelectedProduct(product); setFormData({ name: product.name || product.customName || '', brand: product.brand || product.customBrand || '', categoryId: product.categoryId || '', productType: product.productType || product.customProductType || 'granular', activeIngredients: product.activeIngredients || product.customActiveIngredients || '', description: product.description || product.customDescription || '', rates: product.rates && product.rates.length > 0 ? product.rates : [{ applicationType: product.productType || 'granular', rateAmount: product.customRateAmount || '', rateUnit: product.customRateUnit || 'lbs/1000 sq ft', notes: '' }] }); setShowEditModal(true); }; const addRate = () => { setFormData(prev => ({ ...prev, rates: [...prev.rates, { applicationType: prev.productType, rateAmount: '', rateUnit: prev.productType === 'granular' ? 'lbs/1000 sq ft' : 'oz/1000 sq ft', notes: '' }] })); }; const removeRate = (index) => { setFormData(prev => ({ ...prev, rates: prev.rates.filter((_, i) => i !== index) })); }; const updateRate = (index, field, value) => { setFormData(prev => ({ ...prev, rates: prev.rates.map((rate, i) => i === index ? { ...rate, [field]: value } : rate ) })); }; // Combine shared and user products based on filter const allProducts = [ ...(productTypeFilter === 'custom' ? [] : products.map(p => ({ ...p, isShared: true }))), ...(productTypeFilter === 'shared' ? [] : userProducts.map(p => ({ ...p, isShared: false }))) ]; const ProductForm = ({ onSubmit, submitText }) => (
); return (Add, edit, and manage all lawn care products
| Product | Type | Category | Source | Owner | Usage | Actions |
|---|---|---|---|---|---|---|
|
{product.name || product.customName}
{product.brand || product.customBrand}
|
{product.productType} | {product.categoryName || 'Uncategorized'} | {product.isShared ? 'Shared' : 'Custom'} |
{product.isShared ? (
System
) : (
{product.userName}
{product.userEmail}
|
{product.isShared ? (
{product.rateCount || 0} rates
{product.usageCount || 0} users
{product.spreaderSettingsCount || 0} spreader settings
{product.usageCount || 0} applications
|
{!product.isShared && (
)}
|
Are you sure you want to delete "{selectedProduct.name || selectedProduct.customName}"? This action cannot be undone.
| Type | Rate | Unit | Notes |
|---|---|---|---|
| {rate.applicationType} | {rate.rateAmount} | {rate.rateUnit} | {rate.notes || 'No notes'} |
No application rates defined for this product.
)}| Equipment | Spreader | Setting | Custom Rate | Notes |
|---|---|---|---|---|
| {setting.equipmentName} | {setting.brandName} {setting.model} | {setting.settingNumber || 'N/A'} | {setting.customRate ? `${setting.customRate} ${setting.customRateUnit}` : 'Standard'} | {setting.notes || 'No notes'} |
No spreader settings configured for this product.
)}