'use client'; import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { Checkbox } from '@/components/ui/checkbox'; import { Server, Building2, CheckCircle, XCircle, AlertCircle, Save, Trash2, Search, RefreshCw, MapPin, Globe } from 'lucide-react'; import { Company } from '@/lib/types/autotask'; // Simple toast implementation const useToast = () => { return { toast: ({ title, description, variant }: { title: string; description: string; variant?: string }) => { if (variant === 'destructive') { console.error(`${title}: ${description}`); alert(`Error: ${description}`); } else { console.log(`${title}: ${description}`); } } }; }; interface SiteRow { id?: number | null; company_id?: number | null; company_name?: string | null; rmm_site_uid: string; rmm_site_name: string; is_primary: boolean; device_count: number; notes?: string | null; last_sync_at?: string | null; created_at?: string | null; updated_at?: string | null; created_by?: string | null; isMapped: boolean; } export default function RMMSiteMappingsPage() { const [sites, setSites] = useState([]); const [companies, setCompanies] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(null); const [searchTerm, setSearchTerm] = useState(''); const [filterStatus, setFilterStatus] = useState<'all' | 'mapped' | 'unmapped'>('all'); const [filterCompany, setFilterCompany] = useState('all'); const { toast } = useToast(); useEffect(() => { fetchData(); }, []); const fetchData = async () => { setLoading(true); try { // Fetch site mappings (including unmapped) const mappingsRes = await fetch('/api/rmm/site-mappings?includeUnmapped=true'); const mappingsData = await mappingsRes.json(); // Fetch all companies const companiesRes = await fetch('/api/companies'); const companiesData = await companiesRes.json(); const siteRows: SiteRow[] = mappingsData.mappings.map((m: any) => ({ ...m, isMapped: m.company_id !== null && m.company_id > 0, })); setSites(siteRows); setCompanies(companiesData.companies || []); } catch (error) { console.error('Error fetching data:', error); toast({ title: 'Error', description: 'Failed to load RMM site mappings', variant: 'destructive', }); } finally { setLoading(false); } }; const handleSaveMapping = async ( siteUid: string, siteName: string, companyId: number, isPrimary: boolean = false ) => { setSaving(siteUid); try { const company = companies.find((c) => c.id === companyId); if (!company) { throw new Error('Company not found'); } const response = await fetch('/api/rmm/site-mappings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rmmSiteUid: siteUid, rmmSiteName: siteName, companyId: companyId, companyName: company.companyName, isPrimary: isPrimary, }), }); if (!response.ok) { throw new Error('Failed to save mapping'); } toast({ title: 'Success', description: `Mapped ${siteName} to ${company.companyName}`, }); await fetchData(); } catch (error) { console.error('Error saving mapping:', error); toast({ title: 'Error', description: 'Failed to save mapping', variant: 'destructive', }); } finally { setSaving(null); } }; const handleDeleteMapping = async (mappingId: number) => { try { const response = await fetch(`/api/rmm/site-mappings?id=${mappingId}`, { method: 'DELETE', }); if (!response.ok) { throw new Error('Failed to delete mapping'); } toast({ title: 'Success', description: 'Mapping deleted successfully', }); await fetchData(); } catch (error) { console.error('Error deleting mapping:', error); toast({ title: 'Error', description: 'Failed to delete mapping', variant: 'destructive', }); } }; const filteredSites = sites.filter((site) => { const matchesSearch = site.rmm_site_name.toLowerCase().includes(searchTerm.toLowerCase()) || site.company_name?.toLowerCase().includes(searchTerm.toLowerCase()); const matchesFilter = filterStatus === 'all' || (filterStatus === 'mapped' && site.isMapped) || (filterStatus === 'unmapped' && !site.isMapped); const matchesCompany = filterCompany === 'all' || (filterCompany === 'unmapped' && !site.isMapped) || site.company_id?.toString() === filterCompany; return matchesSearch && matchesFilter && matchesCompany; }); const stats = { total: sites.length, mapped: sites.filter((s) => s.isMapped).length, unmapped: sites.filter((s) => !s.isMapped).length, companies: new Set(sites.filter(s => s.company_id).map(s => s.company_id)).size, }; // Get unique companies with mappings for filter dropdown const mappedCompanies = companies.filter(c => sites.some(s => s.company_id === c.id) ); return (
{/* Header */}

RMM Site Mappings

Map RMM (Datto) sites to Autotask companies for complete device coverage

{/* Stats Cards */}
Total Sites
{stats.total}
Mapped
{stats.mapped}
Unmapped
{stats.unmapped}
Companies
{stats.companies}
{/* Filters */} Site Mappings Map RMM sites to Autotask companies. Companies can have multiple sites for different locations.
setSearchTerm(e.target.value)} className="pl-10" />
{/* Table */} {loading ? (
) : (
RMM Site
Autotask Company
Primary Status Actions
{filteredSites.length === 0 ? ( No sites found ) : ( filteredSites.map((site) => ( )) )}
)}
); } interface SiteMappingRowProps { site: SiteRow; companies: Company[]; saving: boolean; onSave: (siteUid: string, siteName: string, companyId: number, isPrimary: boolean) => void; onDelete: (mappingId: number) => void; } function SiteMappingRow({ site, companies, saving, onSave, onDelete, }: SiteMappingRowProps) { const [selectedCompanyId, setSelectedCompanyId] = useState( site.company_id || 0 ); const [isPrimary, setIsPrimary] = useState(site.is_primary); const [hasChanges, setHasChanges] = useState(false); const handleCompanyChange = (value: string) => { const companyId = parseInt(value); setSelectedCompanyId(companyId); setHasChanges(companyId !== site.company_id || isPrimary !== site.is_primary); }; const handlePrimaryChange = (checked: boolean) => { setIsPrimary(checked); setHasChanges(selectedCompanyId !== site.company_id || checked !== site.is_primary); }; const handleSave = () => { if (selectedCompanyId > 0) { onSave(site.rmm_site_uid, site.rmm_site_name, selectedCompanyId, isPrimary); setHasChanges(false); } }; return (
{site.rmm_site_name}
{site.rmm_site_uid}
{site.device_count !== undefined && site.device_count > 0 && ( {site.device_count} {site.device_count === 1 ? 'device' : 'devices'} )}
{site.isMapped ? ( Mapped ) : ( Unmapped )}
{hasChanges && ( )} {site.isMapped && site.id && ( )}
); }