'use client'; import { useState, useEffect, useMemo, useRef, type ReactNode } from 'react'; import { Smartphone, Tag, Plus, Search, RefreshCw, ChevronDown, ChevronRight, Trash2, Filter, X, Box, FileSpreadsheet, FileText, Columns3, GripVertical, ArrowUpDown, AlertTriangle, } from 'lucide-react'; import { toast } from 'sonner'; import { format } from 'date-fns'; import { AnimatePresence, motion } from '@/lib/motion'; import { catalogService, BrandResponse, DeviceSeriesResponse, DeviceModelResponse, } 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'; import ImageUpload from '@/components/ui/ImageUpload'; import { CustomSelect } from '@/components/ui/CustomSelect'; import { getMediaUrl } from '@/services/api/config'; const DEVICE_TYPES = ['laptop', 'tablet', 'mobile'] as const; type DeviceType = (typeof DEVICE_TYPES)[number]; type StatusFilter = 'active' | 'inactive'; type SortField = 'name' | 'slug' | 'brand' | 'series' | 'releaseYear' | 'status'; type SortDir = 'asc' | 'desc'; type SortConfig = { field: SortField; dir: SortDir }; type FilterSection = 'name' | 'brand' | 'series' | 'deviceTypes' | 'status'; type ModelFilters = { modelIds: string[]; brandIds: string[]; seriesIds: string[]; deviceTypes: DeviceType[]; statuses: StatusFilter[]; }; const EMPTY_FILTERS: ModelFilters = { modelIds: [], brandIds: [], seriesIds: [], deviceTypes: [], statuses: [], }; const FILTER_PAGE_SIZE = 5; const EMPTY_COLUMNS = { modelName: true, slug: true, brand: true, series: true, deviceType: true, releaseYear: true, fullPath: true, status: true, actions: true, }; const COLUMN_OPTIONS = [ { key: 'modelName' as const, label: 'Model Name', locked: true }, { key: 'slug' as const, label: 'Slug / Path', locked: false }, { key: 'brand' as const, label: 'Brand', locked: false }, { key: 'series' as const, label: 'Series', locked: false }, { key: 'deviceType' as const, label: 'Device Type', locked: false }, { key: 'releaseYear' as const, label: 'Release Year', locked: false }, { key: 'fullPath' as const, label: 'Full Path', 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: 'slug', dir: 'asc', label: 'Slug A-Z' }, { field: 'slug', dir: 'desc', label: 'Slug Z-A' }, { field: 'brand', dir: 'asc', label: 'Brand A-Z' }, { field: 'brand', dir: 'desc', label: 'Brand Z-A' }, { field: 'series', dir: 'asc', label: 'Series A-Z' }, { field: 'series', dir: 'desc', label: 'Series Z-A' }, { field: 'releaseYear', dir: 'asc', label: 'Release Year Ascending' }, { field: 'releaseYear', dir: 'desc', label: 'Release Year 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' }; function normalizeId(value: unknown): string { if (value == null) return ''; return String(value).trim(); } function idsEqual(a: unknown, b: unknown): boolean { const left = normalizeId(a); const right = normalizeId(b); return left !== '' && right !== '' && left === right; } 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 formatDeviceTypeLabel(value?: string | null): string { if (!value) return '—'; return value.charAt(0).toUpperCase() + value.slice(1); } export default function DeviceModelsPage() { const [brands, setBrands] = useState([]); const [seriesList, setSeriesList] = useState([]); const [models, setModels] = 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 [draftFilters, setDraftFilters] = useState(EMPTY_FILTERS); const [activeFilters, setActiveFilters] = useState(EMPTY_FILTERS); const [expandedFilterSections, setExpandedFilterSections] = useState>({ name: false, brand: false, series: false, deviceTypes: true, status: false, }); const [filterSectionSearch, setFilterSectionSearch] = useState({ name: '', brand: '', series: '' }); const [filterVisibleCounts, setFilterVisibleCounts] = useState({ name: FILTER_PAGE_SIZE, brand: FILTER_PAGE_SIZE, series: FILTER_PAGE_SIZE, }); const [visibleColumns, setVisibleColumns] = useState(EMPTY_COLUMNS); const [sortConfig, setSortConfig] = useState(DEFAULT_SORT); const filterRef = useRef(null); const exportRef = useRef(null); const columnsRef = useRef(null); const sortRef = useRef(null); const [showModelDrawer, setShowModelDrawer] = useState(false); const [editingModel, setEditingModel] = useState(null); const [modelName, setModelName] = useState(''); const [modelSeriesId, setModelSeriesId] = useState(''); const [modelBrandId, setModelBrandId] = useState(''); const [modelReleaseYear, setModelReleaseYear] = useState(''); const [modelImg, setModelImg] = useState(''); const [modelDeviceType, setModelDeviceType] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string } | null>(null); const [isDeleting, setIsDeleting] = useState(false); const brandMap = useMemo(() => { const map = new Map(); brands.forEach((brand) => map.set(normalizeId(brand.brand_id), brand)); return map; }, [brands]); const seriesMap = useMemo(() => { const map = new Map(); seriesList.forEach((s) => map.set(normalizeId(s.series_id), s)); return map; }, [seriesList]); const getBrandName = (brandId: string) => brandMap.get(normalizeId(brandId))?.name || '—'; const getSeriesName = (seriesId: string) => seriesMap.get(normalizeId(seriesId))?.name || '—'; const fetchData = async () => { setLoading(true); try { const [fetchedBrands, fetchedSeries, fetchedModels] = await Promise.all([ catalogService.getBrands(), catalogService.getDeviceSeries(), catalogService.getDeviceModels(), ]); setBrands(fetchedBrands); setSeriesList(fetchedSeries); setModels(fetchedModels); } catch (err: any) { toast.error(err?.message || 'Failed to load device models data'); } finally { setLoading(false); } }; useEffect(() => { fetchData(); }, []); // Auto-inherit device_type from selected series useEffect(() => { if (modelSeriesId) { const selectedSeries = seriesList.find((s) => idsEqual(s.series_id, modelSeriesId)); if (selectedSeries?.device_type) { setModelDeviceType(selectedSeries.device_type); } } }, [modelSeriesId, seriesList]); 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]); const closeOverlays = () => { setShowFilterPanel(false); setShowColumnsPanel(false); setShowSortPanel(false); }; const handleViewModeChange = (mode: 'table' | 'grid') => { setViewMode(mode); setOpenActionId(null); closeOverlays(); }; const selectedBrand = brands.find((b) => idsEqual(b.brand_id, modelBrandId)); const brandDeviceTypes = selectedBrand?.device_types || []; const selectedSeries = seriesList.find((s) => idsEqual(s.series_id, modelSeriesId)); const handleOpenCreate = () => { setEditingModel(null); setModelName(''); setModelSeriesId(''); setModelBrandId(''); setModelReleaseYear(''); setModelImg(''); setModelDeviceType(''); setShowModelDrawer(true); }; const handleOpenEdit = (model: DeviceModelResponse) => { setEditingModel(model); setModelName(model.name); setModelBrandId(model.brand_id); setModelSeriesId(model.series_id || ''); setModelReleaseYear(model.release_year ? model.release_year.toString() : ''); setModelImg(model.image_url || ''); setModelDeviceType(model.device_type || ''); setShowModelDrawer(true); }; const closeModelDrawer = () => { if (isSubmitting) return; setShowModelDrawer(false); }; const handleBrandChange = (brandId: string) => { setModelBrandId(brandId); setModelSeriesId(''); setModelDeviceType(''); }; const handleDeviceTypeChange = (deviceType: string) => { setModelDeviceType(deviceType); setModelSeriesId(''); }; const handleSubmitModel = async (e: React.FormEvent) => { e.preventDefault(); if (!modelName.trim() || !modelBrandId) { toast.error('Name and Brand are required'); return; } const finalDeviceType = brandDeviceTypes.length > 1 ? modelDeviceType || null : selectedSeries?.device_type || brandDeviceTypes[0] || null; setIsSubmitting(true); try { if (editingModel) { const updated = await catalogService.updateDeviceModel(editingModel.model_id, { series_id: modelSeriesId || undefined, brand_id: modelBrandId, name: modelName.trim(), device_type: finalDeviceType, release_year: modelReleaseYear ? parseInt(modelReleaseYear, 10) : undefined, image_url: modelImg.trim() || undefined, }); setModels(models.map((m) => (m.model_id === updated.model_id ? updated : m))); toast.success(`Model "${updated.name}" updated successfully`); } else { const created = await catalogService.createDeviceModel({ series_id: modelSeriesId || undefined, brand_id: modelBrandId, name: modelName.trim(), device_type: finalDeviceType, release_year: modelReleaseYear ? parseInt(modelReleaseYear, 10) : undefined, image_url: modelImg.trim() || undefined, }); setModels([...models, created]); toast.success(`Model "${created.name}" created successfully`); } setModelName(''); setModelSeriesId(''); setModelBrandId(''); setModelReleaseYear(''); setModelImg(''); setModelDeviceType(''); setEditingModel(null); setShowModelDrawer(false); } catch (err: any) { toast.error(err?.message || `Failed to ${editingModel ? 'update' : 'create'} model`); } 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 { setModels((prev) => prev.filter((m) => m.model_id !== id)); await catalogService.deleteDeviceModel(id); toast.success(`Device model "${name}" deleted successfully`); setDeleteTarget(null); fetchData(); } catch (err: any) { toast.error(err?.message || 'Failed to delete device model'); fetchData(); } finally { setIsDeleting(false); } }; const filteredModels = useMemo(() => { const q = searchQuery.trim().toLowerCase(); const result = models.filter((m) => { const brandName = getBrandName(m.brand_id); const seriesName = getSeriesName(m.series_id); const searchMatch = !q || (m.name || '').toLowerCase().includes(q) || (m.slug || '').toLowerCase().includes(q) || brandName.toLowerCase().includes(q) || seriesName.toLowerCase().includes(q) || (m.device_type || '').toLowerCase().includes(q) || (m.full_path || '').toLowerCase().includes(q) || (m.release_year != null && String(m.release_year).includes(q)); const isActive = coerceFlag(m.is_active); const statusMatch = activeFilters.statuses.length === 0 || (activeFilters.statuses.includes('active') && isActive) || (activeFilters.statuses.includes('inactive') && !isActive); const modelMatch = activeFilters.modelIds.length === 0 || activeFilters.modelIds.includes(normalizeId(m.model_id)); const brandMatch = activeFilters.brandIds.length === 0 || activeFilters.brandIds.includes(normalizeId(m.brand_id)); const seriesMatch = activeFilters.seriesIds.length === 0 || activeFilters.seriesIds.includes(normalizeId(m.series_id)); const deviceMatch = activeFilters.deviceTypes.length === 0 || (m.device_type != null && activeFilters.deviceTypes.includes(m.device_type as DeviceType)); return searchMatch && modelMatch && brandMatch && seriesMatch && statusMatch && deviceMatch; }); const dir = sortConfig.dir === 'asc' ? 1 : -1; return [...result].sort((a, b) => { if (sortConfig.field === 'releaseYear') { const left = a.release_year ?? -Infinity; const right = b.release_year ?? -Infinity; return (left - right) * dir; } if (sortConfig.field === 'status') { return (Number(coerceFlag(a.is_active)) - Number(coerceFlag(b.is_active))) * dir; } if (sortConfig.field === 'brand') { return getBrandName(a.brand_id).localeCompare(getBrandName(b.brand_id), undefined, { sensitivity: 'base', }) * dir; } if (sortConfig.field === 'series') { return getSeriesName(a.series_id).localeCompare(getSeriesName(b.series_id), undefined, { sensitivity: 'base', }) * dir; } const left = sortConfig.field === 'name' ? a.name || '' : a.slug || ''; const right = sortConfig.field === 'name' ? b.name || '' : b.slug || ''; return left.localeCompare(right, undefined, { sensitivity: 'base' }) * dir; }); }, [models, searchQuery, activeFilters, sortConfig, brandMap, seriesMap]); const pager = useClientPagination(filteredModels); const filtersActive = activeFilters.modelIds.length > 0 || activeFilters.brandIds.length > 0 || activeFilters.seriesIds.length > 0 || activeFilters.deviceTypes.length > 0 || activeFilters.statuses.length > 0; const sortedModelFilterOptions = useMemo( () => [...models].sort((a, b) => a.name.localeCompare(b.name)), [models] ); const sortedBrandFilterOptions = useMemo( () => [...brands].sort((a, b) => a.name.localeCompare(b.name)), [brands] ); const sortedSeriesFilterOptions = useMemo( () => [...seriesList].sort((a, b) => a.name.localeCompare(b.name)), [seriesList] ); 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: '', brand: '', series: '' }); setFilterVisibleCounts({ name: FILTER_PAGE_SIZE, brand: FILTER_PAGE_SIZE, series: FILTER_PAGE_SIZE }); setShowColumnsPanel(false); setShowSortPanel(false); setShowFilterPanel(true); }; const visibleColumnCount = Object.values(visibleColumns).filter(Boolean).length; const toggleColumn = (key: keyof typeof EMPTY_COLUMNS) => { if (key === 'modelName') return; setVisibleColumns((prev) => ({ ...prev, [key]: !prev[key] })); }; const handleExportExcel = () => { if (filteredModels.length === 0) { toast.warning('No device models to export'); return; } const headers = [ 'S.No', 'Model Name', 'Slug / Path', 'Brand', 'Series', 'Device Type', 'Release Year', 'Full Path', 'Status', ]; const rows = filteredModels.map((m, i) => [ i + 1, m.name, m.slug || '', getBrandName(m.brand_id), getSeriesName(m.series_id), formatDeviceTypeLabel(m.device_type), m.release_year ?? '', m.full_path || '', coerceFlag(m.is_active) ? '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_device_models_${format(new Date(), 'yyyyMMdd')}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); setShowExportMenu(false); toast.success(`Downloaded ${filteredModels.length} ${filteredModels.length === 1 ? 'device model' : 'device models'} as CSV`); }; const handleExportPDF = () => { if (filteredModels.length === 0) { toast.warning('No device models to export'); return; } const printWindow = window.open('', '_blank'); if (!printWindow) { toast.error('Allow pop-ups to open the device models print preview'); return; } const html = ` iFixKart Device Models PDF Export

iFixKart Device Models (${filteredModels.length} Records)

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

${filteredModels .map( (m, i) => ` ` ) .join('')}
S.No Model Name Slug / Path Brand Series Device Type Release Year Full Path Status
${i + 1} ${m.name} ${m.slug || ''} ${getBrandName(m.brand_id)} ${getSeriesName(m.series_id)} ${formatDeviceTypeLabel(m.device_type)} ${m.release_year ?? '—'} ${m.full_path || ''} ${coerceFlag(m.is_active) ? 'Active' : 'Inactive'}
`; printWindow.document.write(html); printWindow.document.close(); setShowExportMenu(false); toast.success(`Print preview opened for ${filteredModels.length} ${filteredModels.length === 1 ? 'device model' : 'device models'}`); }; 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 renderDeviceTypeBadge = (deviceType?: string | null) => { if (!deviceType) { return ; } return ( {deviceType} ); }; const renderBrandAvatar = (brand: BrandResponse, size: 'sm' | 'md' = 'md') => { const dim = size === 'sm' ? 'w-9 h-9' : 'w-10 h-10'; return (
{brand.logo_url ? ( {brand.name} ) : ( )}
); }; const renderModelAvatar = (model: DeviceModelResponse, size: 'sm' | 'md' = 'md') => { const dim = size === 'sm' ? 'w-9 h-9' : 'w-10 h-10'; return (
{model.image_url ? ( {model.name} ) : ( )}
); }; 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 addModelButton = ( ); const viewToggle = ; const renderFilterControl = (align: 'left' | 'right') => { const nameQuery = filterSectionSearch.name.trim().toLowerCase(); const brandQuery = filterSectionSearch.brand.trim().toLowerCase(); const seriesQuery = filterSectionSearch.series.trim().toLowerCase(); const filteredNameOptions = sortedModelFilterOptions.filter( (model) => !nameQuery || model.name.toLowerCase().includes(nameQuery) || (model.slug || '').toLowerCase().includes(nameQuery) ); const filteredBrandOptions = sortedBrandFilterOptions.filter( (brand) => !brandQuery || brand.name.toLowerCase().includes(brandQuery) || (brand.slug || '').toLowerCase().includes(brandQuery) ); const filteredSeriesOptions = sortedSeriesFilterOptions.filter( (s) => !seriesQuery || s.name.toLowerCase().includes(seriesQuery) || (s.slug || '').toLowerCase().includes(seriesQuery) ); const visibleNameOptions = filteredNameOptions.slice(0, filterVisibleCounts.name); const visibleBrandOptions = filteredBrandOptions.slice(0, filterVisibleCounts.brand); const visibleSeriesOptions = filteredSeriesOptions.slice(0, filterVisibleCounts.series); const renderSectionSearch = ( section: 'name' | 'brand' | 'series', value: string, onChange: (next: string) => void ) => (
{ onChange(e.target.value); setFilterVisibleCounts((prev) => ({ ...prev, [section]: 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('name', filterSectionSearch.name, (value) => setFilterSectionSearch((prev) => ({ ...prev, name: value })) )}
{visibleNameOptions.length === 0 ? (

No models found.

) : ( visibleNameOptions.map((model) => { const id = normalizeId(model.model_id); return renderCheckboxOption( draftFilters.modelIds.includes(id), () => toggleDraftListValue('modelIds', id), model.name, renderModelAvatar(model, 'sm') ); }) )}
{filteredNameOptions.length > visibleNameOptions.length && ( )}
)}
{expandedFilterSections.brand && (
{renderSectionSearch('brand', filterSectionSearch.brand, (value) => setFilterSectionSearch((prev) => ({ ...prev, brand: value })) )}
{visibleBrandOptions.length === 0 ? (

No brands found.

) : ( visibleBrandOptions.map((brand) => { const id = normalizeId(brand.brand_id); return renderCheckboxOption( draftFilters.brandIds.includes(id), () => toggleDraftListValue('brandIds', id), brand.name, renderBrandAvatar(brand, 'sm') ); }) )}
{filteredBrandOptions.length > visibleBrandOptions.length && ( )}
)}
{expandedFilterSections.series && (
{renderSectionSearch('series', filterSectionSearch.series, (value) => setFilterSectionSearch((prev) => ({ ...prev, series: value })) )}
{visibleSeriesOptions.length === 0 ? (

No series found.

) : ( visibleSeriesOptions.map((s) => { const id = normalizeId(s.series_id); return renderCheckboxOption( draftFilters.seriesIds.includes(id), () => toggleDraftListValue('seriesIds', id), s.name, (
) ); }) )}
{filteredSeriesOptions.length > visibleSeriesOptions.length && ( )}
)}
{expandedFilterSections.deviceTypes && (
{DEVICE_TYPES.map((dt) => renderCheckboxOption( draftFilters.deviceTypes.includes(dt), () => toggleDraftListValue('deviceTypes', dt), dt.charAt(0).toUpperCase() + dt.slice(1) ) )}
)}
{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 emptyMessage = searchQuery || filtersActive ? 'No device models match your search.' : 'No device models found.'; return (

Device Models

{models.length}
{showExportMenu && (
)}
{viewMode === 'grid' ? (
{renderFilterControl('left')} {searchField}
{viewToggle} {addModelButton}
{loading ? (
Loading device models...
) : filteredModels.length === 0 ? (
{emptyMessage}
) : (
{pager.items.map((m) => (
setOpenActionId(open ? m.model_id : null)} onEdit={() => handleOpenEdit(m)} onDelete={() => openDeleteModal(m.model_id, m.name)} />
{renderModelAvatar(m, 'md')}

{m.name}

{m.slug}

Brand: {getBrandName(m.brand_id)}

Series: {getSeriesName(m.series_id)}

{m.release_year &&

Release Year: {m.release_year}

} {m.full_path && (

{m.full_path}

)}
{renderDeviceTypeBadge(m.device_type)} {renderStatusBadge(coerceFlag(m.is_active))}
))}
)}
) : (
{searchField} {addModelButton}
{sortControl}
{renderFilterControl('right')} {manageColumnsControl} {viewToggle}
{loading ? (
Loading device models...
) : (
{visibleColumns.modelName && ( )} {visibleColumns.slug && ( )} {visibleColumns.brand && ( )} {visibleColumns.series && ( )} {visibleColumns.deviceType && ( )} {visibleColumns.releaseYear && ( )} {visibleColumns.fullPath && ( )} {visibleColumns.status && ( )} {visibleColumns.actions && ( )} {filteredModels.length === 0 ? ( ) : ( pager.items.map((m) => ( {visibleColumns.modelName && ( )} {visibleColumns.slug && ( )} {visibleColumns.brand && ( )} {visibleColumns.series && ( )} {visibleColumns.deviceType && ( )} {visibleColumns.releaseYear && ( )} {visibleColumns.fullPath && ( )} {visibleColumns.status && ( )} {visibleColumns.actions && ( )} )) )}
{renderSortLabel('Model Name', 'name')} {renderSortLabel('Slug / Path', 'slug')} {renderSortLabel('Brand', 'brand')} {renderSortLabel('Series', 'series')} {renderSortLabel('Device Type')} {renderSortLabel('Release Year', 'releaseYear')} {renderSortLabel('Full Path')} {renderSortLabel('Status', 'status')} {renderSortLabel('Action')}
{emptyMessage}
{renderModelAvatar(m, 'sm')}

{m.name}

{m.slug}

{m.slug} {getBrandName(m.brand_id)}{getSeriesName(m.series_id)}{renderDeviceTypeBadge(m.device_type)}{m.release_year ?? '—'} {m.full_path || '—'} {renderStatusBadge(coerceFlag(m.is_active))}
setOpenActionId(open ? m.model_id : null)} onEdit={() => handleOpenEdit(m)} onDelete={() => openDeleteModal(m.model_id, m.name)} />
)}
)} {deleteTarget && (
e.stopPropagation()} >

Delete Device Model

Are you sure you want to delete{' '} "{deleteTarget.name}" and all its related repair services?

)} } >
({ value: brand.brand_id, label: brand.name })), ]} />
({ value: dt, label: dt })), ]} />
{modelBrandId && modelDeviceType && (
idsEqual(s.brand_id, modelBrandId) && s.device_type === modelDeviceType ) .map((s) => ({ value: s.series_id, label: s.name })), ]} />
setModelName(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" />
)}
setModelReleaseYear(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" />
setModelImg(url)} onClear={() => setModelImg('')} />
); }