From 7cc723ce833e59789ba05594fb90b1ba5aa245dc Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 9 Mar 2025 16:11:49 -0400 Subject: [PATCH] Fix creating template from validate table row --- .../products/ProductSearchDialog.tsx | 1611 ----------------- .../components/ValidationContainer.tsx | 155 +- 2 files changed, 153 insertions(+), 1613 deletions(-) delete mode 100644 inventory/src/components/products/ProductSearchDialog.tsx diff --git a/inventory/src/components/products/ProductSearchDialog.tsx b/inventory/src/components/products/ProductSearchDialog.tsx deleted file mode 100644 index 6664f78..0000000 --- a/inventory/src/components/products/ProductSearchDialog.tsx +++ /dev/null @@ -1,1611 +0,0 @@ -import React, { useState, useEffect, useCallback, useMemo } 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"; - -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; -} - -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, - setSelectedCompany, - selectedDateFilter, - setSelectedDateFilter, - companies, - onFilterChange -}: { - selectedCompany: string; - setSelectedCompany: (company: string) => void; - selectedDateFilter: string; - setSelectedDateFilter: (filter: string) => void; - companies: { label: string; value: string; }[]; - onFilterChange: () => void; -}) => ( -
-
- - -
- -
- - -
-
-)); - -// 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 ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: ProductSearchDialogProps) { - const [searchTerm, setSearchTerm] = useState(''); - const [searchResults, setSearchResults] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [step, setStep] = useState<'search' | 'form'>('search'); - const [formData, setFormData] = useState({ - company: '', - product_type: '', - }); - const [fieldOptions, setFieldOptions] = useState(null); - const [, setProductLines] = useState([]); - const [isSubmitting, setIsSubmitting] = useState(false); - const [currentPage, setCurrentPage] = useState(1); - const productsPerPage = 500; - - // Filter states - const [selectedCompany, setSelectedCompany] = useState('all'); - const [selectedDateFilter, setSelectedDateFilter] = useState('none'); - - // Sorting states - const [sortField, setSortField] = useState(null); - const [sortDirection, setSortDirection] = useState(null); - - const [hasSearched, setHasSearched] = useState(false); - - // Add this new function to handle all search state changes - const handleSearchStateChange = async () => { - setIsLoading(true); - setHasSearched(true); - - // If no active filters or search term, reset everything - if (!searchTerm.trim() && selectedCompany === 'all' && selectedDateFilter === 'none') { - setSearchResults([]); - setHasSearched(false); - setIsLoading(false); - return; - } - - try { - const params: Record = {}; - - // Always include search term if present - if (searchTerm.trim()) { - params.q = searchTerm.trim(); - } - - // Add filters - if (selectedCompany !== 'all') { - params.company = selectedCompany; - // Only use wildcard if there's no search term - if (!searchTerm.trim()) { - params.q = '*'; - } - } - - if (selectedDateFilter !== 'none') { - params.dateRange = selectedDateFilter; - // Only use wildcard if there's no search term and no company filter - if (!searchTerm.trim() && selectedCompany === 'all') { - params.q = '*'; - } - } - - const response = await axios.get('/api/import/search-products', { params }); - 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, - // ... other parsing remains the same - }))); - } 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); - } - }; - - // Reset all search state when dialog is closed - useEffect(() => { - if (!isOpen) { - // Reset search state - setSearchTerm(''); - setSearchResults([]); - setSelectedCompany('all'); - setSelectedDateFilter('none'); - setSortField(null); - setSortDirection(null); - setCurrentPage(1); - setStep('search'); - setHasSearched(false); - } - }, [isOpen]); - - // Fetch field options when component mounts - useEffect(() => { - if (isOpen) { - fetchFieldOptions(); - // Perform initial search if any filters are already applied - if (selectedCompany !== 'all' || selectedDateFilter !== 'none') { - handleSearchStateChange(); - } - } - }, [isOpen]); - - // Update the useEffect for filter changes to use the new search function - useEffect(() => { - if (selectedCompany !== 'all' || selectedDateFilter !== 'none') { - handleSearchStateChange(); - } - }, [selectedCompany, selectedDateFilter]); - - // Fetch product lines when company changes - useEffect(() => { - if (formData.company) { - fetchProductLines(formData.company); - } - }, [formData.company]); - - 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 fetchProductLines = async (companyId: string) => { - try { - const response = await axios.get(`/api/import/product-lines/${companyId}`); - setProductLines(response.data); - } catch (error) { - console.error('Error fetching product lines:', error); - setProductLines([]); - } - }; - - // Update the search input handlers - const handleSearch = () => { - handleSearchStateChange(); - }; - - const clearSearch = () => { - setSearchTerm(''); - handleSearchStateChange(); - }; - - const clearFilters = () => { - setSelectedCompany('all'); - setSelectedDateFilter('none'); - handleSearchStateChange(); - }; - - const handleProductSelect = (product: Product) => { - - // Ensure all values are of the correct type - setFormData({ - company: product.brand_id ? String(product.brand_id) : '', - product_type: '', - // For supplier, we need to find the supplier ID by matching the vendor name - // vendor_reference is NOT the supplier ID, it's the supplier's product identifier - supplier: undefined, // We'll set this below if we can find a match - msrp: typeof product.regular_price === 'number' ? product.regular_price : - typeof product.regular_price === 'string' ? parseFloat(product.regular_price) : undefined, - cost_each: typeof product.cost_price === 'number' ? product.cost_price : - typeof product.cost_price === 'string' ? parseFloat(product.cost_price) : undefined, - qty_per_unit: typeof product.moq === 'number' ? product.moq : - typeof product.moq === 'string' ? parseInt(product.moq, 10) : undefined, - hts_code: product.harmonized_tariff_code || undefined, - description: product.description || undefined, - weight: typeof product.weight === 'number' ? product.weight : - typeof product.weight === 'string' ? parseFloat(product.weight) : undefined, - length: typeof product.length === 'number' ? product.length : - typeof product.length === 'string' ? parseFloat(product.length) : undefined, - width: typeof product.width === 'number' ? product.width : - typeof product.width === 'string' ? parseFloat(product.width) : undefined, - height: typeof product.height === 'number' ? product.height : - typeof product.height === 'string' ? parseFloat(product.height) : undefined, - categories: [], - ship_restrictions: undefined - }); - - // Try to find the supplier ID from the vendor name - 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) { - setFormData(prev => ({ - ...prev, - supplier: supplierOption.value - })); - - } else { - console.log('No supplier match found for vendor:', product.vendor); - } - } - - // Fetch product categories - if (product.pid) { - - fetchProductCategories(product.pid); - } - - setStep('form'); - }; - - // Add a function to fetch product categories - const fetchProductCategories = async (productId: number) => { - try { - const response = await axios.get(`/api/import/product-categories/${productId}`); - - if (response.data && Array.isArray(response.data)) { - // Filter out categories with type 20 (themes) and type 21 (subthemes) - const filteredCategories = response.data.filter((category: any) => - category.type !== 20 && category.type !== 21 - ); - - // Extract category IDs and update form data - const categoryIds = filteredCategories.map((category: any) => category.value); - setFormData(prev => ({ - ...prev, - categories: categoryIds - })); - } - } catch (error) { - console.error('Error fetching product categories:', error); - toast.error('Failed to fetch product categories', { - description: 'Could not retrieve categories for this product' - }); - } - }; - - const handleInputChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); - }; - - const handleTextAreaChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); - }; - - const handleMultiSelectChange = (name: string, value: string) => { - setFormData(prev => { - const currentValues = prev[name as keyof typeof prev] as string[] || []; - const valueSet = new Set(currentValues); - - if (valueSet.has(value)) { - valueSet.delete(value); - } else { - valueSet.add(value); - } - - return { - ...prev, - [name]: Array.from(valueSet), - }; - }); - }; - - const handleSelectChange = (name: string, value: string | string[]) => { - setFormData(prev => ({ ...prev, [name]: value })); - }; - - const handleNumberInputChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - const numValue = value === '' ? undefined : parseFloat(value); - setFormData(prev => ({ ...prev, [name]: numValue })); - }; - - const handleSubmit = async () => { - // Validate required fields - if (!formData.company || !formData.product_type) { - toast.error('Validation Error', { - description: 'Company and Product Type are required' - }); - return; - } - - // Log supplier information for debugging - if (formData.supplier) { - - } else { - console.log('No supplier selected for submission'); - } - - // Log categories information for debugging - if (formData.categories && formData.categories.length > 0) { - - } else { - console.log('No categories selected for submission'); - } - - setIsSubmitting(true); - try { - // Prepare the data to be sent - ensure all fields are properly formatted - const dataToSend = { - company: formData.company, - product_type: formData.product_type, - supplier: formData.supplier || null, - msrp: formData.msrp !== undefined ? Number(formData.msrp) : null, - cost_each: formData.cost_each !== undefined ? Number(formData.cost_each) : null, - qty_per_unit: formData.qty_per_unit !== undefined ? Number(formData.qty_per_unit) : null, - case_qty: formData.case_qty !== undefined ? Number(formData.case_qty) : null, - hts_code: formData.hts_code || null, - description: formData.description || null, - weight: formData.weight !== undefined ? Number(formData.weight) : null, - length: formData.length !== undefined ? Number(formData.length) : null, - width: formData.width !== undefined ? Number(formData.width) : null, - height: formData.height !== undefined ? Number(formData.height) : null, - tax_cat: formData.tax_cat || null, - size_cat: formData.size_cat || null, - categories: formData.categories || [], - ship_restrictions: formData.ship_restrictions || null - }; - - - const response = await axios.post('/api/templates', dataToSend); - - - if (response.status >= 200 && response.status < 300) { - toast.success('Template created successfully'); - onTemplateCreated(); - onClose(); - } else { - throw new Error(`Server responded with status: ${response.status}`); - } - } catch (error) { - console.error('Error creating template:', error); - let errorMessage = 'Failed to create template'; - - if (axios.isAxiosError(error)) { - console.error('Axios error details:', { - status: error.response?.status, - data: error.response?.data, - headers: error.response?.headers - }); - - if (error.response?.data?.message) { - errorMessage = error.response.data.message; - } else if (error.response?.data?.error) { - errorMessage = error.response.data.error; - } else if (error.message) { - errorMessage = error.message; - } - } - - toast.error('Template Creation Failed', { - description: errorMessage - }); - } finally { - setIsSubmitting(false); - } - }; - - const handleCancel = () => { - if (step === 'form') { - setStep('search'); - } else { - // Reset form data when closing - setFormData({ - company: '', - product_type: '', - }); - onClose(); - } - }; - - // 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; - }); - }; - - // Apply filters to search results - const getFilteredResults = () => { - if (!searchResults) return []; - - return searchResults.filter(product => { - // Apply company filter if set - if (selectedCompany && selectedCompany !== "all" && product.brand_id !== selectedCompany) { - return false; - } - - // The date filtering is now handled on the server side with the dateRange parameter - // No need to filter dates on the client side anymore - - return true; - }); - }; - - 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)} -
-
- ); - - // Render search step - const renderSearchStep = () => ( - <> - - Search Products - - Search for a product you want to use as a template. - - - -
-
- - - - - {fieldOptions && ((selectedCompany && selectedCompany !== 'all') || selectedDateFilter !== 'none') && ( -
-
Active filters:
-
- {selectedCompany && selectedCompany !== "all" && ( - - Company: {fieldOptions?.companies?.find(c => c.value === selectedCompany)?.label || 'Unknown'} - - - )} - {selectedDateFilter && selectedDateFilter !== 'none' && ( - - Date: {(() => { - const selectedOption = DATE_FILTER_OPTIONS.find(o => o.value === selectedDateFilter); - 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 - {selectedCompany === 'all' && ( - Company - )} - Line - Price - Total Sold - Date In - Last Sold - - - - {currentProductsFiltered.map((product) => ( - handleProductSelect(product)} - > - {product.title} - {selectedCompany === '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"} - /> - - - -
- )} - - )} -
-
-
- - - - - - ); - - const renderFormStep = () => { - if (!fieldOptions) return
Loading...
; - - // Helper function to sort options with selected value at top - 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); - if (aSelected && !bSelected) return -1; - if (!aSelected && bSelected) return 1; - return 0; - } - if (a.value === selectedValue) return -1; - if (b.value === selectedValue) return 1; - return 0; - }); - }; - - // Helper function to get selected category labels - const getSelectedCategoryLabels = () => { - if (!formData.categories?.length) return ""; - const labels = formData.categories - .map(value => fieldOptions.categories.find(cat => cat.value === value)?.label) - .filter(Boolean); - return labels.join(", "); - }; - - // Sort categories with selected ones first and respect levels - const getSortedCategories = () => { - const selected = new Set(formData.categories || []); - - // Filter categories to only include types 10, 11, 12, and 13 (proper categories, not themes) - const validCategoryTypes = [10, 11, 12, 13]; - const filteredCategories = fieldOptions.categories.filter( - category => category.type && validCategoryTypes.includes(category.type) - ); - - return [...filteredCategories].sort((a, b) => { - const aSelected = selected.has(a.value); - const bSelected = selected.has(b.value); - if (aSelected && !bSelected) return -1; - if (!aSelected && bSelected) return 1; - return 0; - }); - }; - - return ( - <> - - Create Template from Product - - Create a new template for importing products. Company and Product Type combination must be unique. - - - -
{ e.preventDefault(); handleSubmit(); }} className="flex flex-col flex-1 overflow-hidden"> - -
-
-
- - - - - - - - - { - const commandList = e.currentTarget; - commandList.scrollTop += e.deltaY; - e.stopPropagation(); - }}> - No companies found. - - {getSortedOptions(fieldOptions.companies, formData.company).map((company) => ( - handleSelectChange('company', company.value)} - > - - {company.label} - - ))} - - - - - -
- -
- - -
-
- -
- - - - - - - - - { - const commandList = e.currentTarget; - commandList.scrollTop += e.deltaY; - e.stopPropagation(); - }}> - No suppliers found. - - {getSortedOptions(fieldOptions.suppliers, formData.supplier).map((supplier) => ( - { - // Make sure we're setting the ID (value), not the label - handleSelectChange('supplier', supplier.value); - - }} - > - - {supplier.label} - - ))} - - - - - -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
- - - - - - - - - { - const commandList = e.currentTarget; - commandList.scrollTop += e.deltaY; - e.stopPropagation(); - }}> - No restrictions found. - - {getSortedOptions(fieldOptions.shippingRestrictions, formData.ship_restrictions).map((restriction) => ( - handleSelectChange('ship_restrictions', restriction.value)} - > - - {restriction.label} - - ))} - - - - - -
-
- - -
-
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
- - - - - - - - - { - const commandList = e.currentTarget; - commandList.scrollTop += e.deltaY; - e.stopPropagation(); - }}> - No tax categories found. - - {getSortedOptions(fieldOptions.taxCategories, formData.tax_cat).map((category) => ( - handleSelectChange('tax_cat', category.value)} - > - - {category.label} - - ))} - - - - - -
-
- - - - - - - - - { - const commandList = e.currentTarget; - commandList.scrollTop += e.deltaY; - e.stopPropagation(); - }}> - No sizes found. - - {getSortedOptions(fieldOptions.sizes, formData.size_cat).map((size) => ( - handleSelectChange('size_cat', size.value)} - > - - {size.label} - - ))} - - - - - -
-
- -
- - - - - - - - - { - const commandList = e.currentTarget; - commandList.scrollTop += e.deltaY; - e.stopPropagation(); - }}> - No categories found. - - {getSortedCategories().map((category) => ( - handleMultiSelectChange('categories', category.value)} - > -
- - - {category.label} - -
-
- ))} -
-
-
-
-
-
- -
- -