Improve template search in validate step

This commit is contained in:
2025-03-01 14:48:10 -05:00
parent f7bdefb0a3
commit 8271c9f95a
2 changed files with 643 additions and 145 deletions

View File

@@ -6,15 +6,13 @@ 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, ChevronLeft, ChevronRight, MoreHorizontal, X, ChevronUp, ChevronDown } from 'lucide-react';
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 { format } from 'date-fns';
import { Calendar } from '@/components/ui/calendar';
import {
Pagination,
PaginationContent,
@@ -118,7 +116,7 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
product_type: '',
});
const [fieldOptions, setFieldOptions] = useState<FieldOptions | null>(null);
const [productLines, setProductLines] = useState<FieldOption[]>([]);
const [, setProductLines] = useState<FieldOption[]>([]);
const [isSubmitting, setIsSubmitting] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const productsPerPage = 500;
@@ -528,9 +526,6 @@ export function ProductSearchDialog({ isOpen, onClose, onTemplateCreated }: Prod
};
// Get current products for pagination
const indexOfLastProduct = currentPage * productsPerPage;
const indexOfFirstProduct = indexOfLastProduct - productsPerPage;
const currentProducts = searchResults.slice(indexOfFirstProduct, indexOfLastProduct);
const totalPages = Math.ceil(searchResults.length / productsPerPage);
// Change page

View File

@@ -846,13 +846,45 @@ function useTemplates<T extends string>(
newTemplateType: "",
})
// Fetch field options to get company names
const { data: fieldOptions } = useQuery({
queryKey: ["field-options"],
queryFn: async () => {
const response = await fetch(`${config.apiUrl}/import/field-options`);
if (!response.ok) {
throw new Error("Failed to fetch field options");
}
return response.json();
},
staleTime: 600000, // 10 minutes
});
// Function to fetch templates
const fetchTemplates = useCallback(async () => {
try {
console.log('Fetching templates...');
const response = await fetch(`${config.apiUrl}/templates`)
console.log('Templates response status:', response.status);
if (!response.ok) throw new Error('Failed to fetch templates')
const templateData = await response.json()
setTemplates(templateData)
console.log('Templates fetched successfully:', templateData);
console.log('First template:', templateData[0]);
// Validate template data
const validTemplates = templateData.filter((t: any) =>
t && typeof t === 'object' && t.id && t.company && t.product_type
);
if (validTemplates.length !== templateData.length) {
console.warn('Some templates were filtered out due to invalid data', {
original: templateData.length,
valid: validTemplates.length
});
}
setTemplates(validTemplates)
} catch (error) {
console.error('Error fetching templates:', error)
toast({
@@ -883,108 +915,155 @@ function useTemplates<T extends string>(
const applyTemplate = useCallback(async (templateId: string, rowIndices?: number[]) => {
if (!templateId) return
const template = templates?.find(t => t.id.toString() === templateId)
if (!template) return
try {
const template = templates?.find(t => t && t.id && t.id.toString() === templateId)
if (!template) {
console.error(`Template with ID ${templateId} not found`);
toast({
title: "Error",
description: `Template not found`,
variant: "destructive",
});
return;
}
setData((prevData: RowData<T>[]) => {
const newData = [...prevData]
const indicesToUpdate = rowIndices || newData.map((_, i) => i)
setData((prevData: RowData<T>[]) => {
try {
const newData = [...prevData]
const indicesToUpdate = rowIndices || newData.map((_, i) => i)
indicesToUpdate.forEach(index => {
const row = newData[index]
if (!row) return
indicesToUpdate.forEach(index => {
const row = newData[index]
if (!row) return
// Apply all template fields except id, company, product_type, created_at, and updated_at
Object.entries(template).forEach(([key, value]) => {
if (!['id', 'company', 'product_type', 'created_at', 'updated_at'].includes(key)) {
// Handle numeric values that might be stored as strings
if (typeof value === 'string' && /^\d+(\.\d+)?$/.test(value)) {
// If it's a price field, add the dollar sign
if (['msrp', 'cost_each'].includes(key)) {
row[key as keyof typeof row] = `$${value}` as any;
} else {
row[key as keyof typeof row] = value as any;
}
}
// Special handling for categories field
else if (key === 'categories') {
console.log('Applying categories from template:', {
key,
value,
type: typeof value,
isArray: Array.isArray(value)
});
// Apply all template fields except id, company, product_type, created_at, and updated_at
Object.entries(template).forEach(([key, value]) => {
if (!['id', 'company', 'product_type', 'created_at', 'updated_at'].includes(key)) {
// Handle numeric values that might be stored as strings
if (typeof value === 'string' && /^\d+(\.\d+)?$/.test(value)) {
// If it's a price field, add the dollar sign
if (['msrp', 'cost_each'].includes(key)) {
row[key as keyof typeof row] = `$${value}` as any;
} else {
row[key as keyof typeof row] = value as any;
}
}
// Special handling for categories field
else if (key === 'categories') {
console.log('Applying categories from template:', {
key,
value,
type: typeof value,
isArray: Array.isArray(value)
});
// If categories is an array, use it directly
if (Array.isArray(value)) {
row[key as keyof typeof row] = value as any;
console.log('Categories is array, using directly:', value);
}
// If categories is a string (possibly a PostgreSQL array representation), parse it
else if (typeof value === 'string') {
try {
// Try to parse as JSON if it's a JSON string
if (value.startsWith('[') && value.endsWith(']')) {
const parsed = JSON.parse(value);
row[key as keyof typeof row] = parsed as any;
console.log('Categories parsed from JSON string:', parsed);
// If categories is an array, use it directly
if (Array.isArray(value)) {
row[key as keyof typeof row] = value as any;
console.log('Categories is array, using directly:', value);
}
// Handle PostgreSQL array format like {value1,value2}
else if (value.startsWith('{') && value.endsWith('}')) {
const parsed = value.substring(1, value.length - 1).split(',');
row[key as keyof typeof row] = parsed as any;
console.log('Categories parsed from PostgreSQL array:', parsed);
// If categories is a string (possibly a PostgreSQL array representation), parse it
else if (typeof value === 'string') {
try {
// Try to parse as JSON if it's a JSON string
if (value.startsWith('[') && value.endsWith(']')) {
const parsed = JSON.parse(value);
row[key as keyof typeof row] = parsed as any;
console.log('Categories parsed from JSON string:', parsed);
}
// Otherwise, it might be a PostgreSQL array format like {val1,val2}
else if (value.startsWith('{') && value.endsWith('}')) {
const parsed = value.slice(1, -1).split(',');
row[key as keyof typeof row] = parsed as any;
console.log('Categories parsed from PostgreSQL array:', parsed);
}
// If it's a single value, wrap it in an array
else {
row[key as keyof typeof row] = [value] as any;
console.log('Categories is single value, wrapping in array:', [value]);
}
} catch (error) {
console.error('Error parsing categories:', error);
// If parsing fails, use as-is
row[key as keyof typeof row] = value as any;
}
}
// If it's a comma-separated string
else if (value.includes(',')) {
const parsed = value.split(',');
row[key as keyof typeof row] = parsed as any;
console.log('Categories parsed from comma-separated string:', parsed);
}
// If it's a single value
else if (value.trim()) {
const parsed = [value.trim()];
row[key as keyof typeof row] = parsed as any;
console.log('Categories parsed from single value:', parsed);
}
// Empty value
// For any other type, use as-is
else {
row[key as keyof typeof row] = [] as any;
console.log('Categories is empty string, using empty array');
row[key as keyof typeof row] = value as any;
}
} catch (e) {
console.error('Error parsing categories:', e);
row[key as keyof typeof row] = [] as any;
}
// Handle ship_restrictions field similarly to categories
else if (key === 'ship_restrictions') {
console.log('Applying ship_restrictions from template:', {
key,
value,
type: typeof value,
isArray: Array.isArray(value)
});
// If ship_restrictions is an array, use it directly
if (Array.isArray(value)) {
row[key as keyof typeof row] = value as any;
}
// If ship_restrictions is a string, try to parse it
else if (typeof value === 'string') {
try {
// Try to parse as JSON if it's a JSON string
if (value.startsWith('[') && value.endsWith(']')) {
const parsed = JSON.parse(value);
row[key as keyof typeof row] = parsed as any;
}
// Otherwise, it might be a PostgreSQL array format like {val1,val2}
else if (value.startsWith('{') && value.endsWith('}')) {
const parsed = value.slice(1, -1).split(',');
row[key as keyof typeof row] = parsed as any;
}
// If it's a single value, wrap it in an array
else {
row[key as keyof typeof row] = [value] as any;
}
} catch (error) {
console.error('Error parsing ship_restrictions:', error);
// If parsing fails, use as-is
row[key as keyof typeof row] = value as any;
}
}
// For any other type, use as-is
else {
row[key as keyof typeof row] = value as any;
}
}
// For all other fields, apply directly
else {
row[key as keyof typeof row] = value as any;
}
}
// Default to empty array for any other case
else {
row[key as keyof typeof row] = [] as any;
console.log('Categories is not array or string, using empty array');
}
}
// Handle other array values
else if (Array.isArray(value)) {
row[key as keyof typeof row] = [...value] as any;
}
// Handle other values
else {
row[key as keyof typeof row] = value as any;
}
}
})
});
// Update the template reference
row.__template = templateId;
// Update the template reference
row.__template = templateId;
})
return newData
} catch (error) {
console.error('Error applying template:', error);
return prevData;
}
})
return newData
})
toast({
title: "Template Applied",
description: `Applied template to ${rowIndices?.length || data.length} row(s)`,
})
toast({
title: "Template Applied",
description: `Applied template to ${rowIndices?.length || data.length} row(s)`,
})
} catch (error) {
console.error('Error in applyTemplate:', error);
toast({
title: "Error",
description: "Failed to apply template",
variant: "destructive",
});
}
}, [templates, setData, data.length, toast])
const saveAsTemplate = useCallback(async () => {
@@ -1089,6 +1168,36 @@ function useTemplates<T extends string>(
}
}, [state, data, rowSelection, toast, setTemplates, setState, setData]);
// Helper function to get company name from ID
const getCompanyName = useCallback((companyId: string) => {
if (!fieldOptions || !fieldOptions.companies) return companyId;
try {
const company = fieldOptions.companies.find((c: { value: string; label: string }) => c.value === companyId);
return company ? company.label : companyId;
} catch (error) {
console.error("Error getting company name:", error);
return companyId;
}
}, [fieldOptions]);
// Format template display text
const getTemplateDisplayText = useCallback((template: Template) => {
if (!template) {
console.error("Template is null or undefined in getTemplateDisplayText");
return "Unknown Template";
}
try {
const companyId = template.company || "";
const productType = template.product_type || "Unknown Type";
const companyName = getCompanyName(companyId);
return `${companyName} - ${productType}`;
} catch (error) {
console.error("Error formatting template display text:", error, template);
return "Error displaying template";
}
}, [getCompanyName]);
return {
templates,
selectedTemplateId: state.selectedTemplateId,
@@ -1101,10 +1210,370 @@ function useTemplates<T extends string>(
newTemplateType: state.newTemplateType,
applyTemplate,
saveAsTemplate,
getTemplateDisplayText,
}
}
// Add this component before the ValidationStep component
// Add this component before the SaveTemplateDialog component
const SearchableTemplateSelect = memo(({
templates,
value,
onValueChange,
getTemplateDisplayText,
placeholder = "Select template",
className,
triggerClassName,
defaultBrand,
}: {
templates: Template[];
value: string;
onValueChange: (value: string) => void;
getTemplateDisplayText: (template: Template) => string;
placeholder?: string;
className?: string;
triggerClassName?: string;
defaultBrand?: string;
}) => {
const [searchTerm, setSearchTerm] = useState("");
const [selectedBrand, setSelectedBrand] = useState<string | null>(null);
const [open, setOpen] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isTemplatesReady, setIsTemplatesReady] = useState(false);
// Set default brand when component mounts or defaultBrand changes
useEffect(() => {
if (defaultBrand) {
setSelectedBrand(defaultBrand);
}
}, [defaultBrand]);
// Handle wheel events for scrolling
const handleWheel = (e: React.WheelEvent) => {
const scrollArea = e.currentTarget;
scrollArea.scrollTop += e.deltaY;
};
// Log props for debugging
useEffect(() => {
console.log('SearchableTemplateSelect props:', {
templatesCount: templates?.length || 0,
value,
hasGetTemplateDisplayText: !!getTemplateDisplayText,
placeholder,
className,
triggerClassName
});
// Check if templates are valid
if (templates && templates.length > 0) {
const firstTemplate = templates[0];
console.log('First template:', firstTemplate);
setIsTemplatesReady(true);
} else {
setIsTemplatesReady(false);
}
}, [templates, value, getTemplateDisplayText, placeholder, className, triggerClassName]);
// Extract unique brands from templates
const brands = useMemo(() => {
try {
if (!templates || templates.length === 0) return [];
const brandSet = new Set<string>();
const brandNames: {id: string, name: string}[] = [];
templates.forEach(template => {
if (!template || !template.company) return;
const companyId = template.company;
if (!brandSet.has(companyId)) {
brandSet.add(companyId);
// Try to get the company name from the template display text
try {
const displayText = getTemplateDisplayText(template);
const companyName = displayText.split(' - ')[0];
brandNames.push({ id: companyId, name: companyName || `Company ${companyId}` });
} catch (err) {
brandNames.push({ id: companyId, name: `Company ${companyId}` });
}
}
});
return brandNames.sort((a, b) => a.name.localeCompare(b.name));
} catch (err) {
console.error("Error extracting brands:", err);
return [];
}
}, [templates, getTemplateDisplayText]);
// Group templates by company for better organization
const groupedTemplates = useMemo(() => {
try {
if (!templates || templates.length === 0) return {};
const groups: Record<string, Template[]> = {};
templates.forEach(template => {
if (!template) return;
const companyId = template.company;
if (!groups[companyId]) {
groups[companyId] = [];
}
groups[companyId].push(template);
});
return groups;
} catch (err) {
console.error("Error grouping templates:", err);
return {};
}
}, [templates]);
// Filter templates based on selected brand and search term
const filteredTemplates = useMemo(() => {
try {
if (!templates || templates.length === 0) return [];
// First filter by brand if selected
let brandFiltered = templates;
if (selectedBrand) {
brandFiltered = templates.filter(t => t && t.company === selectedBrand);
}
// Then filter by search term if provided
if (!searchTerm.trim()) return brandFiltered;
const lowerSearchTerm = searchTerm.toLowerCase();
return brandFiltered.filter(template => {
if (!template) return false;
try {
const displayText = getTemplateDisplayText(template);
const productType = template.product_type?.toLowerCase() || '';
// Search in both the display text and product type
return displayText.toLowerCase().includes(lowerSearchTerm) ||
productType.includes(lowerSearchTerm);
} catch (error) {
console.error("Error filtering template:", error, template);
return false;
}
});
} catch (err) {
console.error("Error in filteredTemplates:", err);
setError("Error filtering templates");
return [];
}
}, [templates, selectedBrand, searchTerm, getTemplateDisplayText]);
// Get the display text for the selected template
const selectedTemplate = useMemo(() => {
try {
if (!templates || templates.length === 0 || !value) return null;
return templates.find(t => t && t.id && t.id.toString() === value);
} catch (err) {
console.error("Error finding selected template:", err);
setError("Error finding selected template");
return null;
}
}, [templates, value]);
// Handle errors gracefully
const getDisplayText = useCallback((template: Template | null) => {
try {
if (!template) return placeholder;
return getTemplateDisplayText(template);
} catch (err) {
console.error("Error getting template display text:", err);
setError("Error displaying template");
return placeholder;
}
}, [getTemplateDisplayText, placeholder]);
// Reset filters
const resetFilters = useCallback(() => {
setSearchTerm("");
setSelectedBrand(null);
}, []);
// Handle errors in the component
if (error) {
return (
<Button
variant="outline"
className={cn("w-full justify-between text-destructive", triggerClassName)}
onClick={() => setError(null)}
>
Error: {error}
</Button>
);
}
// Safe render function for CommandItem
const renderCommandItem = useCallback((template: Template) => {
try {
return (
<CommandItem
key={template.id}
value={template.id.toString()}
onSelect={(currentValue) => {
try {
onValueChange(currentValue);
setOpen(false);
setSearchTerm("");
setSelectedBrand(null);
} catch (err) {
console.error("Error in onSelect:", err);
setError("Error selecting template");
}
}}
className="flex items-start gap-2 py-2"
>
<Check
className={cn(
"mr-1 h-4 w-4 mt-0.5",
value === template.id.toString() ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex flex-col">
<span className="font-medium">{getDisplayText(template)}</span>
</div>
</CommandItem>
);
} catch (err) {
console.error("Error rendering CommandItem:", err, template);
return null;
}
}, [getDisplayText, onValueChange, value]);
// Ensure we have templates before rendering the dropdown
if (!templates || templates.length === 0) {
return (
<Button
variant="outline"
className={cn("w-full justify-between", triggerClassName)}
>
No templates available
</Button>
);
}
return (
<Popover open={open} onOpenChange={(newOpen) => {
try {
// Only allow opening if templates are ready
if (newOpen && !isTemplatesReady) {
console.warn("Prevented opening popover because templates are not ready");
return;
}
setOpen(newOpen);
if (!newOpen) {
// Reset filters when closing
resetFilters();
}
} catch (err) {
console.error("Error in onOpenChange:", err);
setError("Error opening dropdown");
}
}}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={cn(
"w-full justify-between",
triggerClassName
)}
>
{value && selectedTemplate
? getDisplayText(selectedTemplate)
: placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className={cn("w-[350px] p-0", className)}>
<Command shouldFilter={false}>
{/* Filter controls */}
<div className="flex flex-col border-b">
{/* Brand filter */}
<div className="flex items-center px-3 py-2">
<div className="flex-1">
<Select
value={selectedBrand || "all"}
onValueChange={(value) => setSelectedBrand(value === "all" ? null : value)}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="All brands" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All brands</SelectItem>
{brands.map(brand => (
<SelectItem key={brand.id} value={brand.id}>
{brand.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{/* Search input */}
<div className="flex items-center px-3 pb-2">
<CommandInput
placeholder="Search by product type..."
value={searchTerm}
onValueChange={(value) => {
try {
setSearchTerm(value);
} catch (err) {
console.error("Error in onValueChange:", err);
setError("Error searching templates");
}
}}
className="h-8 flex-1"
/>
</div>
</div>
{/* Results */}
<CommandEmpty>
<div className="py-6 text-center">
<p className="text-sm text-muted-foreground">No templates found.</p>
</div>
</CommandEmpty>
<CommandList>
<ScrollArea className="max-h-[200px] overflow-y-auto" onWheel={handleWheel}>
{!selectedBrand && !searchTerm ? (
// When no filters are applied, show templates grouped by company
Object.entries(groupedTemplates).map(([companyId, companyTemplates]) => {
// Get company name from the first template
const brand = brands.find(b => b.id === companyId);
const companyName = brand ? brand.name : `Company ${companyId}`;
return (
<CommandGroup key={companyId} heading={companyName}>
{companyTemplates.map(template => renderCommandItem(template))}
</CommandGroup>
);
})
) : (
// When filters are applied, show filtered results
<CommandGroup>
{filteredTemplates.map(template => template ? renderCommandItem(template) : null)}
</CommandGroup>
)}
</ScrollArea>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
});
const SaveTemplateDialog = memo(({
isOpen,
onClose,
@@ -1426,6 +1895,7 @@ export const ValidationStep = <T extends string>({
saveAsTemplate,
setNewTemplateName,
setNewTemplateType,
getTemplateDisplayText,
} = useTemplates(data, setData, useToast, rowSelection)
// Memoize filtered data to prevent recalculation on every render
@@ -1527,31 +1997,52 @@ export const ValidationStep = <T extends string>({
{
id: "template",
header: "Template",
cell: ({ row }) => (
<Select
value={row.original.__template || ""}
onValueChange={(value) => {
const newData = [...data];
const index = newData.findIndex(r => r.__index === row.original.__index);
if (index !== -1) {
newData[index] = { ...newData[index], __template: value };
setData(newData);
applyTemplate(value, [index]);
}
}}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select template" />
</SelectTrigger>
<SelectContent>
{templates?.map((template) => (
<SelectItem key={template.id} value={template.id.toString()}>
{template.company} - {template.product_type}
</SelectItem>
))}
</SelectContent>
</Select>
),
cell: ({ row }) => {
try {
// Only render the component if templates are available
if (!templates || templates.length === 0) {
return (
<Button variant="outline" className="w-full justify-between" disabled>
Loading templates...
</Button>
);
}
return (
<SearchableTemplateSelect
templates={templates}
value={row.original.__template || ""}
onValueChange={(value) => {
try {
const newData = [...data];
const index = newData.findIndex(r => r.__index === row.original.__index);
if (index !== -1) {
newData[index] = { ...newData[index], __template: value };
setData(newData);
applyTemplate(value, [index]);
}
} catch (error) {
console.error("Error applying template in cell:", error);
toast({
title: "Error",
description: "Failed to apply template to row",
variant: "destructive",
});
}
}}
getTemplateDisplayText={getTemplateDisplayText}
defaultBrand={globalSelections?.company}
/>
);
} catch (error) {
console.error("Error rendering template cell:", error);
return (
<Button variant="outline" className="w-full text-destructive">
Error loading templates
</Button>
);
}
},
size: 200,
},
...(Array.from(fields as ReadonlyFields<T>).map((field): ColumnDef<Data<T> & ExtendedMeta> => ({
@@ -1589,7 +2080,7 @@ export const ValidationStep = <T extends string>({
})))
]
return baseColumns
}, [fields, updateRows, data, copyValueDown, templates, applyTemplate, productLines, sublines])
}, [fields, updateRows, data, copyValueDown, templates, applyTemplate, productLines, sublines, getTemplateDisplayText])
const table = useReactTable({
data: filteredData,
@@ -2839,25 +3330,37 @@ export const ValidationStep = <T extends string>({
<X className="h-4 w-4" />
</Button>
</div>
<Select
value={selectedTemplateId || ""}
onValueChange={(value) => {
setSelectedTemplateId(value);
const selectedRows = Object.keys(rowSelection).map(Number);
applyTemplate(value, selectedRows);
}}
>
<SelectTrigger className="w-[180px] h-8 bg-card text-xs font-semibold">
<SelectValue placeholder="Apply template" />
</SelectTrigger>
<SelectContent>
{templates?.map((template) => (
<SelectItem key={template.id} value={template.id.toString()}>
{template.company} - {template.product_type}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="flex items-center">
{/* Wrap in a fragment instead of an IIFE */}
{templates && templates.length > 0 ? (
<SearchableTemplateSelect
templates={templates}
value={selectedTemplateId || ""}
onValueChange={(value) => {
try {
setSelectedTemplateId(value);
const selectedRows = Object.keys(rowSelection).map(Number);
applyTemplate(value, selectedRows);
} catch (error) {
console.error("Error applying template to selection:", error);
toast({
title: "Error",
description: "Failed to apply template to selected rows",
variant: "destructive",
});
}
}}
getTemplateDisplayText={getTemplateDisplayText}
placeholder="Select template"
triggerClassName="w-[200px]"
defaultBrand={globalSelections?.company}
/>
) : (
<Button variant="outline" className="w-full justify-between" disabled>
Loading templates...
</Button>
)}
</div>
{Object.keys(rowSelection).length === 1 && (
<Button