'use client'; import { useState, useEffect, useMemo, useRef } from 'react'; import { Smartphone, Plus, Search, Layers, RefreshCw, X, Wrench, Package, Cpu, Trash2, ChevronDown, Box, FileSpreadsheet, Columns3, GripVertical, ArrowUpDown, Calendar, AlertTriangle, } from 'lucide-react'; import { toast } from 'sonner'; import { format, subDays } from 'date-fns'; import { catalogService, BrandResponse, DeviceSeriesResponse, DeviceModelResponse, ServiceTypeResponse, RepairServiceResponse, RepairVariantResponse, PartResponse, } 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 { CustomSelect } from '@/components/ui/CustomSelect'; import { getMediaUrl } from '@/services/api/config'; type ActiveTab = 'models' | 'services' | 'variants'; type StatusFilter = 'active' | 'inactive'; type ModelSortField = 'name' | 'brand' | 'series' | 'releaseYear' | 'status'; type ServiceSortField = 'model' | 'type' | 'path'; type VariantSortField = 'name' | 'price' | 'status'; type SortDir = 'asc' | 'desc'; const FILTER_PAGE_SIZE = 5; const DEFAULT_DATE_FROM = format(subDays(new Date(), 30), 'yyyy-MM-dd'); const DEFAULT_DATE_TO = format(new Date(), 'yyyy-MM-dd'); 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 MODEL_COLUMNS = { modelName: true, brand: true, series: true, fullPath: true, releaseYear: true, status: true, actions: true, }; const MODEL_COLUMN_OPTIONS = [ { key: 'modelName' as const, label: 'Model Name', locked: true }, { key: 'brand' as const, label: 'Brand', locked: false }, { key: 'series' as const, label: 'Series', locked: false }, { key: 'fullPath' as const, label: 'Full Path', locked: false }, { key: 'releaseYear' as const, label: 'Release Year', locked: false }, { key: 'status' as const, label: 'Status', locked: false }, { key: 'actions' as const, label: 'Actions', locked: false }, ]; const MODEL_SORT_OPTIONS: { field: ModelSortField; dir: SortDir; label: string }[] = [ { field: 'name', dir: 'asc', label: 'Name A-Z' }, { field: 'name', dir: 'desc', label: 'Name Z-A' }, { field: 'brand', dir: 'asc', label: 'Brand A-Z' }, { field: 'series', dir: 'asc', label: 'Series A-Z' }, { field: 'releaseYear', dir: 'desc', label: 'Release Year (newest)' }, { field: 'status', dir: 'asc', label: 'Status Active first' }, ]; 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 n = String(value).trim().toLowerCase(); if (['true', '1', 'yes', 'active'].includes(n)) return true; if (['false', '0', 'no', 'inactive', ''].includes(n)) return false; return false; } function dayStartMs(isoDate: string): number { const [y, m, d] = isoDate.split('-').map(Number); if (!y || !m || !d) return NaN; return new Date(y, m - 1, d, 0, 0, 0, 0).getTime(); } function dayEndMs(isoDate: string): number { const [y, m, d] = isoDate.split('-').map(Number); if (!y || !m || !d) return NaN; return new Date(y, m - 1, d, 23, 59, 59, 999).getTime(); } export default function DevicesPage() { const [activeTab, setActiveTab] = useState('models'); const [brands, setBrands] = useState([]); const [series, setSeries] = useState([]); const [models, setModels] = useState([]); const [serviceTypes, setServiceTypes] = useState([]); const [repairServices, setRepairServices] = useState([]); const [variants, setVariants] = useState([]); const [parts, setParts] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [viewMode, setViewMode] = useState<'table' | 'grid'>('table'); const [openActionId, setOpenActionId] = useState(null); const [showExportMenu, setShowExportMenu] = useState(false); const [showFilterPanel, setShowFilterPanel] = useState(false); const [showColumnsPanel, setShowColumnsPanel] = useState(false); const [showSortPanel, setShowSortPanel] = useState(false); const [showDatePanel, setShowDatePanel] = useState(false); const [visibleColumns, setVisibleColumns] = useState(MODEL_COLUMNS); const [modelSort, setModelSort] = useState<{ field: ModelSortField; dir: SortDir }>({ field: 'name', dir: 'asc' }); const [serviceSort, setServiceSort] = useState<{ field: ServiceSortField; dir: SortDir }>({ field: 'model', dir: 'asc' }); const [variantSort, setVariantSort] = useState<{ field: VariantSortField; dir: SortDir }>({ field: 'name', dir: 'asc' }); const [draftModelFilters, setDraftModelFilters] = useState({ modelIds: [] as string[], brandIds: [] as string[], seriesIds: [] as string[], statuses: [] as StatusFilter[] }); const [activeModelFilters, setActiveModelFilters] = useState({ modelIds: [] as string[], brandIds: [] as string[], seriesIds: [] as string[], statuses: [] as StatusFilter[] }); const [draftServiceFilters, setDraftServiceFilters] = useState({ modelIds: [] as string[], typeIds: [] as string[] }); const [activeServiceFilters, setActiveServiceFilters] = useState({ modelIds: [] as string[], typeIds: [] as string[] }); const [draftVariantFilters, setDraftVariantFilters] = useState({ statuses: [] as string[] }); const [activeVariantFilters, setActiveVariantFilters] = useState({ statuses: [] as string[] }); const [expandedFilterSections, setExpandedFilterSections] = useState>({ name: false, brand: false, series: false, status: true, model: false, type: 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 [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 exportRef = useRef(null); const filterRef = useRef(null); const columnsRef = useRef(null); const sortRef = useRef(null); const dateRef = useRef(null); const [showSeriesModal, setShowSeriesModal] = useState(false); const [seriesName, setSeriesName] = useState(''); const [seriesBrandId, setSeriesBrandId] = useState(''); const [isSubmittingSeries, setIsSubmittingSeries] = useState(false); const [showModelModal, setShowModelModal] = 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 [isSubmittingModel, setIsSubmittingModel] = useState(false); const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string } | null>(null); const [isDeleting, setIsDeleting] = useState(false); const [showTypeModal, setShowTypeModal] = useState(false); const [typeName, setTypeName] = useState(''); const [typeDesc, setTypeDesc] = useState(''); const [typeIcon, setTypeIcon] = useState(''); const [showServiceModal, setShowServiceModal] = useState(false); const [serviceModelId, setServiceModelId] = useState(''); const [serviceTypeId, setServiceTypeId] = useState(''); const [serviceDesc, setServiceDesc] = useState(''); const [showVariantModal, setShowVariantModal] = useState(false); const [variantServiceId, setVariantServiceId] = useState(''); const [variantName, setVariantName] = useState(''); const [variantPrice, setVariantPrice] = useState(''); const [variantCost, setVariantCost] = useState(''); const [variantDuration, setVariantDuration] = useState('45'); const [variantWarranty, setVariantWarranty] = useState('90'); const [bomParts, setBomParts] = useState>([]); const fetchData = async () => { setLoading(true); try { const [ fetchedBrands, fetchedSeries, fetchedModels, fetchedTypes, fetchedServices, fetchedVariants, fetchedParts, ] = await Promise.all([ catalogService.getBrands(), catalogService.getDeviceSeries(), catalogService.getDeviceModels(), catalogService.getServiceTypes(), catalogService.getRepairServices(), catalogService.getRepairVariants(), catalogService.getParts(), ]); setBrands(fetchedBrands); setSeries(fetchedSeries); setModels(fetchedModels); setServiceTypes(fetchedTypes); setRepairServices(fetchedServices); setVariants(fetchedVariants); setParts(fetchedParts); } catch (err: any) { toast.error(err?.message || 'Failed to load catalog datasets'); } finally { setLoading(false); } }; useEffect(() => { fetchData(); }, []); useEffect(() => { const refs = [ { open: showFilterPanel, ref: filterRef, close: () => setShowFilterPanel(false) }, { open: showExportMenu, ref: exportRef, close: () => setShowExportMenu(false) }, { open: showColumnsPanel, ref: columnsRef, close: () => setShowColumnsPanel(false) }, { open: showSortPanel, ref: sortRef, close: () => setShowSortPanel(false) }, { open: showDatePanel, ref: dateRef, close: () => setShowDatePanel(false) }, ]; const active = refs.filter((r) => r.open); if (active.length === 0) return; const onPointerDown = (e: MouseEvent) => { if (active.some((r) => r.ref.current?.contains(e.target as Node))) return; active.forEach((r) => r.close()); }; const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') active.forEach((r) => r.close()); }; document.addEventListener('mousedown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('mousedown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [showFilterPanel, showExportMenu, showColumnsPanel, showSortPanel, showDatePanel]); const getBrandName = (id: string) => brands.find((b) => b.brand_id === id)?.name || 'Unknown'; const getSeriesName = (id: string) => series.find((s) => s.series_id === id)?.name || 'Unknown'; const getModelName = (id: string) => models.find((m) => m.model_id === id)?.name || 'Unknown'; const getTypeName = (id: string) => serviceTypes.find((t) => t.service_type_id === id)?.name || 'Unknown'; const modelFiltersActive = activeModelFilters.modelIds.length > 0 || activeModelFilters.brandIds.length > 0 || activeModelFilters.seriesIds.length > 0 || activeModelFilters.statuses.length > 0; const serviceFiltersActive = activeServiceFilters.modelIds.length > 0 || activeServiceFilters.typeIds.length > 0; const variantFiltersActive = activeVariantFilters.statuses.length > 0; const filteredModels = useMemo(() => { const q = searchQuery.trim().toLowerCase(); const dir = modelSort.dir === 'asc' ? 1 : -1; const result = models.filter((m) => { const searchMatch = !q || m.name.toLowerCase().includes(q) || m.full_path.toLowerCase().includes(q); const brandMatch = activeModelFilters.brandIds.length === 0 || activeModelFilters.brandIds.includes(m.brand_id); const seriesMatch = activeModelFilters.seriesIds.length === 0 || activeModelFilters.seriesIds.includes(m.series_id); const modelMatch = activeModelFilters.modelIds.length === 0 || activeModelFilters.modelIds.includes(m.model_id); const isActive = coerceFlag(m.is_active); const statusMatch = activeModelFilters.statuses.length === 0 || (activeModelFilters.statuses.includes('active') && isActive) || (activeModelFilters.statuses.includes('inactive') && !isActive); return searchMatch && brandMatch && seriesMatch && modelMatch && statusMatch; }); return [...result].sort((a, b) => { if (modelSort.field === 'status') return (Number(coerceFlag(a.is_active)) - Number(coerceFlag(b.is_active))) * dir; if (modelSort.field === 'releaseYear') return ((a.release_year || 0) - (b.release_year || 0)) * dir; if (modelSort.field === 'brand') return getBrandName(a.brand_id).localeCompare(getBrandName(b.brand_id)) * dir; if (modelSort.field === 'series') return getSeriesName(a.series_id).localeCompare(getSeriesName(b.series_id)) * dir; return a.name.localeCompare(b.name) * dir; }); }, [models, searchQuery, activeModelFilters, modelSort, brands, series]); const filteredServices = useMemo(() => { const q = searchQuery.trim().toLowerCase(); const dir = serviceSort.dir === 'asc' ? 1 : -1; const result = repairServices.filter((rs) => { const modelName = getModelName(rs.model_id); const typeName = getTypeName(rs.service_type_id); const searchMatch = !q || modelName.toLowerCase().includes(q) || typeName.toLowerCase().includes(q) || rs.full_path.toLowerCase().includes(q); const modelMatch = activeServiceFilters.modelIds.length === 0 || activeServiceFilters.modelIds.includes(rs.model_id); const typeMatch = activeServiceFilters.typeIds.length === 0 || activeServiceFilters.typeIds.includes(rs.service_type_id); return searchMatch && modelMatch && typeMatch; }); return [...result].sort((a, b) => { if (serviceSort.field === 'type') return getTypeName(a.service_type_id).localeCompare(getTypeName(b.service_type_id)) * dir; if (serviceSort.field === 'path') return a.full_path.localeCompare(b.full_path) * dir; return getModelName(a.model_id).localeCompare(getModelName(b.model_id)) * dir; }); }, [repairServices, searchQuery, activeServiceFilters, serviceSort, models, serviceTypes]); const filteredVariants = useMemo(() => { const q = searchQuery.trim().toLowerCase(); const dir = variantSort.dir === 'asc' ? 1 : -1; const fromTs = dateFilterEnabled && dateFrom ? dayStartMs(dateFrom) : null; const toTs = dateFilterEnabled && dateTo ? dayEndMs(dateTo) : null; const result = variants.filter((v) => { const svc = repairServices.find((s) => s.repair_service_id === v.repair_service_id); const modelName = svc ? getModelName(svc.model_id) : ''; const typeName = svc ? getTypeName(svc.service_type_id) : ''; const searchMatch = !q || v.name.toLowerCase().includes(q) || modelName.toLowerCase().includes(q) || typeName.toLowerCase().includes(q); const statusMatch = activeVariantFilters.statuses.length === 0 || activeVariantFilters.statuses.includes(v.status); let dateMatch = true; if (fromTs != null && toTs != null && !Number.isNaN(fromTs) && !Number.isNaN(toTs)) { const ts = new Date(v.created_at).getTime(); if (!Number.isNaN(ts)) dateMatch = ts >= fromTs && ts <= toTs; } return searchMatch && statusMatch && dateMatch; }); return [...result].sort((a, b) => { if (variantSort.field === 'price') return (a.price - b.price) * dir; if (variantSort.field === 'status') return a.status.localeCompare(b.status) * dir; return a.name.localeCompare(b.name) * dir; }); }, [variants, searchQuery, activeVariantFilters, variantSort, dateFilterEnabled, dateFrom, dateTo, repairServices, models, serviceTypes]); const modelsPager = useClientPagination(filteredModels); const servicesPager = useClientPagination(filteredServices); const variantsPager = useClientPagination(filteredVariants); const tabCount = activeTab === 'models' ? models.length : activeTab === 'services' ? repairServices.length : variants.length; const dateRangeLabel = `${format(new Date(dayStartMs(dateFrom)), 'd MMM yy')} - ${format(new Date(dayStartMs(dateTo)), 'd MMM yy')}`; const visibleColumnCount = Object.values(visibleColumns).filter(Boolean).length; const filtersActive = activeTab === 'models' ? modelFiltersActive : activeTab === 'services' ? serviceFiltersActive : variantFiltersActive; const closeOverlays = () => { setShowFilterPanel(false); setShowColumnsPanel(false); setShowSortPanel(false); setShowDatePanel(false); }; const switchTab = (tab: ActiveTab) => { setActiveTab(tab); setSearchQuery(''); setOpenActionId(null); closeOverlays(); if (tab === 'models') setVisibleColumns(MODEL_COLUMNS); }; const handleViewModeChange = (mode: 'table' | 'grid') => { setViewMode(mode); setOpenActionId(null); closeOverlays(); }; const renderSortLabel = (label: string, active = false) => ( {label} ); const renderStatusBadge = (active: boolean) => ( {active ? 'Active' : 'Inactive'} ); 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 handleOpenCreateModel = () => { setEditingModel(null); setModelName(''); setModelSeriesId(''); setModelBrandId(''); setModelReleaseYear(''); setModelImg(''); setShowModelModal(true); }; const handleOpenEditModel = (model: DeviceModelResponse) => { setEditingModel(model); setModelName(model.name); setModelSeriesId(model.series_id); setModelBrandId(model.brand_id); setModelReleaseYear(model.release_year?.toString() || ''); setModelImg(model.image_url || ''); setShowModelModal(true); }; const openDeleteModal = (id: string, name: string) => { setOpenActionId(null); setDeleteTarget({ id, name }); }; const handleConfirmDelete = async () => { if (!deleteTarget) return; setIsDeleting(true); try { setModels((prev) => prev.filter((m) => m.model_id !== deleteTarget.id)); await catalogService.deleteDeviceModel(deleteTarget.id); toast.success(`Model "${deleteTarget.name}" deleted successfully`); setDeleteTarget(null); fetchData(); } catch (err: any) { toast.error(err?.message || 'Failed to delete model'); fetchData(); } finally { setIsDeleting(false); } }; const handleAddSeries = async (e: React.FormEvent) => { e.preventDefault(); if (!seriesName.trim() || !seriesBrandId) { toast.error('Name and Brand are required'); return; } setIsSubmittingSeries(true); try { const created = await catalogService.createDeviceSeries({ brand_id: seriesBrandId, name: seriesName.trim() }); setSeries([...series, created]); toast.success(`Series "${created.name}" created successfully`); setSeriesName(''); setSeriesBrandId(''); setShowSeriesModal(false); } catch (err: any) { toast.error(err?.message || 'Failed to create series'); } finally { setIsSubmittingSeries(false); } }; const handleAddModel = async (e: React.FormEvent) => { e.preventDefault(); if (!modelName.trim() || !modelSeriesId || !modelBrandId) { toast.error('Name, Brand, and Series are required'); return; } setIsSubmittingModel(true); try { const payload = { series_id: modelSeriesId, brand_id: modelBrandId, name: modelName.trim(), release_year: modelReleaseYear ? parseInt(modelReleaseYear) : undefined, image_url: modelImg.trim() || undefined, }; if (editingModel) { const updated = await catalogService.updateDeviceModel(editingModel.model_id, payload); 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(payload); setModels([...models, created]); toast.success(`Model "${created.name}" created successfully`); } setModelName(''); setModelSeriesId(''); setModelBrandId(''); setModelReleaseYear(''); setModelImg(''); setEditingModel(null); setShowModelModal(false); } catch (err: any) { toast.error(err?.message || `Failed to ${editingModel ? 'update' : 'create'} model`); } finally { setIsSubmittingModel(false); } }; const handleAddServiceType = async (e: React.FormEvent) => { e.preventDefault(); if (!typeName.trim()) { toast.error('Service type name is required'); return; } try { const created = await catalogService.createServiceType({ name: typeName.trim(), icon_url: typeIcon.trim() || undefined, description: typeDesc.trim() || undefined, }); setServiceTypes([...serviceTypes, created]); toast.success(`Service Type "${created.name}" created successfully`); setTypeName(''); setTypeIcon(''); setTypeDesc(''); setShowTypeModal(false); } catch (err: any) { toast.error(err?.message || 'Failed to create service type'); } }; const handleAddRepairService = async (e: React.FormEvent) => { e.preventDefault(); if (!serviceModelId || !serviceTypeId) { toast.error('Device Model and Service Type are required'); return; } try { const created = await catalogService.createRepairService({ model_id: serviceModelId, service_type_id: serviceTypeId, description: serviceDesc.trim() || undefined, }); setRepairServices([...repairServices, created]); toast.success(`Repair service mapped to the selected model`); setServiceModelId(''); setServiceTypeId(''); setServiceDesc(''); setShowServiceModal(false); } catch (err: any) { toast.error(err?.message || 'Failed to map repair service'); } }; const handleAddVariant = async (e: React.FormEvent) => { e.preventDefault(); if (!variantServiceId || !variantName.trim() || !variantPrice || !variantCost) { toast.error('Mapping Service, Name, Price, and Cost are required'); return; } try { const created = await catalogService.createRepairVariant({ repair_service_id: variantServiceId, name: variantName.trim(), price: parseFloat(variantPrice), cost: parseFloat(variantCost), duration_minutes: variantDuration ? parseInt(variantDuration) : undefined, warranty_days: variantWarranty ? parseInt(variantWarranty) : undefined, parts: bomParts.filter((p) => p.part_id !== ''), }); setVariants([...variants, created]); toast.success(`Repair variant "${created.name}" created successfully`); setVariantServiceId(''); setVariantName(''); setVariantPrice(''); setVariantCost(''); setVariantDuration('45'); setVariantWarranty('90'); setBomParts([]); setShowVariantModal(false); } catch (err: any) { toast.error(err?.message || 'Failed to create repair variant'); } }; const addBomPartRow = () => { setBomParts([...bomParts, { part_id: '', quantity: 1 }]); }; const updateBomPartRow = (index: number, field: 'part_id' | 'quantity', value: string) => { const updated = [...bomParts]; if (field === 'part_id') { updated[index].part_id = value; } else { updated[index].quantity = parseInt(value) || 1; } setBomParts(updated); }; const removeBomPartRow = (index: number) => { setBomParts(bomParts.filter((_, i) => i !== index)); }; const handleExportExcel = () => { const rows = activeTab === 'models' ? filteredModels.map((m, i) => [i + 1, m.name, getBrandName(m.brand_id), getSeriesName(m.series_id), m.full_path, m.release_year || '', coerceFlag(m.is_active) ? 'Active' : 'Inactive']) : activeTab === 'services' ? filteredServices.map((rs, i) => [i + 1, getModelName(rs.model_id), getTypeName(rs.service_type_id), rs.full_path, rs.description || '']) : filteredVariants.map((v, i) => { const svc = repairServices.find((s) => s.repair_service_id === v.repair_service_id); return [i + 1, v.name, svc ? getModelName(svc.model_id) : '', svc ? getTypeName(svc.service_type_id) : '', v.price, v.cost, v.status]; }); if (rows.length === 0) { toast.warning( activeTab === 'models' ? 'No device models to export' : activeTab === 'services' ? 'No repair services to export' : 'No repair variants to export' ); return; } const headers = activeTab === 'models' ? ['S.No', 'Model Name', 'Brand', 'Series', 'Full Path', 'Release Year', 'Status'] : activeTab === 'services' ? ['S.No', 'Device Model', 'Service Type', 'Route Path', 'Description'] : ['S.No', 'Variant Name', 'Device Model', 'Service Type', 'Price', 'Cost', 'Status']; const csv = [headers.join(','), ...rows.map((r) => r.map((v) => `"${String(v).replace(/"/g, '""')}"`).join(','))].join('\n'); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `ifixkart_devices_${activeTab}_${format(new Date(), 'yyyyMMdd')}.csv`; link.click(); setShowExportMenu(false); toast.success( activeTab === 'models' ? `Downloaded ${rows.length} ${rows.length === 1 ? 'device model' : 'device models'} as CSV` : activeTab === 'services' ? `Downloaded ${rows.length} ${rows.length === 1 ? 'repair service' : 'repair services'} as CSV` : `Downloaded ${rows.length} ${rows.length === 1 ? 'repair variant' : 'repair variants'} as CSV` ); }; 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 viewToggle = ; const tabActions = ( <> {activeTab === 'models' && ( <> )} {activeTab === 'services' && ( <> )} {activeTab === 'variants' && ( )} ); const emptyMessage = searchQuery || filtersActive || (activeTab === 'variants' && dateFilterEnabled) ? 'No records match your search.' : 'No records found.'; return (

Devices

{tabCount}
{showExportMenu && (
)}
{(['models', 'services', 'variants'] as ActiveTab[]).map((tab) => ( ))}
{viewMode === 'grid' && activeTab !== 'services' ? (
{searchField}
{viewToggle}{tabActions}
) : ( <>
{searchField} {tabActions}
{showSortPanel && (
{(activeTab === 'models' ? MODEL_SORT_OPTIONS : activeTab === 'services' ? [{ field: 'model', dir: 'asc', label: 'Model A-Z' }, { field: 'type', dir: 'asc', label: 'Service Type A-Z' }] : [{ field: 'name', dir: 'asc', label: 'Name A-Z' }, { field: 'price', dir: 'asc', label: 'Price Low-High' }]).map((opt: any) => ( ))}
)}
{activeTab === 'variants' && (
{showDatePanel && (
setDraftDateFrom(e.target.value)} className="w-full h-8 px-2.5 crm-radius-control border border-border text-[13px]" /> setDraftDateTo(e.target.value)} className="w-full h-8 px-2.5 crm-radius-control border border-border text-[13px]" />
)}
)}
{activeTab === 'models' && (
{showColumnsPanel && (
{MODEL_COLUMN_OPTIONS.map((col) => (
{col.label}
))}
)}
)} {viewToggle}
)}
{loading ? (
Loading catalog assets...
) : activeTab === 'models' ? ( viewMode === 'grid' ? ( filteredModels.length === 0 ? (
{emptyMessage}
) : (
{modelsPager.items.map((m) => (
setOpenActionId(open ? m.model_id : null)} onEdit={() => handleOpenEditModel(m)} onDelete={() => openDeleteModal(m.model_id, m.name)} />
{renderModelAvatar(m)}

{m.name}

{getBrandName(m.brand_id)} · {getSeriesName(m.series_id)}

{m.full_path}

{renderStatusBadge(coerceFlag(m.is_active))}
))}
) ) : (
{visibleColumns.modelName && } {visibleColumns.brand && } {visibleColumns.series && } {visibleColumns.fullPath && } {visibleColumns.releaseYear && } {visibleColumns.status && } {visibleColumns.actions && } {filteredModels.length === 0 ? ( ) : modelsPager.items.map((m) => ( {visibleColumns.modelName && ( )} {visibleColumns.brand && } {visibleColumns.series && } {visibleColumns.fullPath && ( )} {visibleColumns.releaseYear && } {visibleColumns.status && } {visibleColumns.actions && ( )} ))}
{renderSortLabel('Model Name', modelSort.field === 'name')}{renderSortLabel('Brand', modelSort.field === 'brand')}{renderSortLabel('Series', modelSort.field === 'series')}{renderSortLabel('Full Path')}{renderSortLabel('Release Year', modelSort.field === 'releaseYear')}{renderSortLabel('Status', modelSort.field === 'status')}{renderSortLabel('Action')}
{emptyMessage}
{renderModelAvatar(m, 'sm')}

{m.name}

{m.slug}

{getBrandName(m.brand_id)}{getSeriesName(m.series_id)} {m.full_path} {m.release_year || '—'}{renderStatusBadge(coerceFlag(m.is_active))}
setOpenActionId(open ? m.model_id : null)} onEdit={() => handleOpenEditModel(m)} onDelete={() => openDeleteModal(m.model_id, m.name)} />
) ) : activeTab === 'services' ? (
{filteredServices.length === 0 ? ( ) : servicesPager.items.map((rs) => ( ))}
{renderSortLabel('Device Model', serviceSort.field === 'model')} {renderSortLabel('Service Type', serviceSort.field === 'type')} {renderSortLabel('Route Path', serviceSort.field === 'path')}
{emptyMessage}
{getModelName(rs.model_id)} {getTypeName(rs.service_type_id)} {rs.full_path}
) : viewMode === 'grid' ? ( filteredVariants.length === 0 ? (
{emptyMessage}
) : (
{variantsPager.items.map((v) => { const svc = repairServices.find((s) => s.repair_service_id === v.repair_service_id); return (

{svc ? `${getModelName(svc.model_id)} · ${getTypeName(svc.service_type_id)}` : ''}

{v.name}

₹{v.price}

Cost: ₹{v.cost}

{v.duration_minutes} mins{v.warranty_days} days warranty
{v.status}
); })}
) ) : (
{filteredVariants.length === 0 ? ( ) : variantsPager.items.map((v) => { const svc = repairServices.find((s) => s.repair_service_id === v.repair_service_id); return ( ); })}
{renderSortLabel('Variant Name', variantSort.field === 'name')} {renderSortLabel('Device Model')} {renderSortLabel('Service Type')} {renderSortLabel('Price', variantSort.field === 'price')} {renderSortLabel('Cost')} {renderSortLabel('Duration')} {renderSortLabel('Status', variantSort.field === 'status')}
{emptyMessage}
{v.name} {svc ? getModelName(svc.model_id) : '—'} {svc ? getTypeName(svc.service_type_id) : '—'} ₹{v.price} ₹{v.cost} {v.duration_minutes} mins {v.status}
)} {activeTab === 'models' && } {activeTab === 'services' && } {activeTab === 'variants' && }
{deleteTarget && (
!isDeleting && setDeleteTarget(null)}>
e.stopPropagation()}>

Delete Model

Delete "{deleteTarget.name}" and related data?

)} !isSubmittingSeries && setShowSeriesModal(false)} title="Create Device Series" icon={}>
({ value: b.brand_id, label: b.name })), ]} />
setSeriesName(e.target.value)} className="w-full h-9 px-3 crm-radius-control border border-border bg-card text-[13px]" />
!isSubmittingModel && setShowModelModal(false)} title={editingModel ? 'Edit Device Model' : 'Create Device Model'} icon={}>
({ value: b.brand_id, label: b.name })), ]} />
s.brand_id === modelBrandId).map((s) => ({ value: s.series_id, label: s.name })), ]} />
setModelName(e.target.value)} className="w-full h-9 px-3 crm-radius-control border border-border bg-card text-[13px]" />
setModelReleaseYear(e.target.value)} className="w-full h-9 px-3 crm-radius-control border border-border bg-card text-[13px]" />
setModelImg(e.target.value)} className="w-full h-9 px-3 crm-radius-control border border-border bg-card text-[13px]" />
setShowTypeModal(false)} title="Create Service Type" icon={}>
setTypeName(e.target.value)} className="w-full h-9 px-3 crm-radius-control border border-border bg-card text-[13px]" />