'use client'; import { useState, useEffect, useMemo, useRef, type ReactNode } from 'react'; import { Sliders, Plus, Search, RefreshCw, ChevronDown, ChevronRight, Trash2, Filter, X, Box, FileSpreadsheet, FileText, Columns3, GripVertical, ArrowUpDown, Calendar, AlertTriangle, } from 'lucide-react'; import { toast } from 'sonner'; import { format, subDays } from 'date-fns'; import { AnimatePresence, motion } from '@/lib/motion'; import { catalogService, AttributeTypeResponse } from '@/services/api/catalogService'; import { SlideOver } from '@/components/ui/SlideOver'; import { RowActionsMenu } from '@/components/ui/RowActionsMenu'; import { ViewModeToggle } from '@/components/ui/ViewModeToggle'; import { TablePagination, useClientPagination } from '@/components/ui/TablePagination'; type StatusFilter = 'active' | 'inactive'; type PresetFilter = 'with' | 'without'; type SortField = 'name' | 'code' | 'presets' | 'status'; type SortDir = 'asc' | 'desc'; type SortConfig = { field: SortField; dir: SortDir }; type FilterSection = 'name' | 'presets' | 'status'; type AttributeFilters = { attributeIds: string[]; presets: PresetFilter[]; statuses: StatusFilter[]; }; const EMPTY_FILTERS: AttributeFilters = { attributeIds: [], presets: [], statuses: [], }; const FILTER_PAGE_SIZE = 5; const EMPTY_COLUMNS = { attributeName: true, code: true, presetValues: true, status: true, actions: true, }; const COLUMN_OPTIONS = [ { key: 'attributeName' as const, label: 'Attribute Name', locked: true }, { key: 'code' as const, label: 'System Code', locked: false }, { key: 'presetValues' as const, label: 'Preset Values', locked: false }, { key: 'status' as const, label: 'Status', locked: false }, { key: 'actions' as const, label: 'Actions', locked: false }, ]; const SORT_OPTIONS: { field: SortField; dir: SortDir; label: string }[] = [ { field: 'name', dir: 'asc', label: 'Name A-Z' }, { field: 'name', dir: 'desc', label: 'Name Z-A' }, { field: 'code', dir: 'asc', label: 'Code A-Z' }, { field: 'code', dir: 'desc', label: 'Code Z-A' }, { field: 'presets', dir: 'asc', label: 'Preset Count Ascending' }, { field: 'presets', dir: 'desc', label: 'Preset Count Descending' }, { field: 'status', dir: 'asc', label: 'Status Active first' }, { field: 'status', dir: 'desc', label: 'Status Inactive first' }, ]; const DEFAULT_SORT: SortConfig = { field: 'name', dir: 'asc' }; const DEFAULT_DATE_FROM = format(subDays(new Date(), 30), 'yyyy-MM-dd'); const DEFAULT_DATE_TO = format(new Date(), 'yyyy-MM-dd'); function normalizeId(value: unknown): string { if (value == null) return ''; return String(value).trim(); } function coerceFlag(value: unknown): boolean { if (typeof value === 'boolean') return value; if (typeof value === 'number') return value !== 0; if (value == null) return false; const normalized = String(value).trim().toLowerCase(); if (['true', '1', 'yes', 'enabled', 'active'].includes(normalized)) return true; if (['false', '0', 'no', 'disabled', 'inactive', '', 'null', 'undefined'].includes(normalized)) return false; return false; } function isAttributeActive(status?: string): boolean { if (status == null || String(status).trim() === '') return true; return coerceFlag(status); } function presetList(attribute: AttributeTypeResponse): string[] { return (attribute.preset_values || []).filter((value) => String(value).trim() !== ''); } function dayStartMs(isoDate: string): number { const [year, month, day] = isoDate.split('-').map(Number); if (!year || !month || !day) return NaN; return new Date(year, month - 1, day, 0, 0, 0, 0).getTime(); } function dayEndMs(isoDate: string): number { const [year, month, day] = isoDate.split('-').map(Number); if (!year || !month || !day) return NaN; return new Date(year, month - 1, day, 23, 59, 59, 999).getTime(); } export default function AttributesPage() { const [attributes, setAttributes] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [viewMode, setViewMode] = useState<'table' | 'grid'>('table'); const [openActionId, setOpenActionId] = useState(null); const [showFilterPanel, setShowFilterPanel] = useState(false); const [showExportMenu, setShowExportMenu] = useState(false); const [showColumnsPanel, setShowColumnsPanel] = useState(false); const [showSortPanel, setShowSortPanel] = useState(false); const [showDatePanel, setShowDatePanel] = useState(false); const [draftFilters, setDraftFilters] = useState(EMPTY_FILTERS); const [activeFilters, setActiveFilters] = useState(EMPTY_FILTERS); const [expandedFilterSections, setExpandedFilterSections] = useState>({ name: false, presets: true, status: false, }); const [filterSectionSearch, setFilterSectionSearch] = useState({ name: '' }); const [filterVisibleCounts, setFilterVisibleCounts] = useState({ name: FILTER_PAGE_SIZE }); const [visibleColumns, setVisibleColumns] = useState(EMPTY_COLUMNS); const [sortConfig, setSortConfig] = useState(DEFAULT_SORT); const [dateFrom, setDateFrom] = useState(DEFAULT_DATE_FROM); const [dateTo, setDateTo] = useState(DEFAULT_DATE_TO); const [draftDateFrom, setDraftDateFrom] = useState(DEFAULT_DATE_FROM); const [draftDateTo, setDraftDateTo] = useState(DEFAULT_DATE_TO); const [dateFilterEnabled, setDateFilterEnabled] = useState(false); const filterRef = useRef(null); const exportRef = useRef(null); const columnsRef = useRef(null); const sortRef = useRef(null); const dateRef = useRef(null); const [showAttributeModal, setShowAttributeModal] = useState(false); const [editingAttribute, setEditingAttribute] = useState(null); const [attrName, setAttrName] = useState(''); const [attrCode, setAttrCode] = useState(''); const [presetValues, setPresetValues] = useState([]); const [newPresetInput, setNewPresetInput] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string } | null>(null); const [isDeleting, setIsDeleting] = useState(false); const fetchData = async () => { setLoading(true); try { const fetchedAttrs = await catalogService.getAttributes(); setAttributes(fetchedAttrs); } catch (err: any) { toast.error(err?.message || 'Failed to load master attributes'); } finally { setLoading(false); } }; useEffect(() => { fetchData(); }, []); useEffect(() => { if (!showFilterPanel) return; const onPointerDown = (event: MouseEvent) => { if (!filterRef.current?.contains(event.target as Node)) setShowFilterPanel(false); }; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setShowFilterPanel(false); }; document.addEventListener('mousedown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('mousedown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [showFilterPanel]); useEffect(() => { if (!showExportMenu) return; const onPointerDown = (event: MouseEvent) => { if (!exportRef.current?.contains(event.target as Node)) setShowExportMenu(false); }; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setShowExportMenu(false); }; document.addEventListener('mousedown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('mousedown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [showExportMenu]); useEffect(() => { if (!showColumnsPanel) return; const onPointerDown = (event: MouseEvent) => { if (!columnsRef.current?.contains(event.target as Node)) setShowColumnsPanel(false); }; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setShowColumnsPanel(false); }; document.addEventListener('mousedown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('mousedown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [showColumnsPanel]); useEffect(() => { if (!showSortPanel) return; const onPointerDown = (event: MouseEvent) => { if (!sortRef.current?.contains(event.target as Node)) setShowSortPanel(false); }; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setShowSortPanel(false); }; document.addEventListener('mousedown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('mousedown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [showSortPanel]); useEffect(() => { if (!showDatePanel) return; const onPointerDown = (event: MouseEvent) => { if (!dateRef.current?.contains(event.target as Node)) setShowDatePanel(false); }; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setShowDatePanel(false); }; document.addEventListener('mousedown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('mousedown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [showDatePanel]); const closeOverlays = () => { setShowFilterPanel(false); setShowColumnsPanel(false); setShowSortPanel(false); setShowDatePanel(false); }; const handleViewModeChange = (mode: 'table' | 'grid') => { setViewMode(mode); setOpenActionId(null); closeOverlays(); }; const handleOpenCreate = () => { setEditingAttribute(null); setAttrName(''); setAttrCode(''); setPresetValues([]); setNewPresetInput(''); setShowAttributeModal(true); }; const handleOpenEdit = (attribute: AttributeTypeResponse) => { setEditingAttribute(attribute); setAttrName(attribute.name); setAttrCode(attribute.code); setPresetValues(attribute.preset_values || []); setNewPresetInput(''); setShowAttributeModal(true); }; const closeAttributeDrawer = () => { if (isSubmitting) return; setShowAttributeModal(false); }; const handleAddPresetValue = () => { const val = newPresetInput.trim(); if (!val) return; if (presetValues.some((item) => item.toLowerCase() === val.toLowerCase())) { toast.error('Value already added'); return; } setPresetValues([...presetValues, val]); setNewPresetInput(''); }; const handleRemovePresetValue = (idx: number) => { setPresetValues(presetValues.filter((_, i) => i !== idx)); }; const handleSubmitAttribute = async (e: React.FormEvent) => { e.preventDefault(); if (!attrName.trim() || !attrCode.trim()) { toast.error('Both Attribute Name and System Code are required'); return; } setIsSubmitting(true); try { if (editingAttribute) { const updated = await catalogService.updateAttribute(editingAttribute.attribute_id, { name: attrName.trim(), code: attrCode.trim().toLowerCase(), preset_values: presetValues, }); setAttributes(attributes.map((a) => (a.attribute_id === updated.attribute_id ? updated : a))); toast.success(`Attribute "${updated.name}" updated successfully`); } else { const created = await catalogService.createAttribute({ name: attrName.trim(), code: attrCode.trim().toLowerCase(), preset_values: presetValues, }); setAttributes([...attributes, created]); toast.success(`Attribute "${created.name}" created successfully`); } setAttrName(''); setAttrCode(''); setPresetValues([]); setNewPresetInput(''); setEditingAttribute(null); setShowAttributeModal(false); } catch (err: any) { toast.error(err?.message || `Failed to ${editingAttribute ? 'update' : 'create'} attribute`); } finally { setIsSubmitting(false); } }; const openDeleteModal = (id: string, name: string) => { setOpenActionId(null); setDeleteTarget({ id, name }); }; const closeDeleteModal = () => { if (isDeleting) return; setDeleteTarget(null); }; const handleConfirmDelete = async () => { if (!deleteTarget) return; const { id, name } = deleteTarget; setIsDeleting(true); try { setAttributes((prev) => prev.filter((a) => a.attribute_id !== id)); await catalogService.deleteAttribute(id); toast.success(`Master attribute "${name}" deleted successfully`); setDeleteTarget(null); fetchData(); } catch (err: any) { toast.error(err?.message || 'Failed to delete master attribute'); fetchData(); } finally { setIsDeleting(false); } }; const filteredAttributes = useMemo(() => { const q = searchQuery.trim().toLowerCase(); const fromTs = dateFilterEnabled && dateFrom ? dayStartMs(dateFrom) : null; const toTs = dateFilterEnabled && dateTo ? dayEndMs(dateTo) : null; const result = attributes.filter((a) => { const values = presetList(a); const searchMatch = !q || (a.name || '').toLowerCase().includes(q) || (a.code || '').toLowerCase().includes(q) || values.some((value) => value.toLowerCase().includes(q)); const isActive = isAttributeActive(a.status); const statusMatch = activeFilters.statuses.length === 0 || (activeFilters.statuses.includes('active') && isActive) || (activeFilters.statuses.includes('inactive') && !isActive); const attributeMatch = activeFilters.attributeIds.length === 0 || activeFilters.attributeIds.includes(normalizeId(a.attribute_id)); const hasPresets = values.length > 0; const presetMatch = activeFilters.presets.length === 0 || (activeFilters.presets.includes('with') && hasPresets) || (activeFilters.presets.includes('without') && !hasPresets); let dateMatch = true; if (fromTs != null && toTs != null && !Number.isNaN(fromTs) && !Number.isNaN(toTs) && a.created_at) { const createdTs = new Date(a.created_at).getTime(); if (!Number.isNaN(createdTs)) { dateMatch = createdTs >= fromTs && createdTs <= toTs; } } return searchMatch && attributeMatch && statusMatch && presetMatch && dateMatch; }); const dir = sortConfig.dir === 'asc' ? 1 : -1; return [...result].sort((a, b) => { if (sortConfig.field === 'status') { return (Number(isAttributeActive(a.status)) - Number(isAttributeActive(b.status))) * dir; } if (sortConfig.field === 'presets') { return (presetList(a).length - presetList(b).length) * dir; } const left = sortConfig.field === 'code' ? a.code || '' : a.name || ''; const right = sortConfig.field === 'code' ? b.code || '' : b.name || ''; return left.localeCompare(right, undefined, { sensitivity: 'base' }) * dir; }); }, [attributes, searchQuery, activeFilters, dateFrom, dateTo, dateFilterEnabled, sortConfig]); const pager = useClientPagination(filteredAttributes); const filtersActive = activeFilters.attributeIds.length > 0 || activeFilters.presets.length > 0 || activeFilters.statuses.length > 0; const sortedAttributeFilterOptions = useMemo( () => [...attributes].sort((a, b) => a.name.localeCompare(b.name)), [attributes] ); const toggleFilterSection = (section: FilterSection) => { setExpandedFilterSections((prev) => ({ ...prev, [section]: !prev[section] })); }; const toggleDraftListValue = ( key: keyof Pick, value: T ) => { setDraftFilters((prev) => { const current = prev[key] as T[]; return { ...prev, [key]: current.includes(value) ? current.filter((item) => item !== value) : [...current, value], }; }); }; const openFilterPanel = () => { setDraftFilters(activeFilters); setFilterSectionSearch({ name: '' }); setFilterVisibleCounts({ name: FILTER_PAGE_SIZE }); setShowColumnsPanel(false); setShowSortPanel(false); setShowDatePanel(false); setShowFilterPanel(true); }; const visibleColumnCount = Object.values(visibleColumns).filter(Boolean).length; const toggleColumn = (key: keyof typeof EMPTY_COLUMNS) => { if (key === 'attributeName') return; setVisibleColumns((prev) => ({ ...prev, [key]: !prev[key] })); }; const handleExportExcel = () => { if (filteredAttributes.length === 0) { toast.warning('No attributes to export'); return; } const headers = ['S.No', 'Attribute Name', 'System Code', 'Preset Values', 'Status']; const rows = filteredAttributes.map((a, i) => [ i + 1, a.name, a.code || '', presetList(a).join(', ') || 'None', isAttributeActive(a.status) ? 'Active' : 'Inactive', ]); const csvContent = [headers.join(','), ...rows.map((row) => row.map((val) => `"${String(val).replace(/"/g, '""')}"`).join(','))].join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `ifixkart_attributes_${format(new Date(), 'yyyyMMdd')}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); setShowExportMenu(false); toast.success(`Downloaded ${filteredAttributes.length} ${filteredAttributes.length === 1 ? 'attribute' : 'attributes'} as CSV`); }; const handleExportPDF = () => { if (filteredAttributes.length === 0) { toast.warning('No attributes to export'); return; } const printWindow = window.open('', '_blank'); if (!printWindow) { toast.error('Allow pop-ups to open the attributes print preview'); return; } const html = ` iFixKart Master Attributes PDF Export

iFixKart Master Attributes (${filteredAttributes.length} Records)

Exported on: ${format(new Date(), 'yyyy-MM-dd HH:mm:ss')}

${filteredAttributes.map((a, i) => ` `).join('')}
S.No Attribute Name System Code Preset Values Status
${i + 1} ${a.name} ${a.code || ''} ${presetList(a).join(', ') || 'None'} ${isAttributeActive(a.status) ? 'Active' : 'Inactive'}
`; printWindow.document.write(html); printWindow.document.close(); setShowExportMenu(false); toast.success(`Print preview opened for ${filteredAttributes.length} ${filteredAttributes.length === 1 ? 'attribute' : 'attributes'}`); }; const dataCardShell = 'crm-radius-section border border-border bg-card shadow-[0_1px_3px_rgba(16,24,40,0.06)] overflow-hidden min-w-0'; const renderSortLabel = (label: string, field?: SortField, showIcon = true) => { const active = field != null && sortConfig.field === field; return ( {label} {showIcon && ( )} ); }; const renderStatusBadge = (active: boolean) => ( {active ? 'Active' : 'Inactive'} ); const renderPresetBadges = (values: string[], limit?: number) => { if (values.length === 0) { return No preset values; } const shown = limit != null ? values.slice(0, limit) : values; const remaining = limit != null ? Math.max(values.length - shown.length, 0) : 0; return (
{shown.map((value) => ( {value} ))} {remaining > 0 && ( +{remaining} )}
); }; const searchField = (
setSearchQuery(e.target.value)} className="w-full h-9 pl-9 pr-3 crm-radius-control border border-border bg-card text-[13px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors" />
); const addAttributeButton = ( ); const viewToggle = ; const renderFilterControl = (align: 'left' | 'right') => { const nameQuery = filterSectionSearch.name.trim().toLowerCase(); const filteredNameOptions = sortedAttributeFilterOptions.filter( (attribute) => !nameQuery || attribute.name.toLowerCase().includes(nameQuery) || (attribute.code || '').toLowerCase().includes(nameQuery) ); const visibleNameOptions = filteredNameOptions.slice(0, filterVisibleCounts.name); const renderSectionSearch = (value: string, onChange: (next: string) => void) => (
{ onChange(e.target.value); setFilterVisibleCounts({ name: FILTER_PAGE_SIZE }); }} className="w-full h-8 pl-8 pr-3 crm-radius-control border border-border bg-card text-[12px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary" />
); const renderCheckboxOption = (checked: boolean, onToggle: () => void, label: string, icon?: ReactNode) => ( ); return (
{showFilterPanel && (
Filter
{expandedFilterSections.name && (
{renderSectionSearch(filterSectionSearch.name, (value) => setFilterSectionSearch({ name: value }) )}
{visibleNameOptions.length === 0 ? (

No attributes found.

) : ( visibleNameOptions.map((attribute) => { const id = normalizeId(attribute.attribute_id); return renderCheckboxOption( draftFilters.attributeIds.includes(id), () => toggleDraftListValue('attributeIds', id), attribute.name, (
) ); }) )}
{filteredNameOptions.length > visibleNameOptions.length && ( )}
)}
{expandedFilterSections.presets && (
{renderCheckboxOption( draftFilters.presets.includes('with'), () => toggleDraftListValue('presets', 'with'), 'With presets' )} {renderCheckboxOption( draftFilters.presets.includes('without'), () => toggleDraftListValue('presets', 'without'), 'Without presets' )}
)}
{expandedFilterSections.status && (
{renderCheckboxOption( draftFilters.statuses.includes('active'), () => toggleDraftListValue('statuses', 'active'), 'Active' )} {renderCheckboxOption( draftFilters.statuses.includes('inactive'), () => toggleDraftListValue('statuses', 'inactive'), 'Inactive' )}
)}
)}
); }; const manageColumnsControl = (
{showColumnsPanel && (
{COLUMN_OPTIONS.map((col) => (
{col.label}
))}
)}
); const sortControl = (
{showSortPanel && (
{SORT_OPTIONS.map((option) => { const active = option.field === sortConfig.field && option.dir === sortConfig.dir; return ( ); })}
)}
); const dateRangeLabel = `${format(new Date(dayStartMs(dateFrom || DEFAULT_DATE_FROM)), 'd MMM yy')} - ${format(new Date(dayStartMs(dateTo || DEFAULT_DATE_TO)), 'd MMM yy')}`; const dateControl = (
{showDatePanel && (
setDraftDateFrom(e.target.value)} className="w-full h-8 px-2.5 crm-radius-control border border-border bg-card text-[13px] text-foreground outline-none focus:border-primary" />
setDraftDateTo(e.target.value)} className="w-full h-8 px-2.5 crm-radius-control border border-border bg-card text-[13px] text-foreground outline-none focus:border-primary" />
)}
); const emptyMessage = searchQuery || filtersActive || dateFilterEnabled ? 'No attributes match your search.' : 'No master attributes found.'; return (

Master Attributes

{attributes.length}
{showExportMenu && (
)}
{viewMode === 'grid' ? (
{renderFilterControl('left')} {searchField}
{viewToggle} {addAttributeButton}
{loading ? (
Loading master attributes...
) : filteredAttributes.length === 0 ? (
{emptyMessage}
) : (
{pager.items.map((a) => (
setOpenActionId(open ? a.attribute_id : null)} onEdit={() => handleOpenEdit(a)} onDelete={() => openDeleteModal(a.attribute_id, a.name)} />

{a.name}

{a.code}

{renderPresetBadges(presetList(a), 4)}
{renderStatusBadge(isAttributeActive(a.status))}
))}
)}
) : (
{searchField} {addAttributeButton}
{sortControl} {dateControl}
{renderFilterControl('right')} {manageColumnsControl} {viewToggle}
{loading ? (
Loading master attributes...
) : (
{visibleColumns.attributeName && ( )} {visibleColumns.code && ( )} {visibleColumns.presetValues && ( )} {visibleColumns.status && ( )} {visibleColumns.actions && ( )} {filteredAttributes.length === 0 ? ( ) : ( pager.items.map((a) => ( {visibleColumns.attributeName && ( )} {visibleColumns.code && ( )} {visibleColumns.presetValues && ( )} {visibleColumns.status && ( )} {visibleColumns.actions && ( )} )) )}
{renderSortLabel('Attribute Name', 'name')} {renderSortLabel('System Code', 'code')} {renderSortLabel('Preset Values', 'presets')} {renderSortLabel('Status', 'status')} {renderSortLabel('Action')}
{emptyMessage}

{a.name}

{a.code}

{a.code}{renderPresetBadges(presetList(a), 5)}{renderStatusBadge(isAttributeActive(a.status))}
setOpenActionId(open ? a.attribute_id : null)} onEdit={() => handleOpenEdit(a)} onDelete={() => openDeleteModal(a.attribute_id, a.name)} />
)}
)} {deleteTarget && (
e.stopPropagation()} >

Delete Attribute

Are you sure you want to delete{' '} "{deleteTarget.name}"? Products using this attribute may lose these preset values.

)} } >
setAttrName(e.target.value)} className="w-full h-9 px-3 bg-card border border-border crm-radius-control text-[13px] text-foreground focus:outline-none focus:border-primary transition-colors" />
setAttrCode(e.target.value)} disabled={editingAttribute !== null} className="w-full h-9 px-3 bg-card border border-border crm-radius-control text-[13px] text-foreground focus:outline-none focus:border-primary transition-colors disabled:opacity-60 disabled:bg-muted/40" />

These values appear as dropdown choices when creating or editing products.

setNewPresetInput(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAddPresetValue(); } }} className="flex-1 h-9 px-3 bg-card border border-border crm-radius-control text-[13px] text-foreground outline-none focus:border-primary" />
{presetValues.length === 0 ? ( No preset values added yet. Type a value above and click Add. ) : ( presetValues.map((val, idx) => ( {val} )) )}
); }