'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 { Smartphone, Building2, CheckCircle, XCircle, AlertCircle, Save, Trash2, Search, RefreshCw } from 'lucide-react'; import { AddigyOrgMapping } from '@/lib/types/addigy'; import { Company } from '@/lib/types/autotask'; // Simple toast implementation const useToast = () => { return { toast: ({ title, description, variant }: { title: string; description: string; variant?: string }) => { // For now, use console and alert - can be enhanced with a proper toast library later if (variant === 'destructive') { console.error(`${title}: ${description}`); alert(`Error: ${description}`); } else { console.log(`${title}: ${description}`); } } }; }; interface OrgRow extends Partial { addigyOrgId: string; addigyOrgName: string; isMapped: boolean; deviceCount?: number; } export default function AddigyMappingsPage() { const [orgs, setOrgs] = 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 [selectedPolicies, setSelectedPolicies] = useState>(new Set()); const [bulkCompanyId, setBulkCompanyId] = useState(0); const [bulkSaving, setBulkSaving] = useState(false); const { toast } = useToast(); useEffect(() => { fetchData(); }, []); const fetchData = async () => { setLoading(true); try { // Fetch org mappings (including unmapped) const mappingsRes = await fetch('/api/addigy/org-mappings?includeUnmapped=true'); const mappingsData = await mappingsRes.json(); // Fetch all companies const companiesRes = await fetch('/api/companies'); const companiesData = await companiesRes.json(); const orgRows: OrgRow[] = (mappingsData.mappings || []).map((m: any) => ({ ...m, isMapped: m.autotaskCompanyId > 0, })); setOrgs(orgRows); setCompanies(companiesData.companies || []); // Show warning if Addigy API is not configured if (mappingsData.warning) { console.warn(mappingsData.warning); toast({ title: 'Warning', description: mappingsData.warning, }); } } catch (error) { console.error('Error fetching data:', error); toast({ title: 'Error', description: 'Failed to load Addigy org mappings', variant: 'destructive', }); } finally { setLoading(false); } }; const handleSaveMapping = async (orgId: string, orgName: string, companyId: number) => { setSaving(orgId); try { const company = companies.find((c) => c.id === companyId); if (!company) { throw new Error('Company not found'); } const response = await fetch('/api/addigy/org-mappings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ addigyOrgId: orgId, addigyOrgName: orgName, autotaskCompanyId: companyId, autotaskCompanyName: company.companyName, }), }); if (!response.ok) { throw new Error('Failed to save mapping'); } toast({ title: 'Success', description: `Mapped ${orgName} 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/addigy/org-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 handleBulkSave = async () => { if (selectedPolicies.size === 0 || bulkCompanyId === 0) { toast({ title: 'Error', description: 'Please select policies and a company', variant: 'destructive', }); return; } setBulkSaving(true); const company = companies.find((c) => c.id === bulkCompanyId); if (!company) { toast({ title: 'Error', description: 'Company not found', variant: 'destructive', }); setBulkSaving(false); return; } let successCount = 0; let errorCount = 0; for (const policyId of selectedPolicies) { const policy = orgs.find((o) => o.addigyOrgId === policyId); if (!policy) continue; try { const response = await fetch('/api/addigy/org-mappings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ addigyOrgId: policy.addigyOrgId, addigyOrgName: policy.addigyOrgName, autotaskCompanyId: bulkCompanyId, autotaskCompanyName: company.companyName, }), }); if (response.ok) { successCount++; } else { errorCount++; } } catch (error) { console.error('Error saving mapping:', error); errorCount++; } } setBulkSaving(false); setSelectedPolicies(new Set()); setBulkCompanyId(0); if (errorCount === 0) { toast({ title: 'Success', description: `Mapped ${successCount} ${successCount === 1 ? 'policy' : 'policies'} to ${company.companyName}`, }); } else { toast({ title: 'Partial Success', description: `Mapped ${successCount} policies, ${errorCount} failed`, variant: 'destructive', }); } await fetchData(); }; const togglePolicySelection = (policyId: string) => { const newSelection = new Set(selectedPolicies); if (newSelection.has(policyId)) { newSelection.delete(policyId); } else { newSelection.add(policyId); } setSelectedPolicies(newSelection); }; const toggleSelectAll = () => { if (selectedPolicies.size === filteredOrgs.length) { setSelectedPolicies(new Set()); } else { setSelectedPolicies(new Set(filteredOrgs.map((o) => o.addigyOrgId))); } }; const filteredOrgs = orgs.filter((org) => { const matchesSearch = org.addigyOrgName.toLowerCase().includes(searchTerm.toLowerCase()) || org.autotaskCompanyName?.toLowerCase().includes(searchTerm.toLowerCase()); const matchesFilter = filterStatus === 'all' || (filterStatus === 'mapped' && org.isMapped) || (filterStatus === 'unmapped' && !org.isMapped); return matchesSearch && matchesFilter; }); const stats = { total: orgs.length, mapped: orgs.filter((o) => o.isMapped).length, unmapped: orgs.filter((o) => !o.isMapped).length, }; return (
{/* Header */}

Apple RMM Policy Mappings

Map Addigy policies to Autotask companies for Apple device synchronization

{/* Stats Cards */}
Total Policies
{stats.total}
Mapped
{stats.mapped}
Unmapped
{stats.unmapped}
{/* Filters */} Policy Mappings Select an Autotask company for each Addigy policy to enable device matching
setSearchTerm(e.target.value)} className="pl-10" />
{/* Bulk Actions */} {selectedPolicies.size > 0 && (
{selectedPolicies.size} {selectedPolicies.size === 1 ? 'policy' : 'policies'} selected
)} {/* Table */} {loading ? (
) : (
0} onCheckedChange={toggleSelectAll} />
Addigy Policy
Autotask Company
Status Actions
{filteredOrgs.length === 0 ? ( No policies found ) : ( filteredOrgs.map((org) => ( togglePolicySelection(org.addigyOrgId)} /> )) )}
)}
); } interface OrgMappingRowProps { org: OrgRow; companies: Company[]; saving: boolean; onSave: (orgId: string, orgName: string, companyId: number) => void; onDelete: (mappingId: number) => void; isSelected: boolean; onToggleSelect: () => void; } function OrgMappingRow({ org, companies, saving, onSave, onDelete, isSelected, onToggleSelect, }: OrgMappingRowProps) { const [selectedCompanyId, setSelectedCompanyId] = useState( org.autotaskCompanyId || 0 ); const [hasChanges, setHasChanges] = useState(false); const handleCompanyChange = (value: string) => { const companyId = parseInt(value); setSelectedCompanyId(companyId); setHasChanges(companyId !== org.autotaskCompanyId); }; const handleSave = () => { if (selectedCompanyId > 0) { onSave(org.addigyOrgId, org.addigyOrgName, selectedCompanyId); setHasChanges(false); } }; return (
{org.addigyOrgName}
{org.addigyOrgId}
{org.isMapped ? ( Mapped ) : ( Unmapped )}
{hasChanges && ( )} {org.isMapped && org.id && ( )}
); }