diff --git a/src/app/dashboard/data/enrichment/page.tsx b/src/app/dashboard/data/enrichment/page.tsx new file mode 100644 index 0000000..76c0bab --- /dev/null +++ b/src/app/dashboard/data/enrichment/page.tsx @@ -0,0 +1,166 @@ +'use client'; + +import { useMemo } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import Link from 'next/link'; +import { apiFetch } from '../../../../lib/api'; +import { useSession } from '../../../../components/session-provider'; + +interface Contact { id: string; fullName: string; email: string | null; phone: string | null; role: string | null; linkedinUrl: string | null; tags: string[]; } +interface Organization { id: string; name: string; industry: string | null; country: string | null; website: string | null; employeeCount: number | null; } + +interface EnrichmentItem { id: string; name: string; type: 'contact' | 'organization'; missing: string[]; completeness: number; } + +export default function EnrichmentPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + + const { data: contacts = [], isLoading: cL } = useQuery({ + queryKey: ['enr-contacts', tenantId], + queryFn: () => apiFetch('/v1/contacts?limit=500', { tenantId }), + enabled: Boolean(tenantId), staleTime: 120_000, + }); + const { data: orgs = [], isLoading: oL } = useQuery({ + queryKey: ['enr-orgs', tenantId], + queryFn: () => apiFetch('/v1/organizations?limit=500', { tenantId }), + enabled: Boolean(tenantId), staleTime: 120_000, + }); + + const items = useMemo(() => { + const contactItems: EnrichmentItem[] = contacts.map((c) => { + const missing: string[] = []; + if (!c.email) missing.push('email'); + if (!c.phone) missing.push('telefon'); + if (!c.role) missing.push('funcție'); + if (!c.linkedinUrl) missing.push('LinkedIn'); + const completeness = Math.round(((4 - missing.length) / 4) * 100); + return { id: c.id, name: c.fullName, type: 'contact', missing, completeness }; + }).filter((i) => i.completeness < 100); + + const orgItems: EnrichmentItem[] = orgs.map((o) => { + const missing: string[] = []; + if (!o.industry) missing.push('industrie'); + if (!o.country) missing.push('țară'); + if (!o.website) missing.push('website'); + if (!o.employeeCount) missing.push('angajați'); + const completeness = Math.round(((4 - missing.length) / 4) * 100); + return { id: o.id, name: o.name, type: 'organization', missing, completeness }; + }).filter((i) => i.completeness < 100); + + return [...contactItems, ...orgItems].sort((a, b) => a.completeness - b.completeness); + }, [contacts, orgs]); + + const avgContact = useMemo(() => { + if (!contacts.length) return 0; + const scores = contacts.map((c) => { + let filled = 0; + if (c.email) filled++; + if (c.phone) filled++; + if (c.role) filled++; + if (c.linkedinUrl) filled++; + return (filled / 4) * 100; + }); + return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length); + }, [contacts]); + + const avgOrg = useMemo(() => { + if (!orgs.length) return 0; + const scores = orgs.map((o) => { + let filled = 0; + if (o.industry) filled++; + if (o.country) filled++; + if (o.website) filled++; + if (o.employeeCount) filled++; + return (filled / 4) * 100; + }); + return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length); + }, [orgs]); + + function clsCls(pct: number) { + if (pct >= 75) return 'text-signal-ok'; + if (pct >= 50) return 'text-warn'; + return 'text-signal-danger'; + } + + return ( +
+
+

Enrichment Date

+

+ {cL || oL ? 'Se calculează…' : `${items.length} entități cu date incomplete`} +

+
+ +
+
+

{avgContact}%

+

Completitudine medie contacte

+
+
= 75 ? 'bg-signal-ok' : avgContact >= 50 ? 'bg-warn' : 'bg-signal-danger'}`} + style={{ width: `${avgContact}%` }} /> +
+
+
+

{avgOrg}%

+

Completitudine medie organizații

+
+
= 75 ? 'bg-signal-ok' : avgOrg >= 50 ? 'bg-warn' : 'bg-signal-danger'}`} + style={{ width: `${avgOrg}%` }} /> +
+
+
+ +
+

Surse de enrichment disponibile

+
+ 📊 ClickHouse (147M+ rânduri date publice) + 🔍 AnythingLLM (RAG pe documente interne) + 🤖 Hermes (enrichment automat prin AI) +
+

+ Enrichment-ul automat rulează periodic pe server. Această pagină afișează starea curentă. +

+
+ + {cL || oL ? ( +
Se calculează…
+ ) : items.length === 0 ? ( +
+

+

Toate entitățile au date complete.

+
+ ) : ( +
+

+ Entități de îmbogățit (sortat după completitudine crescătoare) +

+
+ {items.slice(0, 50).map((item) => ( +
+ + {item.completeness}% + +
+

{item.name}

+
+ {item.missing.map((m) => ( + -{m} + ))} +
+
+ + Editează → + +
+ ))} + {items.length > 50 && ( +

și alte {items.length - 50}

+ )} +
+
+ )} +
+ ); +}