'use client'; import { useState, useEffect, Suspense } from 'react'; import { useSearchParams } from 'next/navigation'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Skeleton } from '@/components/ui/skeleton'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { CompanySelectorEnhanced } from '@/components/companies/company-selector-enhanced'; import { ThemeToggle } from '@/components/theme-toggle'; import { ConfigItemModal } from '@/components/configuration-items/config-item-modal'; import { ContactCell } from '@/components/configuration-items/contact-cell'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Calendar as CalendarComponent } from '@/components/ui/calendar'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Server, Monitor, HardDrive, Network, AlertCircle, CheckCircle, XCircle, RefreshCw, Search, Settings, Activity, Cpu, MemoryStick, Wifi, Shield, Calendar as CalendarIcon, Hash, Building2, ArrowLeft, Filter, ArrowUpDown, ArrowUp, ArrowDown, Users, Download, ChevronRight, Info, Power } from 'lucide-react'; import { format } from 'date-fns'; import { ConfigurationItem } from '@/lib/types/autotask'; import { DattoRMMDevice } from '@/lib/types/datto-rmm'; import { AuvikDevice } from '@/lib/types/auvik'; import { AddigyDevice } from '@/lib/types/addigy'; import { useApi } from '@/lib/hooks/use-api'; import { RmmDispatchDialog } from '@/components/rmm/rmm-dispatch-dialog'; interface DeviceComparison { autotaskDevice?: ConfigurationItem; rmmDevice?: DattoRMMDevice; auvikDevice?: AuvikDevice; addigyDevice?: AddigyDevice; status: 'matched' | 'autotask-only' | 'rmm-only'; matchedBy?: string; } function ConfigurationItemsContent() { const searchParams = useSearchParams(); const [selectedCompany, setSelectedCompany] = useState(); const [selectedCompanyName, setSelectedCompanyName] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [filterType, setFilterType] = useState('all'); const [configItems, setConfigItems] = useState([]); const [comparison, setComparison] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [viewMode, setViewMode] = useState<'autotask' | 'comparison'>('comparison'); const [selectedItemId, setSelectedItemId] = useState(null); const [selectedItem, setSelectedItem] = useState(null); const [modalOpen, setModalOpen] = useState(false); const [adminExpanded, setAdminExpanded] = useState(false); const [selectedItems, setSelectedItems] = useState>(new Set()); const [bulkProcessing, setBulkProcessing] = useState(false); const [lastSeenAfterDate, setLastSeenAfterDate] = useState(); const [activeFilter, setActiveFilter] = useState<'active' | 'inactive' | 'all'>('active'); const [displayLimit, setDisplayLimit] = useState(50); // Start with 50 items const [contacts, setContacts] = useState>({}); const [filtersExpanded, setFiltersExpanded] = useState(false); const [sortField, setSortField] = useState<'name' | 'ip' | 'contact' | null>(null); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); const [groupByContact, setGroupByContact] = useState(false); const [expandedContacts, setExpandedContacts] = useState>(new Set()); const [exportModalOpen, setExportModalOpen] = useState(false); const [selectedExportFields, setSelectedExportFields] = useState>(new Set()); const [configItemTypePicklist, setConfigItemTypePicklist] = useState>({}); const [rmmDeviceTypePicklist, setRmmDeviceTypePicklist] = useState>({}); const [forceRefresh, setForceRefresh] = useState(0); // Initialize company from URL params on mount useEffect(() => { const companyIdParam = searchParams.get('companyId'); const companyNameParam = searchParams.get('companyName'); if (companyIdParam) { setSelectedCompany(parseInt(companyIdParam)); setSelectedCompanyName(companyNameParam || ''); } }, [searchParams]); // Fetch configuration item type picklists on mount useEffect(() => { const fetchPicklists = async () => { try { // Fetch main type picklist const typeResponse = await fetch('/api/picklists?entity=ConfigurationItems&field=type'); if (typeResponse.ok) { const typeData = await typeResponse.json(); console.log('Config Item Type Picklist Response:', typeData); console.log('Config Item Type Picklist Values:', typeData.picklistValues); setConfigItemTypePicklist(typeData.picklistValues || {}); } else { console.error('Failed to fetch config item type picklist:', typeResponse.status); } // Fetch RMM device type picklist const rmmTypeResponse = await fetch('/api/picklists?entity=ConfigurationItems&field=rmmDeviceAuditDeviceTypeID'); if (rmmTypeResponse.ok) { const rmmTypeData = await rmmTypeResponse.json(); console.log('RMM Device Type Picklist Response:', rmmTypeData); console.log('Setting rmmDeviceTypePicklist to:', rmmTypeData.picklistValues); setRmmDeviceTypePicklist(rmmTypeData.picklistValues || {}); console.log('rmmDeviceTypePicklist state should now be set'); } } catch (error) { console.error('Failed to fetch picklists:', error); } }; fetchPicklists(); }, []); // Fetch configuration items when company changes useEffect(() => { if (!selectedCompany) { setConfigItems([]); return; } const fetchConfigItems = async () => { setLoading(true); setError(null); try { // Fetch comparison data (includes both Autotask and RMM) // Add skipCache parameter when forceRefresh is triggered const skipCache = forceRefresh > 0 ? '&skipCache=true' : ''; const response = await fetch( `/api/rmm-devices?companyId=${selectedCompany}&companyName=${encodeURIComponent(selectedCompanyName)}&activeFilter=${activeFilter}${skipCache}` ); if (!response.ok) { throw new Error('Failed to fetch devices'); } const data = await response.json(); console.log('Sample autotask device:', data.comparison[0]?.autotaskDevice); console.log('configurationItemType (camelCase):', data.comparison[0]?.autotaskDevice?.configurationItemType); console.log('type field:', data.comparison[0]?.autotaskDevice?.type); console.log('configuration_item_type (snake_case):', data.comparison[0]?.autotaskDevice?.configuration_item_type); console.log('rmmDeviceAuditDeviceTypeID:', data.comparison[0]?.autotaskDevice?.rmmDeviceAuditDeviceTypeID); setComparison(data.comparison || []); setStats(data.stats); setContacts(data.contacts || {}); } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred'); setComparison([]); } finally { setLoading(false); } }; fetchConfigItems(); }, [selectedCompany, selectedCompanyName, activeFilter, forceRefresh]); // Filter comparison items based on search and type const filteredComparison = comparison.filter((item: DeviceComparison) => { const deviceName = item.autotaskDevice?.referenceTitle || item.rmmDevice?.hostname || ''; const serialNumber = item.autotaskDevice?.serialNumber || item.rmmDevice?.serialNumber || ''; const searchLower = searchTerm.toLowerCase(); const matchesSearch = deviceName.toLowerCase().includes(searchLower) || serialNumber.toLowerCase().includes(searchLower); const matchesType = filterType === 'all' || (filterType === 'matched' && item.status === 'matched') || (filterType === 'autotask-only' && item.status === 'autotask-only') || (filterType === 'rmm-only' && item.status === 'rmm-only'); // Filter by last seen date in RMM (after specified date) let matchesLastSeen = true; if (lastSeenAfterDate && item.rmmDevice?.lastSeen) { const lastSeenDate = new Date(item.rmmDevice.lastSeen); matchesLastSeen = lastSeenDate >= lastSeenAfterDate; } return matchesSearch && matchesType && matchesLastSeen; }); // Sort filtered items const sortedComparison = [...filteredComparison].sort((a, b) => { if (!sortField) return 0; let aValue = ''; let bValue = ''; if (sortField === 'name') { aValue = (a.autotaskDevice?.referenceTitle || a.rmmDevice?.hostname || '').toLowerCase(); bValue = (b.autotaskDevice?.referenceTitle || b.rmmDevice?.hostname || '').toLowerCase(); } else if (sortField === 'ip') { aValue = (a.autotaskDevice?.rmmDeviceAuditIPAddress || a.rmmDevice?.intIpAddress || '').toLowerCase(); bValue = (b.autotaskDevice?.rmmDeviceAuditIPAddress || b.rmmDevice?.intIpAddress || '').toLowerCase(); } else if (sortField === 'contact') { const aContactId = a.autotaskDevice?.contactID; const bContactId = b.autotaskDevice?.contactID; const aContact = aContactId ? contacts[aContactId] : null; const bContact = bContactId ? contacts[bContactId] : null; aValue = aContact ? `${aContact.firstName || ''} ${aContact.lastName || ''}`.toLowerCase() : ''; bValue = bContact ? `${bContact.firstName || ''} ${bContact.lastName || ''}`.toLowerCase() : ''; } if (aValue < bValue) return sortDirection === 'asc' ? -1 : 1; if (aValue > bValue) return sortDirection === 'asc' ? 1 : -1; return 0; }); // Group by contact if enabled const groupedByContact = groupByContact ? sortedComparison.reduce((acc, item) => { const contactId = item.autotaskDevice?.contactID || 0; if (!acc[contactId]) { acc[contactId] = []; } acc[contactId].push(item); return acc; }, {} as Record) : null; const displayComparison = sortedComparison; const handleSort = (field: 'name' | 'ip' | 'contact') => { if (sortField === field) { setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortDirection('asc'); } }; // Handle company selection const handleCompanyChange = (companyId: number | undefined, companyName?: string) => { setSelectedCompany(companyId); setSelectedCompanyName(companyName || ''); setSelectedItems(new Set()); // Clear selections when company changes }; const handleSelectItem = (itemId: number, checked: boolean) => { const newSelected = new Set(selectedItems); if (checked) { newSelected.add(itemId); } else { newSelected.delete(itemId); } setSelectedItems(newSelected); }; const handleSelectAll = (checked: boolean) => { if (checked) { const allIds = new Set( filteredComparison .filter(item => item.autotaskDevice?.id) .map(item => item.autotaskDevice!.id) ); setSelectedItems(allIds); } else { setSelectedItems(new Set()); } }; const handleBulkMakeInactive = async () => { if (selectedItems.size === 0) return; setBulkProcessing(true); try { const promises = Array.from(selectedItems).map(itemId => fetch(`/api/configuration-items/${itemId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ isActive: false }), }) ); await Promise.all(promises); // Refresh the data if (selectedCompany) { const response = await fetch( `/api/rmm-devices?companyId=${selectedCompany}&companyName=${encodeURIComponent(selectedCompanyName)}` ); if (response.ok) { const data = await response.json(); setComparison(data.comparison || []); setStats(data.stats); } } setSelectedItems(new Set()); setAdminExpanded(false); } catch (err) { console.error('Bulk operation failed:', err); setError('Failed to make items inactive'); } finally { setBulkProcessing(false); } }; const getDeviceIcon = (item: ConfigurationItem) => { if (item.rmmDeviceAuditDeviceTypeID) { // You can map device type IDs to specific icons return ; } if (item.dattoSerialNumber) { return ; } return ; }; const getRMMStatus = (item: ConfigurationItem) => { if (item.rmmDeviceUID) { return ( RMM Connected ); } return ( No RMM ); }; const getDattoStatus = (item: ConfigurationItem) => { if (item.dattoSerialNumber) { return ( Datto Protected ); } return null; }; // Define available fields for export const exportFields = [ { key: 'status', label: 'Match Status' }, { key: 'deviceName', label: 'Device Name' }, { key: 'serialNumber', label: 'Serial Number' }, { key: 'ipAddress', label: 'IP Address' }, { key: 'contact', label: 'Contact Name' }, { key: 'configurationItemType', label: 'Configuration Item Type' }, { key: 'psaId', label: 'PSA ID' }, { key: 'rmmId', label: 'RMM ID' }, { key: 'referenceNumber', label: 'Reference Number' }, { key: 'location', label: 'Location' }, { key: 'isActive', label: 'Active Status' }, { key: 'modelNumber', label: 'Model Number' }, { key: 'macAddress', label: 'MAC Address' }, { key: 'installDate', label: 'Install Date' }, { key: 'warrantyExpiration', label: 'Warranty Expiration' }, { key: 'rmmDeviceUID', label: 'RMM Device UID' }, { key: 'lastSeen', label: 'RMM Last Seen' }, { key: 'operatingSystem', label: 'Operating System' }, { key: 'matchType', label: 'Match Type' }, ]; const toggleExportField = (fieldKey: string) => { const newSelected = new Set(selectedExportFields); if (newSelected.has(fieldKey)) { newSelected.delete(fieldKey); } else { newSelected.add(fieldKey); } setSelectedExportFields(newSelected); }; const toggleAllExportFields = () => { if (selectedExportFields.size === exportFields.length) { setSelectedExportFields(new Set()); } else { setSelectedExportFields(new Set(exportFields.map(f => f.key))); } }; const handleExport = () => { if (selectedExportFields.size === 0 || displayComparison.length === 0) return; const headers = exportFields.filter(f => selectedExportFields.has(f.key)).map(f => f.label); const rows = displayComparison.map(item => { return exportFields.filter(f => selectedExportFields.has(f.key)).map(f => { let value = ''; switch (f.key) { case 'status': value = item.status === 'matched' ? 'Matched' : item.status === 'autotask-only' ? 'Autotask Only' : 'RMM Only'; break; case 'deviceName': value = item.autotaskDevice?.referenceTitle || item.rmmDevice?.hostname || ''; break; case 'serialNumber': value = item.autotaskDevice?.serialNumber || item.rmmDevice?.serialNumber || ''; break; case 'ipAddress': value = item.autotaskDevice?.rmmDeviceAuditIPAddress || item.rmmDevice?.intIpAddress || ''; break; case 'contact': const contactId = item.autotaskDevice?.contactID; const contact = contactId ? contacts[contactId] : null; value = contact ? `${contact.firstName || ''} ${contact.lastName || ''}`.trim() : ''; break; case 'configurationItemType': // Prioritize rmmDeviceAuditDeviceTypeID since it has picklist values const rmmTypeValue = item.autotaskDevice?.rmmDeviceAuditDeviceTypeID; if (rmmTypeValue) { value = rmmDeviceTypePicklist[rmmTypeValue] || rmmTypeValue.toString(); } break; case 'psaId': value = item.autotaskDevice?.id?.toString() || ''; break; case 'rmmId': value = item.rmmDevice?.uid || ''; break; case 'referenceNumber': value = item.autotaskDevice?.referenceNumber || ''; break; case 'location': value = item.autotaskDevice?.location || ''; break; case 'isActive': value = item.autotaskDevice?.isActive ? 'Active' : 'Inactive'; break; case 'modelNumber': value = item.autotaskDevice?.modelNumber || ''; break; case 'macAddress': value = item.autotaskDevice?.macAddress || ''; break; case 'installDate': value = item.autotaskDevice?.installDate ? format(new Date(item.autotaskDevice.installDate), 'yyyy-MM-dd') : ''; break; case 'warrantyExpiration': value = item.autotaskDevice?.warrantyExpirationDate ? format(new Date(item.autotaskDevice.warrantyExpirationDate), 'yyyy-MM-dd') : ''; break; case 'rmmDeviceUID': value = item.autotaskDevice?.rmmDeviceUID || ''; break; case 'lastSeen': value = item.rmmDevice?.lastSeen ? format(new Date(item.rmmDevice.lastSeen), 'yyyy-MM-dd HH:mm:ss') : ''; break; case 'operatingSystem': value = item.rmmDevice?.operatingSystem || ''; break; case 'matchType': value = item.matchedBy || ''; break; } if (value && (value.includes(',') || value.includes('\n') || value.includes('"'))) { return `"${value.replace(/"/g, '""')}"`; } return value; }); }); const csvContent = [headers.join(','), ...rows.map(row => row.join(','))].join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); const filename = `config-items-${selectedCompanyName || selectedCompany}-${format(new Date(), 'yyyy-MM-dd')}.csv`; link.setAttribute('download', filename); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); setExportModalOpen(false); }; return (
{/* Header */}

Configuration Items

Autotask & RMM Device Management

{selectedCompany && filteredComparison.length > 0 && ( )}
{/* Main Content */}
{/* Compact Company Selector & Filters */}
{/* Company Selector Row */}
{selectedCompany && (
PSA {stats?.totalAutotask || 0} · RMM {stats?.totalRmm || 0} · NMS {stats?.totalAuvik || 0} · ARMM {stats?.totalAddigy || 0}
)}
{/* Collapsible Filters */} {selectedCompany && (
setSearchTerm(e.target.value)} className="pl-8" />
{lastSeenAfterDate && ( )}
)}
{/* Admin Section - Appears when shield icon is clicked */} {selectedCompany && filteredComparison.length > 0 && adminExpanded && (

Bulk Actions

{selectedItems.size} device{selectedItems.size !== 1 ? 's' : ''} selected

)} {/* Configuration Items Table */} {selectedCompany && (
Device Comparison {displayComparison.length} {selectedCompany && stats && (
PSA: {stats.totalAutotask || 0}
|
RMM: {stats.totalRmm || 0}
|
NMS: {stats.totalAuvik || 0}
|
ARMM: {stats.totalAddigy || 0}
|
Matched: {stats.matched || 0}
)}
{loading && (
Loading...
)}
{loading ? (
{[1, 2, 3].map(i => ( ))}
) : error ? (
Error: {error}
) : displayComparison.length === 0 ? (
{!selectedCompany ? (

Select a company to view configuration items

) : searchTerm || filterType !== 'all' ? (

No devices found matching your filters

) : (

No configuration items found for this company

)}
) : (
i.autotaskDevice).length && selectedItems.size > 0} onCheckedChange={handleSelectAll} /> Status Serial Number
Type PSA RMM NMS ARMM Actions
{groupByContact && groupedByContact ? ( Object.entries(groupedByContact).map(([contactIdStr, items]) => { const contactId = parseInt(contactIdStr); const contact = contactId ? contacts[contactId] : null; const contactName = contact ? `${contact.firstName || ''} ${contact.lastName || ''}`.trim() : 'No Contact'; const isExpanded = expandedContacts.has(contactId); return ( <> { const newExpanded = new Set(expandedContacts); if (isExpanded) { newExpanded.delete(contactId); } else { newExpanded.add(contactId); } setExpandedContacts(newExpanded); }} >
{contactName} {items.length}
{isExpanded && items.map((item: DeviceComparison, index: number) => ( { // Don't open modal if clicking checkbox or button const target = e.target as HTMLElement; if (target.closest('input[type="checkbox"]') || target.closest('button')) return; // For RMM-only devices, use a special ID since there's no Autotask record const itemId = item.autotaskDevice?.id || 'rmm-only'; setSelectedItemId(itemId); setSelectedItem(item); setModalOpen(true); }} > {item.autotaskDevice?.id && ( handleSelectItem(item.autotaskDevice!.id, checked as boolean)} /> )} {item.status === 'matched' && ( Matched )} {item.status === 'autotask-only' && ( AT Only )} {item.status === 'rmm-only' && ( {item.addigyDevice && !item.rmmDevice ? 'ARMM Only' : 'RMM Only'} )}
{item.autotaskDevice?.referenceTitle || item.rmmDevice?.hostname || item.auvikDevice?.deviceName || item.addigyDevice?.['Device Name'] || 'Unknown Device'}
{(item.autotaskDevice?.rmmDeviceAuditHostname || item.rmmDevice?.description || item.auvikDevice?.description) && (
{item.autotaskDevice?.rmmDeviceAuditHostname || item.rmmDevice?.description || item.auvikDevice?.description}
)}
{item.autotaskDevice?.serialNumber || item.rmmDevice?.serialNumber || item.auvikDevice?.serialNumber || item.addigyDevice?.['Serial Number'] || '-'} {item.autotaskDevice?.rmmDeviceAuditIPAddress || item.rmmDevice?.intIpAddress || item.auvikDevice?.ipAddresses?.[0] || item.addigyDevice?.['IP Address'] || '-'} {(() => { // Prioritize rmmDeviceAuditDeviceTypeID since it has picklist values const rmmTypeValue = item.autotaskDevice?.rmmDeviceAuditDeviceTypeID; if (rmmTypeValue) { const label = rmmDeviceTypePicklist[rmmTypeValue]; if (!label && typeof window !== 'undefined') { console.log('Missing label for rmmTypeValue:', rmmTypeValue, 'picklist:', rmmDeviceTypePicklist); } return ( {label || rmmTypeValue} ); } return -; })()} {item.autotaskDevice ? ( ) : ( )} {item.rmmDevice ? ( ) : ( )} {item.auvikDevice ? ( ) : ( )} {item.addigyDevice ? ( ) : ( )} e.stopPropagation()}> {item.rmmDevice?.uid ? ( ) : ( )}
))} ); }) ) : ( displayComparison.slice(0, displayLimit).map((item: DeviceComparison, index: number) => ( { // Don't open modal if clicking checkbox or button const target = e.target as HTMLElement; if (target.closest('input[type="checkbox"]') || target.closest('button')) return; // For RMM-only devices, use a special ID since there's no Autotask record const itemId = item.autotaskDevice?.id || 'rmm-only'; setSelectedItemId(itemId); setSelectedItem(item); setModalOpen(true); }} > {item.autotaskDevice?.id && ( handleSelectItem(item.autotaskDevice!.id, checked as boolean)} /> )} {item.status === 'matched' && ( Matched )} {item.status === 'autotask-only' && ( AT Only )} {item.status === 'rmm-only' && ( {item.addigyDevice && !item.rmmDevice ? 'ARMM Only' : 'RMM Only'} )}
{item.autotaskDevice?.referenceTitle || item.rmmDevice?.hostname || item.auvikDevice?.deviceName || item.addigyDevice?.['Device Name'] || 'Unknown Device'}
{(item.autotaskDevice?.rmmDeviceAuditHostname || item.rmmDevice?.description || item.auvikDevice?.description) && (
{item.autotaskDevice?.rmmDeviceAuditHostname || item.rmmDevice?.description || item.auvikDevice?.description}
)}
{item.autotaskDevice?.serialNumber || item.rmmDevice?.serialNumber || item.auvikDevice?.serialNumber || item.addigyDevice?.['Serial Number'] || '-'} {item.autotaskDevice?.rmmDeviceAuditIPAddress || item.rmmDevice?.intIpAddress || item.auvikDevice?.ipAddresses?.[0] || item.addigyDevice?.['IP Address'] || '-'} {(() => { const typeValue = item.autotaskDevice?.type || item.autotaskDevice?.configuration_item_type; const typeLabel = typeValue ? (configItemTypePicklist[typeValue] || typeValue) : null; return typeLabel ? ( {typeLabel} ) : ( - ); })()} {item.autotaskDevice ? ( ) : ( )} {item.rmmDevice ? ( ) : ( )} {item.auvikDevice ? ( ) : ( )} {item.addigyDevice ? ( ) : ( )} e.stopPropagation()}> {item.rmmDevice?.uid ? ( ) : ( )}
)) )}
{/* Load More Button */} {filteredComparison.length > displayLimit && (
)}
)}
)} {/* Info Card when no company selected */} {!selectedCompany && (

Get Started

Select a company from the dropdown above to view and manage their configuration items. You can compare devices between Autotask and RMM systems.

View Autotask devices
Check RMM status
Monitor Datto devices
)}
{/* Configuration Item Detail Modal */} { setModalOpen(open); // Refresh data when modal closes if (!open && selectedCompany) { fetch( `/api/rmm-devices?companyId=${selectedCompany}&companyName=${encodeURIComponent(selectedCompanyName)}&activeFilter=${activeFilter}` ) .then(res => res.json()) .then(data => { setComparison(data.comparison || []); setStats(data.stats); }) .catch(err => console.error('Failed to refresh data:', err)); } }} /> {/* Export Modal */} Export Configuration Items Select the fields you want to include in the CSV export. Exporting {displayComparison.length} device{displayComparison.length !== 1 ? 's' : ''}.
{exportFields.map((field) => (
toggleExportField(field.key)} />
))}
); } export default function ConfigurationItemsPage() { return ( Loading...}> ); }