diff --git a/src/app/dashboard/trust/credibility/page.tsx b/src/app/dashboard/trust/credibility/page.tsx new file mode 100644 index 0000000..4cdd7bf --- /dev/null +++ b/src/app/dashboard/trust/credibility/page.tsx @@ -0,0 +1,171 @@ +'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 Observation { id: string; metric: string; value: string; subjectType: string; source: string | null; confidence: number | null; createdAt: string; observedAt: string | null; } +interface Goal { id: string; status: string; } +interface Task { id: string; status: string; tags: string[]; } +interface Contact { id: string; tags: string[]; consentStatus: string | null; } + +interface CredibilityFactor { + id: string; label: string; icon: string; + value: number; source: string; date: string | null; detail: string; +} + +const CRED_METRICS = ['certificare', 'certification', 'certificate', 'diploma', 'curs', 'course', 'badge']; +const SKILL_METRICS = ['skill', 'competenta', 'abilitate', 'limbaj', 'framework', 'tool']; +const REF_TAGS = ['reference', 'referinta', 'testimonial', 'recomandare']; +const COMMIT_TAGS = ['commitment', 'promise']; + +export default function TrustCredibilityPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + + const { data: observations = [] } = useQuery({ queryKey: ['cred-obs', tenantId], queryFn: () => apiFetch('/v1/observations', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: goals = [] } = useQuery({ queryKey: ['cred-goals', tenantId], queryFn: () => apiFetch('/v1/goals?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: tasks = [] } = useQuery({ queryKey: ['cred-tasks', tenantId], queryFn: () => apiFetch('/v1/tasks?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: contacts = [] } = useQuery({ queryKey: ['cred-contacts', tenantId], queryFn: () => apiFetch('/v1/contacts?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + + const factors = useMemo((): CredibilityFactor[] => { + const creds = observations.filter((o) => CRED_METRICS.some((k) => o.metric.toLowerCase().includes(k))); + const skills = observations.filter((o) => SKILL_METRICS.some((k) => o.metric.toLowerCase().includes(k))); + const refs = contacts.filter((c) => c.tags.some((t) => REF_TAGS.includes(t.toLowerCase()))); + const commitTasks = tasks.filter((t) => t.tags.some((tag) => COMMIT_TAGS.includes(tag))); + const keptCommits = commitTasks.filter((t) => t.status === 'completed'); + const completedGoals = goals.filter((g) => g.status === 'completed'); + const avgConf = observations.length > 0 ? observations.reduce((s, o) => s + (o.confidence ?? 1), 0) / observations.length : 0; + + return [ + { + id: 'credentials', + label: 'Credențiale verificabile', + icon: '🎓', + value: Math.min(100, creds.length * 10), + source: 'auto (observations)', + date: creds[0]?.observedAt ?? creds[0]?.createdAt ?? null, + detail: `${creds.length} certificări/diplome logate`, + }, + { + id: 'skills', + label: 'Competențe declarate', + icon: '🛠️', + value: Math.min(100, skills.length * 8), + source: 'auto (observations)', + date: skills[0]?.observedAt ?? null, + detail: `${skills.length} competențe logate`, + }, + { + id: 'references', + label: 'Referințe disponibile', + icon: '📋', + value: Math.min(100, refs.length * 20), + source: 'auto (contacts)', + date: null, + detail: `${refs.length} contacte cu tag referință`, + }, + { + id: 'commitments', + label: 'Angajamente onorate', + icon: '🤝', + value: commitTasks.length > 0 ? Math.round((keptCommits.length / commitTasks.length) * 100) : 100, + source: 'auto (tasks)', + date: null, + detail: `${keptCommits.length}/${commitTasks.length} finalizate`, + }, + { + id: 'track_record', + label: 'Track record obiective', + icon: '🎯', + value: goals.length > 0 ? Math.min(100, Math.round((completedGoals.length / goals.length) * 100) + completedGoals.length * 2) : 0, + source: 'auto (goals)', + date: null, + detail: `${completedGoals.length}/${goals.length} obiective finalizate`, + }, + { + id: 'data_quality', + label: 'Calitate date auto-raportate', + icon: '📊', + value: Math.round(avgConf * 100), + source: 'auto (confidence)', + date: null, + detail: `Confidență medie: ${Math.round(avgConf * 100)}%`, + }, + ]; + }, [observations, goals, tasks, contacts]); + + const overall = useMemo(() => + factors.length > 0 ? Math.round(factors.reduce((s, f) => s + f.value, 0) / factors.length) : 0, + [factors]); + + function scoreCls(s: number) { return s >= 70 ? 'text-signal-ok' : s >= 40 ? 'text-warn' : 'text-signal-danger'; } + function barCls(s: number) { return s >= 70 ? 'bg-signal-ok' : s >= 40 ? 'bg-warn' : 'bg-signal-danger'; } + + return ( +
+
+ +

Profil Credibilitate

+

Fiecare factor cu valoare, sursă, dată și posibilitate de contestare.

+
+ +
+

+ Factorii de credibilitate sunt derivați exclusiv din date auto-raportate în CEO OS. + Nu reprezintă un scor de credit sau o evaluare externă. Utilizatorul poate contesta sau corecta orice factor. +

+
+ + {/* Overall */} +
+
+

{overall}

+

credibilitate

+
+
+
+
+
+

Medie aritmetică a {factors.length} factori

+
+
+ + {/* Factor details */} +
+ {factors.map((f) => ( +
+
+
+ {f.icon} +
+

{f.label}

+

{f.detail}

+
+
+
+

{f.value}%

+

{f.source}

+
+
+
+
+
+
+ {f.date ? ( +

Ultima actualizare: {new Date(f.date).toLocaleDateString('ro-RO', { dateStyle: 'medium' })}

+ ) : } + +
+
+ ))} +
+ + ← Trust Dashboard +
+ ); +}