From 4288839b0602b15f7d15fcbaae1d36a1cdfc9e16 Mon Sep 17 00:00:00 2001 From: admin-valentin Date: Sun, 2 Aug 2026 17:22:12 +0000 Subject: [PATCH] feat(CC-083): add Trust Dashboard (weighted score from credentials/commitments/privacy/consistency) --- src/app/dashboard/trust/page.tsx | 149 +++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/app/dashboard/trust/page.tsx diff --git a/src/app/dashboard/trust/page.tsx b/src/app/dashboard/trust/page.tsx new file mode 100644 index 0000000..dd03685 --- /dev/null +++ b/src/app/dashboard/trust/page.tsx @@ -0,0 +1,149 @@ +'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; confidence: number | null; source: string | null; createdAt: string; observedAt: string | null; } +interface Goal { id: string; status: string; progress: number | null; } +interface Task { id: string; status: string; tags: string[]; priority: string | null; } +interface Contact { id: string; consentStatus: string | null; } + +const TRUST_FACTORS = [ + { id: 'credentials', label: 'Credențiale & Certificări', href: '/dashboard/trust/credentials', icon: '🎓', weight: 25 }, + { id: 'professional', label: 'Reputație Profesională', href: '/dashboard/trust/professional', icon: '💼', weight: 25 }, + { id: 'commitments', label: 'Angajamente Onorate', href: '/dashboard/promises', icon: '🤝', weight: 20 }, + { id: 'consistency', label: 'Consistență Date', href: '/dashboard/data/quality', icon: '📊', weight: 15 }, + { id: 'privacy', label: 'Conformitate Privacy', href: '/dashboard/privacy/consents', icon: '🛡️', weight: 15 }, +]; + +const TRUST_LINKS = [ + { label: 'Credențiale', href: '/dashboard/trust/credentials', icon: '🎓' }, + { label: 'Reputație Profesională', href: '/dashboard/trust/professional', icon: '💼' }, + { label: 'Reputație Socială', href: '/dashboard/trust/social', icon: '🌐' }, + { label: 'Proiecte & Rezultate', href: '/dashboard/trust/outcomes', icon: '🎯' }, + { label: 'Referințe', href: '/dashboard/trust/references', icon: '📋' }, + { label: 'Contribuții', href: '/dashboard/trust/contributions', icon: '🤲' }, + { label: 'Dispute & Incidente', href: '/dashboard/trust/disputes', icon: '⚖️' }, + { label: 'Trust Passport', href: '/dashboard/trust/passport', icon: '🗝️' }, +]; + +export default function TrustDashboardPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + + const { data: observations = [] } = useQuery({ queryKey: ['trust-obs', tenantId], queryFn: () => apiFetch('/v1/observations', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: goals = [] } = useQuery({ queryKey: ['trust-goals', tenantId], queryFn: () => apiFetch('/v1/goals?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: tasks = [] } = useQuery({ queryKey: ['trust-tasks', tenantId], queryFn: () => apiFetch('/v1/tasks?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: contacts = [] } = useQuery({ queryKey: ['trust-contacts', tenantId], queryFn: () => apiFetch('/v1/contacts?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + + const scores = useMemo(() => { + const eduObs = observations.filter((o) => ['course','certification','skill','education'].some((k) => o.metric.toLowerCase().includes(k))); + const credScore = Math.min(100, eduObs.length * 10); + + const completedGoals = goals.filter((g) => g.status === 'completed').length; + const totalGoals = goals.length; + const profScore = totalGoals > 0 ? Math.min(100, Math.round((completedGoals / totalGoals) * 100) + completedGoals * 5) : 0; + + const commitTasks = tasks.filter((t) => t.tags.some((tag) => ['commitment','promise'].includes(tag))); + const keptTasks = commitTasks.filter((t) => t.status === 'completed'); + const commitScore = commitTasks.length > 0 ? Math.round((keptTasks.length / commitTasks.length) * 100) : 100; + + const validObs = observations.filter((o) => (o.confidence ?? 1) >= 0.5).length; + const consistScore = observations.length > 0 ? Math.round((validObs / observations.length) * 100) : 50; + + const grantedContacts = contacts.filter((c) => c.consentStatus === 'granted').length; + const privacyScore = contacts.length > 0 ? Math.round((grantedContacts / contacts.length) * 100) : 100; + + const factorScores: Record = { + credentials: credScore, professional: profScore, commitments: commitScore, + consistency: consistScore, privacy: privacyScore, + }; + + const overall = Math.round( + TRUST_FACTORS.reduce((sum, f) => sum + (factorScores[f.id] ?? 0) * f.weight / 100, 0) + ); + + return { factorScores, overall }; + }, [observations, goals, tasks, contacts]); + + function scoreCls(s: number) { + if (s >= 70) return 'text-signal-ok'; + if (s >= 40) return 'text-warn'; + return 'text-signal-danger'; + } + function barCls(s: number) { + if (s >= 70) return 'bg-signal-ok'; + if (s >= 40) return 'bg-warn'; + return 'bg-signal-danger'; + } + + return ( +
+
+

Trust Dashboard

+

Completitudinea profilului de încredere — date auto-raportate cu surse explicite.

+
+ +
+

+ Important: Scorul afișat nu este un scor social universal sau de credit. + Reflectă completitudinea datelor auto-raportate în CEO OS. Nu poate fi folosit ca acuzație sau clasificare externă. + Utilizatorul controlează ce se partajează prin Trust Passport. +

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

{scores.overall}

+

Scor completitudine

+
+
+
+
+
+

+ {scores.overall >= 70 ? '✅ Profil bine documentat' : scores.overall >= 40 ? '⚠ Lipsesc dovezi în unele arii' : '❌ Profil incomplet — adaugă dovezi'} +

+
+
+ + {/* Factor breakdown */} +
+

Factori de încredere

+ {TRUST_FACTORS.map((f) => { + const s = scores.factorScores[f.id] ?? 0; + return ( + +
+
+ {f.icon} + {f.label} + ({f.weight}%) +
+ {s}% +
+
+
+
+ + ); + })} +
+ + {/* Module links */} +
+ {TRUST_LINKS.map((link) => ( + + {link.icon} +

{link.label}

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