From c3c48669ad220d81cb2e78775ad4b51510311af1 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 9 Mar 2025 15:38:13 -0400 Subject: [PATCH] Fix data coming in correctly when copying template from an existing product, automatically strip out deals and black friday categories --- inventory-server/src/routes/import.js | 81 +- .../templates/SearchProductTemplateDialog.tsx | 81 +- .../SearchProductTemplateDialog.tsx.bak | 914 ------------------ .../src/components/templates/TemplateForm.tsx | 35 +- 4 files changed, 167 insertions(+), 944 deletions(-) delete mode 100644 inventory/src/components/templates/SearchProductTemplateDialog.tsx.bak diff --git a/inventory-server/src/routes/import.js b/inventory-server/src/routes/import.js index 5d8b0e8..28d2a03 100644 --- a/inventory-server/src/routes/import.js +++ b/inventory-server/src/routes/import.js @@ -526,7 +526,7 @@ router.get('/field-options', async (req, res) => { // Fetch tax categories const [taxCategories] = await connection.query(` - SELECT tax_code_id as value, name as label + SELECT CAST(tax_code_id AS CHAR) as value, name as label FROM product_tax_codes ORDER BY tax_code_id = 0 DESC, name `); @@ -820,6 +820,8 @@ router.get('/search-products', async (req, res) => { s.companyname AS vendor, sid.supplier_itemnumber AS vendor_reference, sid.notions_itemnumber AS notions_reference, + sid.supplier_id AS supplier, + sid.notions_case_pack AS case_qty, pc1.name AS brand, p.company AS brand_id, pc2.name AS line, @@ -839,7 +841,10 @@ router.get('/search-products', async (req, res) => { p.country_of_origin, ci.totalsold AS total_sold, p.datein AS first_received, - pls.date_sold AS date_last_sold + pls.date_sold AS date_last_sold, + IF(p.tax_code IS NULL, '', CAST(p.tax_code AS CHAR)) AS tax_code, + CAST(p.size_cat AS CHAR) AS size_cat, + CAST(p.shipping_restrictions AS CHAR) AS shipping_restrictions FROM products p LEFT JOIN product_current_prices pcp ON p.pid = pcp.pid AND pcp.active = 1 LEFT JOIN supplier_item_data sid ON p.pid = sid.pid @@ -893,6 +898,21 @@ router.get('/search-products', async (req, res) => { const [results] = await connection.query(query, queryParams); + // Debug log to check values + if (results.length > 0) { + console.log('Product search result sample fields:', { + pid: results[0].pid, + tax_code: results[0].tax_code, + tax_code_type: typeof results[0].tax_code, + tax_code_value: `Value: '${results[0].tax_code}'`, + size_cat: results[0].size_cat, + shipping_restrictions: results[0].shipping_restrictions, + supplier: results[0].supplier, + case_qty: results[0].case_qty, + moq: results[0].moq + }); + } + res.json(results); } catch (error) { console.error('Error searching products:', error); @@ -1008,7 +1028,7 @@ router.get('/product-categories/:pid', async (req, res) => { // Query to get categories for a specific product const query = ` - SELECT pc.cat_id, pc.name, pc.type, pc.combined_name + SELECT pc.cat_id, pc.name, pc.type, pc.combined_name, pc.master_cat_id FROM product_category_index pci JOIN product_categories pc ON pci.cat_id = pc.cat_id WHERE pci.pid = ? @@ -1023,8 +1043,61 @@ router.get('/product-categories/:pid', async (req, res) => { console.log(`Product ${pid} has ${rows.length} categories with types: ${uniqueTypes.join(', ')}`); console.log('Categories:', rows.map(row => ({ id: row.cat_id, name: row.name, type: row.type }))); + // Check for parent categories to filter out deals and black friday + const sectionQuery = ` + SELECT pc.cat_id, pc.name + FROM product_categories pc + WHERE pc.type = 10 AND (LOWER(pc.name) LIKE '%deal%' OR LOWER(pc.name) LIKE '%black friday%') + `; + + const [dealSections] = await connection.query(sectionQuery); + const dealSectionIds = dealSections.map(section => section.cat_id); + + console.log('Filtering out categories from deal sections:', dealSectionIds); + + // Filter out categories from deals and black friday sections + const filteredCategories = rows.filter(category => { + // Direct check for top-level deal sections + if (category.type === 10) { + return !dealSectionIds.some(id => id === category.cat_id); + } + + // For categories (type 11), check if their parent is a deal section + if (category.type === 11) { + return !dealSectionIds.some(id => id === category.master_cat_id); + } + + // For subcategories (type 12), get their parent category first + if (category.type === 12) { + const parentId = category.master_cat_id; + // Find the parent category in our rows + const parentCategory = rows.find(c => c.cat_id === parentId); + // If parent not found or parent's parent is not a deal section, keep it + return !parentCategory || !dealSectionIds.some(id => id === parentCategory.master_cat_id); + } + + // For subsubcategories (type 13), check their hierarchy manually + if (category.type === 13) { + const parentId = category.master_cat_id; + // Find the parent subcategory + const parentSubcategory = rows.find(c => c.cat_id === parentId); + if (!parentSubcategory) return true; + + // Find the grandparent category + const grandparentId = parentSubcategory.master_cat_id; + const grandparentCategory = rows.find(c => c.cat_id === grandparentId); + // If grandparent not found or grandparent's parent is not a deal section, keep it + return !grandparentCategory || !dealSectionIds.some(id => id === grandparentCategory.master_cat_id); + } + + // Keep all other category types + return true; + }); + + console.log(`Filtered out ${rows.length - filteredCategories.length} deal/black friday categories`); + // Format the response to match the expected format in the frontend - const categories = rows.map(category => ({ + const categories = filteredCategories.map(category => ({ value: category.cat_id.toString(), label: category.name, type: category.type, diff --git a/inventory/src/components/templates/SearchProductTemplateDialog.tsx b/inventory/src/components/templates/SearchProductTemplateDialog.tsx index 7bd37bc..dba6e32 100644 --- a/inventory/src/components/templates/SearchProductTemplateDialog.tsx +++ b/inventory/src/components/templates/SearchProductTemplateDialog.tsx @@ -63,6 +63,10 @@ interface Product { date_last_sold: string | null; supplier?: string; categories?: string[]; + tax_code?: string; + size_cat?: string; + shipping_restrictions?: string; + case_qty?: number; } interface FieldOption { @@ -336,6 +340,17 @@ export function SearchProductTemplateDialog({ isOpen, onClose, onTemplateCreated const [sortField, setSortField] = useState(null); const [sortDirection, setSortDirection] = useState(null); + // Debug log for selectedProduct values + React.useEffect(() => { + if (selectedProduct) { + console.log('Selected Product Details:', { + tax_code: selectedProduct.tax_code, + size_cat: selectedProduct.size_cat, + shipping_restrictions: selectedProduct.shipping_restrictions + }); + } + }, [selectedProduct]); + // Simple helper function to check if any filters are active const hasActiveFilters = () => { return searchParams.company !== 'all' || searchParams.dateFilter !== 'none'; @@ -479,6 +494,16 @@ export function SearchProductTemplateDialog({ isOpen, onClose, onTemplateCreated }; const handleProductSelect = async (product: Product) => { + console.log('Selected product from list:', { + pid: product.pid, + tax_code: product.tax_code, + tax_code_type: typeof product.tax_code, + tax_code_value: `Value: '${product.tax_code}'`, + size_cat: product.size_cat, + shipping_restrictions: product.shipping_restrictions, + case_qty: product.case_qty + }); + setSelectedProduct(product); // Try to find a matching company ID @@ -491,6 +516,25 @@ export function SearchProductTemplateDialog({ isOpen, onClose, onTemplateCreated } } + // Fetch product categories if pid is available + if (product.pid) { + try { + const response = await axios.get(`/api/import/product-categories/${product.pid}`); + const productCategories = response.data; + + // Update the selected product with the categories + setSelectedProduct(prev => ({ + ...prev!, + categories: productCategories.map((cat: any) => cat.value) + })); + } catch (error) { + console.error('Error fetching product categories:', error); + toast.error('Failed to fetch product categories', { + description: 'Could not retrieve categories for this product' + }); + } + } + setStep('form'); }; @@ -892,21 +936,28 @@ export function SearchProductTemplateDialog({ isOpen, onClose, onTemplateCreated isOpen={true} onClose={() => setStep('search')} onSuccess={onTemplateCreated} - initialData={selectedProduct ? { - company: selectedProduct.brand_id, - product_type: '', - supplier: selectedProduct.supplier, - msrp: selectedProduct.regular_price ? Number(Number(selectedProduct.regular_price).toFixed(2)) : undefined, - cost_each: selectedProduct.cost_price ? Number(Number(selectedProduct.cost_price).toFixed(2)) : undefined, - qty_per_unit: selectedProduct.moq ? Number(selectedProduct.moq) : undefined, - hts_code: selectedProduct.harmonized_tariff_code || undefined, - description: selectedProduct.description || undefined, - weight: selectedProduct.weight ? Number(Number(selectedProduct.weight).toFixed(2)) : undefined, - length: selectedProduct.length ? Number(Number(selectedProduct.length).toFixed(2)) : undefined, - width: selectedProduct.width ? Number(Number(selectedProduct.width).toFixed(2)) : undefined, - height: selectedProduct.height ? Number(Number(selectedProduct.height).toFixed(2)) : undefined, - categories: selectedProduct.categories || [], - } : undefined} + initialData={selectedProduct ? (() => { + console.log('Creating TemplateForm initialData with tax_code:', selectedProduct.tax_code); + return { + company: selectedProduct.brand_id, + product_type: '', + supplier: selectedProduct.supplier, + msrp: selectedProduct.regular_price ? Number(Number(selectedProduct.regular_price).toFixed(2)) : undefined, + cost_each: selectedProduct.cost_price ? Number(Number(selectedProduct.cost_price).toFixed(2)) : undefined, + qty_per_unit: selectedProduct.moq ? Number(selectedProduct.moq) : undefined, + case_qty: selectedProduct.case_qty ? Number(selectedProduct.case_qty) : undefined, + hts_code: selectedProduct.harmonized_tariff_code || undefined, + description: selectedProduct.description || undefined, + weight: selectedProduct.weight ? Number(Number(selectedProduct.weight).toFixed(2)) : undefined, + length: selectedProduct.length ? Number(Number(selectedProduct.length).toFixed(2)) : undefined, + width: selectedProduct.width ? Number(Number(selectedProduct.width).toFixed(2)) : undefined, + height: selectedProduct.height ? Number(Number(selectedProduct.height).toFixed(2)) : undefined, + categories: selectedProduct.categories || [], + tax_cat: selectedProduct.tax_code ? String(selectedProduct.tax_code) : undefined, + size_cat: selectedProduct.size_cat ? String(selectedProduct.size_cat) : undefined, + ship_restrictions: selectedProduct.shipping_restrictions ? String(selectedProduct.shipping_restrictions) : undefined + }; + })() : undefined} mode="create" fieldOptions={fieldOptions} /> diff --git a/inventory/src/components/templates/SearchProductTemplateDialog.tsx.bak b/inventory/src/components/templates/SearchProductTemplateDialog.tsx.bak deleted file mode 100644 index 059bf1d..0000000 --- a/inventory/src/components/templates/SearchProductTemplateDialog.tsx.bak +++ /dev/null @@ -1,914 +0,0 @@ -import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; -import axios from 'axios'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { Loader2, Search, ChevronsUpDown, Check, X, ChevronUp, ChevronDown } from 'lucide-react'; -import { toast } from 'sonner'; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; -import { cn } from '@/lib/utils'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { - Pagination, - PaginationContent, - PaginationEllipsis, - PaginationItem, - PaginationLink, - PaginationNext, - PaginationPrevious, -} from "@/components/ui/pagination"; -import { Badge } from "@/components/ui/badge"; -import { TemplateForm } from '@/components/templates/TemplateForm'; - -interface ProductSearchDialogProps { - isOpen: boolean; - onClose: () => void; - onTemplateCreated: () => void; -} - -interface Product { - pid: number; - title: string; - description: string; - sku: string; - barcode: string; - harmonized_tariff_code: string; - price: number; - regular_price: number; - cost_price: number; - vendor: string; - vendor_reference: string; - notions_reference: string; - brand: string; - brand_id: string; - line: string; - line_id: string; - subline: string; - subline_id: string; - artist: string; - artist_id: string; - moq: number; - weight: number; - length: number; - width: number; - height: number; - country_of_origin: string; - total_sold: number; - first_received: string | null; - date_last_sold: string | null; - supplier?: string; - categories?: string[]; -} - -interface FieldOption { - label: string; - value: string; - type?: number; - level?: number; - hexColor?: string; -} - -interface FieldOptions { - companies: FieldOption[]; - artists: FieldOption[]; - sizes: FieldOption[]; - themes: FieldOption[]; - categories: FieldOption[]; - colors: FieldOption[]; - suppliers: FieldOption[]; - taxCategories: FieldOption[]; - shippingRestrictions: FieldOption[]; -} - -interface TemplateFormData { - company: string; - product_type: string; - supplier?: string; - msrp?: number; - cost_each?: number; - qty_per_unit?: number; - case_qty?: number; - hts_code?: string; - description?: string; - weight?: number; - length?: number; - width?: number; - height?: number; - tax_cat?: string; - size_cat?: string; - categories?: string[]; - ship_restrictions?: string; -} - -// Add sorting types -type SortDirection = 'asc' | 'desc' | null; -type SortField = 'title' | 'brand' | 'line' | 'price' | 'total_sold' | 'first_received' | 'date_last_sold' | null; - -// Date filter options -const DATE_FILTER_OPTIONS = [ - { label: "Any Time", value: "none" }, - { label: "Last week", value: "1week" }, - { label: "Last month", value: "1month" }, - { label: "Last 2 months", value: "2months" }, - { label: "Last 3 months", value: "3months" }, - { label: "Last 6 months", value: "6months" }, - { label: "Last year", value: "1year" } -]; - -// Create a memoized search component to prevent unnecessary re-renders -const SearchInput = React.memo(({ - searchTerm, - setSearchTerm, - handleSearch, - isLoading, - onClear -}: { - searchTerm: string; - setSearchTerm: (term: string) => void; - handleSearch: () => void; - isLoading: boolean; - onClear: () => void; -}) => ( -
{ e.preventDefault(); handleSearch(); }} className="flex items-center space-x-2"> -
- setSearchTerm(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - className="pr-8" - /> - {searchTerm && ( - - )} -
- -
-)); - -// Create a memoized filter component -const FilterSelects = React.memo(({ - selectedCompany, - selectedDateFilter, - companies -}: { - selectedCompany: string; - selectedDateFilter: string; - companies: FieldOption[]; -}) => ( -
-
- - -
- -
- - -
-
- )); - -// Create a memoized results table component -const ResultsTable = React.memo(({ - results, - selectedCompany, - onSelect -}: { - results: any[]; - selectedCompany: string; - onSelect: (product: any) => void; -}) => ( - - - - Name - {selectedCompany === 'all' && Company} - Line - Price - Total Sold - Date In - Last Sold - - - - {results.map((product) => ( - onSelect(product)} - > - {product.title} - {selectedCompany === 'all' && {product.brand || '-'}} - {product.line || '-'} - - {product.price != null ? `$${Number(product.price).toFixed(2)}` : '-'} - - {product.total_sold || 0} - - {product.first_received - ? new Date(product.first_received).toLocaleDateString() - : '-'} - - - {product.date_last_sold - ? new Date(product.date_last_sold).toLocaleDateString() - : '-'} - - - ))} - -
-)); - -export function SearchProductTemplateDialog({ isOpen, onClose, onTemplateCreated }: ProductSearchDialogProps) { - // Product search state - const [searchTerm, setSearchTerm] = useState(''); - const [searchResults, setSearchResults] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [step, setStep] = useState<'search' | 'form'>('search'); - const [selectedProduct, setSelectedProduct] = useState(null); - const [fieldOptions, setFieldOptions] = useState(null); - const [currentPage, setCurrentPage] = useState(1); - const productsPerPage = 500; - - // Filter states - using a single object for filters to ensure atomic updates - const [filters, setFilters] = useState({ - company: 'all', - dateFilter: 'none' - }); - - // Sorting states - const [sortField, setSortField] = useState(null); - const [sortDirection, setSortDirection] = useState(null); - - const [hasSearched, setHasSearched] = useState(false); - - // Simplified function to check if any filters are active - const hasActiveFilters = useCallback(() => { - return filters.company !== 'all' || filters.dateFilter !== 'none'; - }, [filters]); - - // Simplified search state change handler - const handleSearchStateChange = useCallback(async (searchTermToUse = searchTerm) => { - console.log('Search state change with:', { searchTermToUse, filters }); - setIsLoading(true); - - const hasSearchTerm = searchTermToUse.trim().length > 0; - const hasFilters = hasActiveFilters(); - setHasSearched(hasSearchTerm || hasFilters); - - // If no active filters or search term, reset everything - if (!hasSearchTerm && !hasFilters) { - console.log('No search term or filters, resetting results'); - setSearchResults([]); - setHasSearched(false); - setIsLoading(false); - return; - } - - try { - const params: Record = {}; - - // Add search term if it exists - if (hasSearchTerm) { - params.q = searchTermToUse.trim(); - } - - // Add company filter if selected - if (filters.company !== 'all') { - params.company = filters.company; - } - - // Add date filter if selected - if (filters.dateFilter !== 'none') { - params.dateRange = filters.dateFilter; - } - - // If we only have filters (no search term), use wildcard search - if (!hasSearchTerm && hasFilters) { - params.q = '*'; - } - - console.log('Search params:', params); - - const response = await axios.get('/api/import/search-products', { params }); - console.log('Search response:', response.data); - - setSearchResults(response.data.map((product: Product) => ({ - ...product, - pid: typeof product.pid === 'string' ? parseInt(product.pid, 10) : product.pid, - price: typeof product.price === 'string' ? parseFloat(product.price) : product.price, - }))); - } catch (error) { - console.error('Error searching products:', error); - toast.error('Failed to search products', { - description: 'Could not retrieve search results from the server' - }); - setSearchResults([]); - } finally { - setIsLoading(false); - } - }, [hasActiveFilters, filters, searchTerm]); - - // Trigger search when dialog opens with active filters - useEffect(() => { - if (isOpen && hasSearched) { - handleSearchStateChange(); - } - }, [isOpen, hasSearched, handleSearchStateChange]); - - // Simplified handler functions - const handleSearch = useCallback(() => { - handleSearchStateChange(); - }, [handleSearchStateChange]); - - const clearSearch = useCallback(() => { - setSearchTerm(''); - handleSearchStateChange(''); - }, [handleSearchStateChange]); - - const clearFilters = useCallback(() => { - setFilters({ - company: 'all', - dateFilter: 'none' - }); - // Give UI time to update - setTimeout(() => { - handleSearchStateChange(); - }, 0); - }, [handleSearchStateChange]); - - // Handler for filter changes - const handleFilterChange = useCallback((type: 'company' | 'dateFilter', value: string) => { - setFilters(prev => { - const newFilters = { ...prev, [type]: value }; - console.log(`Filter ${type} changed to ${value}`, newFilters); - - // Schedule search with updated filters - setTimeout(() => { - handleSearchStateChange(); - }, 0); - - return newFilters; - }); - }, [handleSearchStateChange]); - - // Reset all search state when dialog is closed - useEffect(() => { - if (!isOpen) { - setSearchTerm(''); - setSearchResults([]); - setFilters({ - company: 'all', - dateFilter: 'none' - }); - setSortField(null); - setSortDirection(null); - setCurrentPage(1); - setStep('search'); - setHasSearched(false); - setSelectedProduct(null); - } else { - // Fetch field options when dialog opens - fetchFieldOptions(); - } - }, [isOpen]); - - // Fetch field options when component mounts - const fetchFieldOptions = async () => { - try { - const response = await axios.get('/api/import/field-options'); - setFieldOptions(response.data); - } catch (error) { - console.error('Error fetching field options:', error); - toast.error('Failed to fetch field options', { - description: 'Could not retrieve field options from the server' - }); - } - }; - - const handleProductSelect = async (product: Product) => { - console.log('Selected product:', product); - console.log('Brand ID:', product.brand_id); - - // Try to find the supplier ID from the vendor name - let supplierValue; - if (product.vendor && fieldOptions) { - let supplierOption = fieldOptions.suppliers.find( - supplier => supplier.label.toLowerCase() === product.vendor.toLowerCase() - ); - - // If no exact match, try partial match - if (!supplierOption) { - supplierOption = fieldOptions.suppliers.find( - supplier => supplier.label.toLowerCase().includes(product.vendor.toLowerCase()) || - product.vendor.toLowerCase().includes(supplier.label.toLowerCase()) - ); - } - - if (supplierOption) { - supplierValue = supplierOption.value; - } - } - - // Fetch categories for the product - let categories: string[] = []; - try { - const response = await axios.get(`/api/import/product-categories/${product.pid}`); - if (response.data && Array.isArray(response.data)) { - // Filter out themes and subthemes (types 20 and 21) - categories = response.data - .filter((category: any) => category.type !== 20 && category.type !== 21) - .map((category: any) => category.value); - } - } catch (error) { - console.error('Error fetching product categories:', error); - } - - // Ensure brand_id is properly set - const companyId = product.brand_id || ''; - console.log('Setting company ID:', companyId); - - const selectedProduct = { - ...product, - brand_id: companyId, // Ensure brand_id is set - supplier: supplierValue, - categories: categories || [] - }; - - console.log('Setting selected product:', selectedProduct); - setSelectedProduct(selectedProduct); - setStep('form'); - }; - - // Get current products for pagination - const totalPages = Math.ceil(searchResults.length / productsPerPage); - - // Change page - const paginate = (pageNumber: number) => setCurrentPage(pageNumber); - - // Generate page numbers for pagination - const getPageNumbers = () => { - const pageNumbers = []; - const maxPagesToShow = 5; - - if (totalPages <= maxPagesToShow) { - // If we have fewer pages than the max to show, display all pages - for (let i = 1; i <= totalPages; i++) { - pageNumbers.push(i); - } - } else { - // Always include first page - pageNumbers.push(1); - - // Calculate start and end of middle pages - let startPage = Math.max(2, currentPage - 1); - let endPage = Math.min(totalPages - 1, currentPage + 1); - - // Adjust if we're near the beginning - if (currentPage <= 3) { - endPage = Math.min(totalPages - 1, 4); - } - - // Adjust if we're near the end - if (currentPage >= totalPages - 2) { - startPage = Math.max(2, totalPages - 3); - } - - // Add ellipsis after first page if needed - if (startPage > 2) { - pageNumbers.push('ellipsis-start'); - } - - // Add middle pages - for (let i = startPage; i <= endPage; i++) { - pageNumbers.push(i); - } - - // Add ellipsis before last page if needed - if (endPage < totalPages - 1) { - pageNumbers.push('ellipsis-end'); - } - - // Always include last page - pageNumbers.push(totalPages); - } - - return pageNumbers; - }; - - // Sort function - const toggleSort = (field: SortField) => { - if (sortField === field) { - // Toggle direction if already sorting by this field - if (sortDirection === 'asc') { - setSortDirection('desc'); - } else if (sortDirection === 'desc') { - setSortField(null); - setSortDirection(null); - } - } else { - // Set new sort field and direction - setSortField(field); - setSortDirection('asc'); - } - - // Reset to page 1 when sorting changes - setCurrentPage(1); - }; - - // Apply sorting to results - const getSortedResults = (results: Product[]) => { - if (!sortField || !sortDirection) return results; - - return [...results].sort((a, b) => { - let valueA: any; - let valueB: any; - - // Extract the correct field values - switch (sortField) { - case 'title': - valueA = a.title?.toLowerCase() || ''; - valueB = b.title?.toLowerCase() || ''; - break; - case 'brand': - valueA = a.brand?.toLowerCase() || ''; - valueB = b.brand?.toLowerCase() || ''; - break; - case 'line': - valueA = a.line?.toLowerCase() || ''; - valueB = b.line?.toLowerCase() || ''; - break; - case 'price': - valueA = typeof a.price === 'number' ? a.price : parseFloat(String(a.price) || '0'); - valueB = typeof b.price === 'number' ? b.price : parseFloat(String(b.price) || '0'); - break; - case 'total_sold': - valueA = typeof a.total_sold === 'number' ? a.total_sold : parseInt(String(a.total_sold) || '0', 10); - valueB = typeof b.total_sold === 'number' ? b.total_sold : parseInt(String(b.total_sold) || '0', 10); - break; - case 'first_received': - valueA = a.first_received ? new Date(a.first_received).getTime() : 0; - valueB = b.first_received ? new Date(b.first_received).getTime() : 0; - break; - case 'date_last_sold': - valueA = a.date_last_sold ? new Date(a.date_last_sold).getTime() : 0; - valueB = b.date_last_sold ? new Date(b.date_last_sold).getTime() : 0; - break; - default: - return 0; - } - - // Compare the values - if (valueA < valueB) { - return sortDirection === 'asc' ? -1 : 1; - } - if (valueA > valueB) { - return sortDirection === 'asc' ? 1 : -1; - } - return 0; - }); - }; - - // Update getFilteredResults to remove redundant company filtering - const getFilteredResults = () => { - if (!searchResults) return []; - return searchResults; - }; - - const filteredResults = getFilteredResults(); - const sortedResults = getSortedResults(filteredResults); - - // Get current products for pagination - const indexOfLastProductFiltered = currentPage * productsPerPage; - const indexOfFirstProductFiltered = indexOfLastProductFiltered - productsPerPage; - const currentProductsFiltered = sortedResults.slice(indexOfFirstProductFiltered, indexOfLastProductFiltered); - const totalPagesFiltered = Math.ceil(sortedResults.length / productsPerPage); - - // Get sort icon - const getSortIcon = (field: SortField) => { - if (sortField !== field) { - return ; - } - - return sortDirection === 'asc' - ? - : ; - }; - - // Sortable table header component - const SortableTableHead = ({ field, children }: { field: SortField, children: React.ReactNode }) => ( - toggleSort(field)} - > -
- {children} - {getSortIcon(field)} -
-
- ); - - return ( - !open && onClose()}> - 0 - ? 'max-w-5xl' - : 'max-w-2xl' - : 'max-w-2xl' - }`}> - {step === 'search' ? ( - <> - - Search Products - - Search for a product you want to use as a template. - - - -
-
- - - - - {fieldOptions && ((filters.company && filters.company !== 'all') || filters.dateFilter !== 'none') && ( -
-
Active filters:
-
- {filters.company && filters.company !== "all" && ( - - Company: {fieldOptions?.companies?.find(c => c.value === filters.company)?.label || 'Unknown'} - - - )} - {filters.dateFilter && filters.dateFilter !== 'none' && ( - - Date: {(() => { - const selectedOption = DATE_FILTER_OPTIONS.find(o => o.value === filters.dateFilter); - return selectedOption ? selectedOption.label : 'Custom range'; - })()} - - - )} - -
-
- )} -
- -
- - {isLoading ? ( -
- - Searching... -
- ) : !hasSearched ? ( -
- Use the search field or filters to find products -
- ) : searchResults.length === 0 ? ( -
- No products found matching your criteria -
- ) : ( - <> -
- {sortedResults.length} products found -
-
- - - - Name - {filters.company === 'all' && ( - Company - )} - Line - Price - Total Sold - Date In - Last Sold - - - - {currentProductsFiltered.map((product) => ( - handleProductSelect(product)} - > - {product.title} - {filters.company === 'all' && ( - {product.brand || '-'} - )} - {product.line || '-'} - - {product.price !== null && product.price !== undefined - ? `$${typeof product.price === 'number' - ? product.price.toFixed(2) - : parseFloat(String(product.price)).toFixed(2)}` - : '-'} - - - {product.total_sold !== null && product.total_sold !== undefined - ? typeof product.total_sold === 'number' - ? product.total_sold - : parseInt(String(product.total_sold), 10) - : 0} - - - {product.first_received - ? (() => { - try { - return new Date(product.first_received).toLocaleDateString(); - } catch (e) { - console.error('Error formatting first_received date:', e); - return '-'; - } - })() - : '-'} - - - {product.date_last_sold - ? (() => { - try { - return new Date(product.date_last_sold).toLocaleDateString(); - } catch (e) { - console.error('Error formatting date_last_sold date:', e); - return '-'; - } - })() - : '-'} - - - ))} - -
-
- - {totalPagesFiltered > 1 && ( -
- - - - currentPage > 1 && paginate(currentPage - 1)} - className={currentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"} - /> - - - {getPageNumbers().map((page, index) => ( - - {page === 'ellipsis-start' || page === 'ellipsis-end' ? ( - - ) : ( - typeof page === 'number' && paginate(page)} - className={typeof page === 'number' ? "cursor-pointer" : ""} - > - {page} - - )} - - ))} - - - currentPage < totalPagesFiltered && paginate(currentPage + 1)} - className={currentPage === totalPagesFiltered ? "pointer-events-none opacity-50" : "cursor-pointer"} - /> - - - -
- )} - - )} -
-
-
- - - - - - ) : ( - setStep('search')} - onSuccess={onTemplateCreated} - initialData={selectedProduct ? { - company: selectedProduct.brand_id, - product_type: '', - supplier: selectedProduct.supplier, - msrp: selectedProduct.regular_price ? Number(Number(selectedProduct.regular_price).toFixed(2)) : undefined, - cost_each: selectedProduct.cost_price ? Number(Number(selectedProduct.cost_price).toFixed(2)) : undefined, - qty_per_unit: selectedProduct.moq ? Number(selectedProduct.moq) : undefined, - hts_code: selectedProduct.harmonized_tariff_code || undefined, - description: selectedProduct.description || undefined, - weight: selectedProduct.weight ? Number(Number(selectedProduct.weight).toFixed(2)) : undefined, - length: selectedProduct.length ? Number(Number(selectedProduct.length).toFixed(2)) : undefined, - width: selectedProduct.width ? Number(Number(selectedProduct.width).toFixed(2)) : undefined, - height: selectedProduct.height ? Number(Number(selectedProduct.height).toFixed(2)) : undefined, - categories: selectedProduct.categories || [], - } : undefined} - mode="create" - fieldOptions={fieldOptions} - /> - )} -
-
- ); -} \ No newline at end of file diff --git a/inventory/src/components/templates/TemplateForm.tsx b/inventory/src/components/templates/TemplateForm.tsx index 6d730ce..13990b6 100644 --- a/inventory/src/components/templates/TemplateForm.tsx +++ b/inventory/src/components/templates/TemplateForm.tsx @@ -86,10 +86,10 @@ export function TemplateForm({ length: undefined, width: undefined, height: undefined, - tax_cat: undefined, + tax_cat: "0", size_cat: undefined, categories: [], - ship_restrictions: undefined + ship_restrictions: "0" }; const [formData, setFormData] = React.useState(defaultFormData); @@ -248,14 +248,14 @@ export function TemplateForm({ const getSortedOptions = (options: FieldOption[], selectedValue?: string | string[]) => { return [...options].sort((a, b) => { if (Array.isArray(selectedValue)) { - const aSelected = selectedValue.includes(a.value); - const bSelected = selectedValue.includes(b.value); + const aSelected = selectedValue.includes(String(a.value)); + const bSelected = selectedValue.includes(String(b.value)); if (aSelected && !bSelected) return -1; if (!aSelected && bSelected) return 1; return 0; } - if (a.value === selectedValue) return -1; - if (b.value === selectedValue) return 1; + if (String(a.value) === String(selectedValue)) return -1; + if (String(b.value) === String(selectedValue)) return 1; return 0; }); }; @@ -318,7 +318,7 @@ export function TemplateForm({ String(cat) === String(option.value)) ? "opacity-100" : "opacity-0" )} @@ -335,7 +335,7 @@ export function TemplateForm({ {formData.tax_cat !== undefined - ? (fieldOptions.taxCategories.find( - (cat) => cat.value === formData.tax_cat - )?.label || "Not Specifically Set") + ? (() => { + console.log('Looking for tax_cat:', { + formData_tax_cat: formData.tax_cat, + formData_tax_cat_type: typeof formData.tax_cat, + taxCategories: fieldOptions.taxCategories.map(cat => ({ + value: cat.value, + value_type: typeof cat.value, + label: cat.label + })) + }); + const match = fieldOptions.taxCategories.find( + (cat) => String(cat.value) === String(formData.tax_cat) + ); + console.log('Tax category match:', match); + return match?.label || "Not Specifically Set"; + })() : "Select tax category..."}