'use client'; import { useEffect, useState, useCallback } from 'react'; import { toast } from 'sonner'; import { PageHeader } from '@/components/navigation/page-header'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; import { Skeleton } from '@/components/ui/skeleton'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Search, RefreshCw, Building2, EyeOff } from 'lucide-react'; interface Company { id: string; companyName: string; companyType: number | null; companyTypeLabel: string | null; inScope: boolean; } type ScopeFilter = 'all' | 'in' | 'out'; export default function ClientScopePage() { const [companies, setCompanies] = useState([]); const [total, setTotal] = useState(0); const [excluded, setExcluded] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [search, setSearch] = useState(''); const [typeFilter, setTypeFilter] = useState('all'); const [scopeFilter, setScopeFilter] = useState('all'); const [toggling, setToggling] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); try { const params = new URLSearchParams(); if (search) params.set('search', search); if (typeFilter !== 'all') params.set('type', typeFilter); const res = await fetch(`/api/admin/company-scope?${params}`); if (!res.ok) { const d = await res.json().catch(() => ({})) as { error?: string }; throw new Error(d.error ?? `Request failed: ${res.status}`); } const data = await res.json() as { companies: Company[]; total: number; excluded: number }; setCompanies(data.companies); setTotal(data.total); setExcluded(data.excluded); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load'); } finally { setLoading(false); } }, [search, typeFilter]); useEffect(() => { void load(); }, [load]); async function toggle(company: Company, next: boolean) { setToggling(company.id); try { const res = await fetch(`/api/admin/company-scope/${company.id}`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ inScope: next }), }); if (!res.ok) { const d = await res.json().catch(() => ({})) as { error?: string }; throw new Error(d.error ?? `Failed: ${res.status}`); } setCompanies((prev) => prev.map((c) => (c.id === company.id ? { ...c, inScope: next } : c)) ); setExcluded((n) => (next ? Math.max(0, n - 1) : n + 1)); toast.success(`${company.companyName} ${next ? 'included in' : 'excluded from'} scope`); } catch (err) { toast.error(err instanceof Error ? err.message : 'Toggle failed'); } finally { setToggling(null); } } const visible = companies.filter((c) => { if (scopeFilter === 'in') return c.inScope; if (scopeFilter === 'out') return !c.inScope; return true; }); const uniqueTypes = Array.from(new Set(companies.map((c) => c.companyType).filter(Boolean))).sort() as number[]; return ( <> void load()} disabled={loading}> Refresh } />
{/* Summary */}
Total companies: {total}
Excluded from analytics: {excluded}
{/* Filters */}
setSearch(e.target.value)} className="pl-8" />
{visible.length} shown
{error && (
Failed to load {error}
)} {loading ? (
{[1, 2, 3, 4, 5].map((i) => ( ))}
) : visible.length === 0 ? (
No companies match the current filters.
) : ( Company Type Include in analytics {visible.map((company) => ( {company.companyName} {company.companyTypeLabel ? ( {company.companyTypeLabel} ) : ( — )} void toggle(company, next)} aria-label={`Include ${company.companyName} in analytics`} /> ))}
)}
); }