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 [showAddSettingForm, setShowAddSettingForm] = useState(false); const [newSettingData, setNewSettingData] = useState({ equipmentId: '', spreaderBrand: '', spreaderModel: '', settingValue: '', rateDescription: '', notes: '' }); const [formData, setFormData] = useState({ name: '', brand: '', categoryId: '', productType: 'granular', activeIngredients: '', description: '', seedBlend: [], 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 { try { await productsAPI.deleteUserProduct(selectedProduct.id); } catch (e) { const msg = e?.response?.data?.message || ''; if (msg.includes('used in applications')) { // Fallback to archive instead of delete await productsAPI.archiveUserProduct(selectedProduct.id); toast('Product archived instead of deleted (in use)'); } else { throw e; } } } toast.success('Product deleted successfully'); setShowDeleteModal(false); setSelectedProduct(null); fetchData(); } catch (error) { console.error('Failed to delete product:', error); toast.error(error?.response?.data?.message || '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 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: '', brand: '', categoryId: '', productType: 'granular', activeIngredients: '', description: '', seedBlend: [], 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 || '', seedBlend: product.seedBlend || [], 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 Value | Rate Description | Notes | Actions |
|---|---|---|---|---|---|
| {setting.equipmentName || 'Generic Equipment'} | {setting.spreaderBrand} {setting.spreaderModel} | {setting.settingValue || 'N/A'} | {setting.rateDescription || 'Not specified'} | {setting.notes || 'No notes'} |
No spreader settings configured for this product.
)} {/* Add Setting Button */}