'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 Task { id: string; title: string; status: string; dueDate: string | null; tags: string[]; } interface Contact { id: string; fullName: string; email: string | null; phone: string | null; consentStatus: string | null; } interface Document { id: string; title: string; classification: string | null; type: string | null; } interface Goal { id: string; title: string; status: string; targetDate: string | null; progress: number | null; } interface Observation { id: string; metric: string; value: string; confidence: number | null; } interface DataSource { id: string; name: string; status: string; lastSyncAt: string | null; errorCount: number | null; } function score(good: number, total: number) { return total === 0 ? 100 : Math.round((good / total) * 100); } function cls(pct: number) { if (pct >= 80) return 'text-signal-ok'; if (pct >= 60) return 'text-warn'; return 'text-signal-danger'; } export default function DataQualityPage() { const { activeTenant } = useSession(); const tenantId = activeTenant?.tenantId ?? ''; const { data: tasks = [] } = useQuery({ queryKey: ['dq-tasks', tenantId], queryFn: () => apiFetch('/v1/tasks?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 }); const { data: contacts = [] } = useQuery({ queryKey: ['dq-contacts', tenantId], queryFn: () => apiFetch('/v1/contacts?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 }); const { data: documents = [] } = useQuery({ queryKey: ['dq-docs', tenantId], queryFn: () => apiFetch('/v1/documents?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 }); const { data: goals = [] } = useQuery({ queryKey: ['dq-goals', tenantId], queryFn: () => apiFetch('/v1/goals?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 }); const { data: observations = [] } = useQuery({ queryKey: ['dq-obs', tenantId], queryFn: () => apiFetch('/v1/observations', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 }); const { data: dataSources = [] } = useQuery({ queryKey: ['dq-sources', tenantId], queryFn: () => apiFetch('/v1/data-sources', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 }); const metrics = useMemo(() => { const tasksWithDue = tasks.filter((t) => t.dueDate).length; const contactsWithEmail = contacts.filter((c) => c.email).length; const contactsWithConsent = contacts.filter((c) => c.consentStatus === 'granted' || c.consentStatus === 'implicit').length; const docsClassified = documents.filter((d) => d.classification).length; const docsTyped = documents.filter((d) => d.type).length; const goalsWithTarget = goals.filter((g) => g.targetDate).length; const obsWithConfidence = observations.filter((o) => o.confidence !== null && o.confidence >= 0.5).length; const sourcesHealthy = dataSources.filter((ds) => ds.status === 'active' && (ds.errorCount ?? 0) === 0).length; return [ { category: 'Taskuri', total: tasks.length, checks: [ { label: 'Cu termen de scadență', good: tasksWithDue, desc: 'Taskuri care au dueDate setat' }, ], }, { category: 'Contacte', total: contacts.length, checks: [ { label: 'Cu email', good: contactsWithEmail, desc: 'Contacte cu adresă de email' }, { label: 'Cu consimțământ', good: contactsWithConsent, desc: 'Contacte cu GDPR consent granted/implicit' }, ], }, { category: 'Documente', total: documents.length, checks: [ { label: 'Clasificate (C0-C4)', good: docsClassified, desc: 'Documente cu nivel de clasificare setat' }, { label: 'Cu tip setat', good: docsTyped, desc: 'Documente cu câmpul type completat' }, ], }, { category: 'Obiective', total: goals.length, checks: [ { label: 'Cu termen țintă', good: goalsWithTarget, desc: 'Obiective cu targetDate' }, ], }, { category: 'Observații', total: observations.length, checks: [ { label: 'Cu confidență ≥ 0.5', good: obsWithConfidence, desc: 'Observații cu scor de încredere adecvat' }, ], }, { category: 'Surse de date', total: dataSources.length, checks: [ { label: 'Surse sănătoase', good: sourcesHealthy, desc: 'Surse active fără erori' }, ], }, ]; }, [tasks, contacts, documents, goals, observations, dataSources]); const overallScore = useMemo(() => { const scores = metrics.flatMap((m) => m.checks.map((c) => score(c.good, m.total)) ); return scores.length === 0 ? 0 : Math.round(scores.reduce((a, b) => a + b, 0) / scores.length); }, [metrics]); const issues = useMemo(() => { const list: { label: string; severity: 'CRITICAL' | 'HIGH' | 'MEDIUM'; action: string; href: string }[] = []; const contactsNoConsent = contacts.filter((c) => !c.consentStatus || c.consentStatus === 'revoked'); if (contactsNoConsent.length > contacts.length * 0.3) list.push({ label: `${contactsNoConsent.length} contacte fără consimțământ GDPR`, severity: 'CRITICAL', action: 'Revizuiește consimțăminte', href: '/dashboard/privacy/consents' }); const docsUnclassified = documents.filter((d) => !d.classification).length; if (docsUnclassified > 5) list.push({ label: `${docsUnclassified} documente neclasificate`, severity: 'HIGH', action: 'Clasifică documente', href: '/dashboard/documents' }); const sourcesWithErrors = dataSources.filter((ds) => (ds.errorCount ?? 0) > 0).length; if (sourcesWithErrors > 0) list.push({ label: `${sourcesWithErrors} surse de date cu erori`, severity: 'HIGH', action: 'Verifică surse', href: '/dashboard/integrations' }); const tasksNoDue = tasks.filter((t) => !t.dueDate && t.status !== 'completed').length; if (tasksNoDue > 10) list.push({ label: `${tasksNoDue} taskuri active fără scadență`, severity: 'MEDIUM', action: 'Setează termene', href: '/dashboard/tasks' }); return list; }, [contacts, documents, dataSources, tasks]); const SEVERITY_CLS = { CRITICAL: 'text-signal-danger', HIGH: 'text-warn', MEDIUM: 'text-ink-faint' }; return (

Calitate Date

Completitudine și consistență a datelor din CEO OS.

{/* Overall score */}

{overallScore}%

Scor calitate global

= 80 ? 'bg-signal-ok' : overallScore >= 60 ? 'bg-warn' : 'bg-signal-danger'}`} style={{ width: `${overallScore}%` }} />

{overallScore >= 80 ? '✅ Date de calitate bună' : overallScore >= 60 ? '⚠ Sunt probleme de adresat' : '❌ Calitate slabă — acțiune necesară'}

{/* Issues */} {issues.length > 0 && (

Probleme detectate

{issues.map((issue, i) => (
{issue.severity} {issue.label}
{issue.action} →
))}
)} {/* Detailed metrics */}
{metrics.map((m) => (

{m.category}

{m.total} intrări
{m.checks.map((check) => { const pct = score(check.good, m.total); return (
{check.label} {pct}%
= 80 ? 'bg-signal-ok' : pct >= 60 ? 'bg-warn' : 'bg-signal-danger'}`} style={{ width: `${pct}%` }} />

{check.good}/{m.total} · {check.desc}

); })}
))}
); }