diff --git a/backend/scripts/seed-products.sql b/backend/scripts/seed-products.sql new file mode 100644 index 0000000..3b153b8 --- /dev/null +++ b/backend/scripts/seed-products.sql @@ -0,0 +1,176 @@ +-- Seed products and categories for TurfTracker +-- Based on common lawn care chemicals and fertilizers + +-- Insert product categories +INSERT INTO product_categories (name, description) VALUES +('Herbicides', 'Products for weed control and prevention'), +('Fertilizers', 'Nutrients for lawn growth and health'), +('Fungicides', 'Products for disease prevention and treatment'), +('Insecticides', 'Products for insect control'), +('Pre-emergent', 'Products that prevent weeds from germinating'), +('Post-emergent', 'Products that kill existing weeds'), +('Growth Regulators', 'Products that modify plant growth') +ON CONFLICT (name) DO NOTHING; + +-- Get category IDs for reference +DO $$ +DECLARE + herbicide_cat_id INTEGER; + fertilizer_cat_id INTEGER; + fungicide_cat_id INTEGER; + insecticide_cat_id INTEGER; + pre_emergent_cat_id INTEGER; + post_emergent_cat_id INTEGER; + growth_reg_cat_id INTEGER; +BEGIN + SELECT id INTO herbicide_cat_id FROM product_categories WHERE name = 'Herbicides'; + SELECT id INTO fertilizer_cat_id FROM product_categories WHERE name = 'Fertilizers'; + SELECT id INTO fungicide_cat_id FROM product_categories WHERE name = 'Fungicides'; + SELECT id INTO insecticide_cat_id FROM product_categories WHERE name = 'Insecticides'; + SELECT id INTO pre_emergent_cat_id FROM product_categories WHERE name = 'Pre-emergent'; + SELECT id INTO post_emergent_cat_id FROM product_categories WHERE name = 'Post-emergent'; + SELECT id INTO growth_reg_cat_id FROM product_categories WHERE name = 'Growth Regulators'; + + -- Insert herbicides + INSERT INTO products (name, brand, category_id, product_type, active_ingredients, description) VALUES + ('Tenacity', 'Syngenta', herbicide_cat_id, 'liquid', 'Mesotrione 40%', 'Selective herbicide for crabgrass and broadleaf weeds. Safe for new seeding.'), + ('Prodiamine 65WDG', 'Barricade', pre_emergent_cat_id, 'granular', 'Prodiamine 65%', 'Pre-emergent herbicide for crabgrass and annual grass control'), + ('2,4-D Amine', 'Various', post_emergent_cat_id, 'liquid', '2,4-D Dimethylamine Salt 38.3%', 'Selective broadleaf herbicide for dandelions, clover, and other weeds'), + ('Triclopyr 4E', 'Garlon', herbicide_cat_id, 'liquid', 'Triclopyr 61.6%', 'Selective herbicide for woody plants and tough broadleaf weeds'), + ('Glyphosate 41%', 'Roundup Pro', herbicide_cat_id, 'liquid', 'Glyphosate 41%', 'Non-selective systemic herbicide for total vegetation control'), + ('3-Way Herbicide', 'Various', post_emergent_cat_id, 'liquid', '2,4-D + Dicamba + MCPP', 'Combination herbicide for broadleaf weed control in lawns'), + ('Dismiss', 'FMC', herbicide_cat_id, 'granular', 'Sulfentrazone 75%', 'Selective herbicide for sedge and certain broadleaf weeds'), + ('Sedgehammer', 'Gowan', herbicide_cat_id, 'granular', 'Halosulfuron-methyl 75%', 'Selective post-emergent herbicide for sedges'), + ('Pendimethalin 3.3EC', 'Pendulum', pre_emergent_cat_id, 'liquid', 'Pendimethalin 38.7%', 'Pre-emergent herbicide for annual grasses and certain broadleaf weeds'), + ('Quinclorac 75WP', 'Drive', herbicide_cat_id, 'granular', 'Quinclorac 75%', 'Selective post-emergent herbicide for crabgrass and other annual grasses') + ON CONFLICT (name, brand) DO NOTHING; + + -- Insert fertilizers + INSERT INTO products (name, brand, category_id, product_type, active_ingredients, description) VALUES + ('Milorganite 6-4-0', 'Milorganite', fertilizer_cat_id, 'granular', 'Nitrogen 6%, Phosphorus 4%, Iron 2.5%', 'Organic slow-release nitrogen fertilizer with iron'), + ('32-0-4 Slow Release', 'Various', fertilizer_cat_id, 'granular', 'Nitrogen 32%, Potassium 4%', 'High nitrogen slow-release fertilizer for rapid greening'), + ('24-0-11', 'Various', fertilizer_cat_id, 'granular', 'Nitrogen 24%, Potassium 11%', 'Balanced fertilizer with potassium for root development'), + ('Liquid Nitrogen 28-0-0', 'Various', fertilizer_cat_id, 'liquid', 'Urea Ammonium Nitrate 28%', 'Fast-acting liquid nitrogen for quick green-up'), + ('Iron Sulfate 20%', 'Various', fertilizer_cat_id, 'granular', 'Iron 20%', 'Iron supplement for chlorosis correction and green color'), + ('Starter Fertilizer 18-24-12', 'Various', fertilizer_cat_id, 'granular', 'Nitrogen 18%, Phosphorus 24%, Potassium 12%', 'High phosphorus fertilizer for new seeding and sodding'), + ('Organic Lawn Food 10-0-2', 'Espoma', fertilizer_cat_id, 'granular', 'Nitrogen 10%, Potassium 2%', 'Organic slow-release fertilizer with beneficial microbes'), + ('Liquid Iron 6%', 'Various', fertilizer_cat_id, 'liquid', 'Iron 6%', 'Chelated iron for quick correction of iron deficiency'), + ('Winterizer 13-0-44', 'Various', fertilizer_cat_id, 'granular', 'Nitrogen 13%, Potassium 44%', 'High potassium fertilizer for winter preparation'), + ('Urea 46-0-0', 'Various', fertilizer_cat_id, 'granular', 'Nitrogen 46%', 'Fast-release nitrogen fertilizer for quick feeding') + ON CONFLICT (name, brand) DO NOTHING; + + -- Insert fungicides + INSERT INTO products (name, brand, category_id, product_type, active_ingredients, description) VALUES + ('Propiconazole 14.3', 'Banner Maxx', fungicide_cat_id, 'liquid', 'Propiconazole 14.3%', 'Systemic fungicide for brown patch, dollar spot, and other diseases'), + ('Azoxystrobin 22.9%', 'Heritage', fungicide_cat_id, 'liquid', 'Azoxystrobin 22.9%', 'Strobilurin fungicide for broad spectrum disease control'), + ('Thiophanate-methyl 70WP', 'Cleary''s 3336', fungicide_cat_id, 'granular', 'Thiophanate-methyl 70%', 'Systemic fungicide for summer patch and other diseases'), + ('Myclobutanil 20EW', 'Eagle', fungicide_cat_id, 'liquid', 'Myclobutanil 20%', 'Systemic fungicide for powdery mildew and rust'), + ('Chlorothalonil 82.5%', 'Daconil Ultrex', fungicide_cat_id, 'granular', 'Chlorothalonil 82.5%', 'Contact fungicide for preventive disease control'), + ('Iprodione 2SE', 'Chipco 26019', fungicide_cat_id, 'liquid', 'Iprodione 23.3%', 'Contact fungicide for dollar spot and brown patch'), + ('Fludioxonil 1.12G', 'Medallion', fungicide_cat_id, 'granular', 'Fludioxonil 1.12%', 'Granular fungicide for pythium and other soil-borne diseases') + ON CONFLICT (name, brand) DO NOTHING; + + -- Insert insecticides + INSERT INTO products (name, brand, category_id, product_type, active_ingredients, description) VALUES + ('Imidacloprid 75WP', 'Merit', insecticide_cat_id, 'granular', 'Imidacloprid 75%', 'Systemic insecticide for grub control and surface insects'), + ('Bifenthrin 7.9%', 'Talstar P', insecticide_cat_id, 'liquid', 'Bifenthrin 7.9%', 'Broad spectrum insecticide for surface insects and grubs'), + ('Carbaryl 80WP', 'Sevin', insecticide_cat_id, 'granular', 'Carbaryl 80%', 'Contact insecticide for surface insects and caterpillars'), + ('Chlorantraniliprole 0.2G', 'Acelepryn', insecticide_cat_id, 'granular', 'Chlorantraniliprole 0.2%', 'Long-lasting grub control and surface insect control'), + ('Lambda-cyhalothrin 9.7%', 'Demand CS', insecticide_cat_id, 'liquid', 'Lambda-cyhalothrin 9.7%', 'Fast-acting insecticide for immediate insect control'), + ('Thiamethoxam 25WG', 'Meridian', insecticide_cat_id, 'granular', 'Thiamethoxam 25%', 'Systemic insecticide for grub prevention and surface insects') + ON CONFLICT (name, brand) DO NOTHING; + +END $$; + +-- Insert application rates for key products +DO $$ +DECLARE + product_record RECORD; +BEGIN + -- Tenacity rates + SELECT id INTO product_record FROM products WHERE name = 'Tenacity' LIMIT 1; + IF FOUND THEN + INSERT INTO product_rates (product_id, application_type, rate_amount, rate_unit, notes) VALUES + (product_record.id, 'Pre-emergent', 5, 'fl oz/acre', 'Apply before weed germination'), + (product_record.id, 'Post-emergent', 8, 'fl oz/acre', 'Apply to actively growing weeds'), + (product_record.id, 'At seeding', 4, 'fl oz/acre', 'Safe to apply at time of seeding') + ON CONFLICT DO NOTHING; + END IF; + + -- Prodiamine rates + SELECT id INTO product_record FROM products WHERE name = 'Prodiamine 65WDG' LIMIT 1; + IF FOUND THEN + INSERT INTO product_rates (product_id, application_type, rate_amount, rate_unit, notes) VALUES + (product_record.id, 'Crabgrass prevention', 0.83, 'lbs/acre', 'Apply before soil temperature reaches 55°F'), + (product_record.id, 'Annual grass control', 1.1, 'lbs/acre', 'Higher rate for better control') + ON CONFLICT DO NOTHING; + END IF; + + -- Milorganite rates + SELECT id INTO product_record FROM products WHERE name = 'Milorganite 6-4-0' LIMIT 1; + IF FOUND THEN + INSERT INTO product_rates (product_id, application_type, rate_amount, rate_unit, notes) VALUES + (product_record.id, 'Spring feeding', 32, 'lbs/1000 sq ft', 'Apply when grass begins active growth'), + (product_record.id, 'Summer feeding', 32, 'lbs/1000 sq ft', 'Safe for summer application'), + (product_record.id, 'Fall feeding', 32, 'lbs/1000 sq ft', 'Builds root system for winter') + ON CONFLICT DO NOTHING; + END IF; + + -- 32-0-4 rates + SELECT id INTO product_record FROM products WHERE name = '32-0-4 Slow Release' LIMIT 1; + IF FOUND THEN + INSERT INTO product_rates (product_id, application_type, rate_amount, rate_unit, notes) VALUES + (product_record.id, 'Spring application', 3.125, 'lbs/1000 sq ft', '1 lb nitrogen per 1000 sq ft'), + (product_record.id, 'Summer application', 2.5, 'lbs/1000 sq ft', '0.8 lbs nitrogen per 1000 sq ft'), + (product_record.id, 'Fall application', 3.75, 'lbs/1000 sq ft', '1.2 lbs nitrogen per 1000 sq ft') + ON CONFLICT DO NOTHING; + END IF; + + -- Liquid Nitrogen rates + SELECT id INTO product_record FROM products WHERE name = 'Liquid Nitrogen 28-0-0' LIMIT 1; + IF FOUND THEN + INSERT INTO product_rates (product_id, application_type, rate_amount, rate_unit, notes) VALUES + (product_record.id, 'Quick green-up', 2, 'fl oz/1000 sq ft', 'Light application for color'), + (product_record.id, 'Regular feeding', 4, 'fl oz/1000 sq ft', '0.25 lbs nitrogen per 1000 sq ft'), + (product_record.id, 'Heavy feeding', 6, 'fl oz/1000 sq ft', '0.5 lbs nitrogen per 1000 sq ft') + ON CONFLICT DO NOTHING; + END IF; + + -- 2,4-D rates + SELECT id INTO product_record FROM products WHERE name = '2,4-D Amine' LIMIT 1; + IF FOUND THEN + INSERT INTO product_rates (product_id, application_type, rate_amount, rate_unit, notes) VALUES + (product_record.id, 'Broadleaf weeds', 1, 'fl oz/1000 sq ft', 'Apply to actively growing weeds'), + (product_record.id, 'Spot treatment', 2, 'fl oz/gallon', 'For hand sprayer applications') + ON CONFLICT DO NOTHING; + END IF; + + -- Merit rates + SELECT id INTO product_record FROM products WHERE name = 'Imidacloprid 75WP' LIMIT 1; + IF FOUND THEN + INSERT INTO product_rates (product_id, application_type, rate_amount, rate_unit, notes) VALUES + (product_record.id, 'Grub prevention', 0.15, 'lbs/1000 sq ft', 'Apply in late spring/early summer'), + (product_record.id, 'Surface insects', 0.1, 'lbs/1000 sq ft', 'Lower rate for surface feeding insects') + ON CONFLICT DO NOTHING; + END IF; + + -- Propiconazole rates + SELECT id INTO product_record FROM products WHERE name = 'Propiconazole 14.3' LIMIT 1; + IF FOUND THEN + INSERT INTO product_rates (product_id, application_type, rate_amount, rate_unit, notes) VALUES + (product_record.id, 'Brown patch', 1, 'fl oz/1000 sq ft', 'Apply at first sign of disease'), + (product_record.id, 'Dollar spot', 0.5, 'fl oz/1000 sq ft', 'Lower rate for dollar spot control'), + (product_record.id, 'Preventive', 0.75, 'fl oz/1000 sq ft', 'Apply before disease pressure') + ON CONFLICT DO NOTHING; + END IF; + +END $$; + +-- Create some common product combinations that users often mix +INSERT INTO products (name, brand, category_id, product_type, active_ingredients, description) VALUES +('Tank Mix: 2,4-D + Triclopyr', 'Custom', post_emergent_cat_id, 'liquid', '2,4-D + Triclopyr', 'Common tank mix for tough broadleaf and woody weeds'), +('Tank Mix: Prodiamine + Fertilizer', 'Custom', pre_emergent_cat_id, 'granular', 'Prodiamine + 19-0-6', 'Pre-emergent with slow-release fertilizer'), +('Tank Mix: Fungicide + Insecticide', 'Custom', fungicide_cat_id, 'liquid', 'Propiconazole + Bifenthrin', 'Disease and insect control combination') +ON CONFLICT (name, brand) DO NOTHING; + +COMMIT; \ No newline at end of file diff --git a/frontend/src/pages/Products/Products.js b/frontend/src/pages/Products/Products.js index 8d6d0fc..fd5de9e 100644 --- a/frontend/src/pages/Products/Products.js +++ b/frontend/src/pages/Products/Products.js @@ -1,11 +1,471 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { + PlusIcon, + MagnifyingGlassIcon, + FunnelIcon, + BeakerIcon, + TrashIcon, + PencilIcon +} from '@heroicons/react/24/outline'; +import { productsAPI } from '../../services/api'; +import LoadingSpinner from '../../components/UI/LoadingSpinner'; +import toast from 'react-hot-toast'; const Products = () => { + const [sharedProducts, setSharedProducts] = useState([]); + const [userProducts, setUserProducts] = useState([]); + const [categories, setCategories] = useState([]); + const [loading, setLoading] = useState(true); + const [showCreateForm, setShowCreateForm] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + const [selectedCategory, setSelectedCategory] = useState(''); + const [selectedType, setSelectedType] = useState(''); + const [activeTab, setActiveTab] = useState('shared'); // 'shared' or 'custom' + + useEffect(() => { + fetchData(); + }, []); + + const fetchData = async () => { + try { + setLoading(true); + const [productsResponse, categoriesResponse] = await Promise.all([ + productsAPI.getAll({ + category: selectedCategory, + type: selectedType, + search: searchTerm + }), + productsAPI.getCategories() + ]); + + setSharedProducts(productsResponse.data.data.sharedProducts || []); + setUserProducts(productsResponse.data.data.userProducts || []); + setCategories(categoriesResponse.data.data.categories || []); + } catch (error) { + console.error('Failed to fetch products:', error); + toast.error('Failed to load products'); + setSharedProducts([]); + setUserProducts([]); + } finally { + setLoading(false); + } + }; + + const handleSearch = (e) => { + setSearchTerm(e.target.value); + // Debounce search + setTimeout(() => { + fetchData(); + }, 300); + }; + + const handleFilterChange = () => { + fetchData(); + }; + + const handleCreateProduct = async (productData) => { + try { + await productsAPI.createUserProduct(productData); + toast.success('Custom product created successfully!'); + setShowCreateForm(false); + fetchData(); + } catch (error) { + console.error('Failed to create product:', error); + toast.error('Failed to create product'); + } + }; + + const handleDeleteUserProduct = async (productId) => { + if (window.confirm('Are you sure you want to delete this custom product?')) { + try { + await productsAPI.deleteUserProduct(productId); + toast.success('Product deleted successfully'); + fetchData(); + } catch (error) { + console.error('Failed to delete product:', error); + toast.error('Failed to delete product'); + } + } + }; + + const ProductCard = ({ product, isUserProduct = false }) => ( +
{product.brand}
+ )} + + {product.productType || 'Unknown'} + ++ Category: {product.categoryName} +
+ )} + + {product.activeIngredients && ( ++ Active Ingredients: {product.activeIngredients} +
+ )} + + {/* Application Rates */} + {isUserProduct && product.customRateAmount && ( +Your Rate:
++ {product.customRateAmount} {product.customRateUnit} +
+Application Rates:
+ {product.rates.slice(0, 2).map((rate, index) => ( ++ +{product.rates.length - 2} more rates +
+ )} ++ Notes: {product.notes} +
+ )} + + {!isUserProduct && ( +Product management coming soon...
+Manage your lawn care products and application rates
+Try adjusting your search or filters
+Create your own products with custom rates and notes
+ +