diff --git a/src/app/dashboard/reports/reputation/page.tsx b/src/app/dashboard/reports/reputation/page.tsx new file mode 100644 index 0000000..3bddc8c --- /dev/null +++ b/src/app/dashboard/reports/reputation/page.tsx @@ -0,0 +1,201 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { apiFetch } from '../../../lib/api'; +import { useSession } from '../../../components/session-provider'; + +interface Observation { id: string; metric: string; value: string; unit: string | null; subjectType: string; confidence: number | null; createdAt: string; observedAt: string | null; source: string | null; } +interface Goal { id: string; title: string; status: string; progress: number | null; } +interface Decision { id: string; title: string; status: string; impact: string | null; outcome: string | null; createdAt: string; } +interface Task { id: string; status: string; priority: string | null; tags: string[]; } + +const EDU_TYPES = ['course', 'certification', 'skill', 'education', 'certificate']; + +export default function ReputationReportsPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const [period, setPeriod] = useState(90); + + const { data: observations = [] } = useQuery({ queryKey: ['rep-obs', tenantId], queryFn: () => apiFetch('/v1/observations', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: goals = [] } = useQuery({ queryKey: ['rep-goals', tenantId], queryFn: () => apiFetch('/v1/goals?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: decisions = [] } = useQuery({ queryKey: ['rep-decisions', tenantId], queryFn: () => apiFetch('/v1/decisions?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: tasks = [] } = useQuery({ queryKey: ['rep-tasks', tenantId], queryFn: () => apiFetch('/v1/tasks?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + + const cutoff = useMemo(() => new Date(Date.now() - period * 86400_000), [period]); + + const metrics = useMemo(() => { + const recentObs = observations.filter((o) => new Date(o.observedAt ?? o.createdAt) >= cutoff); + const eduObs = recentObs.filter((o) => EDU_TYPES.some((e) => o.metric.toLowerCase().includes(e) || o.subjectType.toLowerCase().includes(e))); + const certs = recentObs.filter((o) => o.metric.toLowerCase().includes('certificar') || o.metric.toLowerCase().includes('certificate')); + + const recentGoals = goals.filter((g) => g.status === 'completed'); + const recentDecisions = decisions.filter((d) => new Date(d.createdAt) >= cutoff); + const recentTasks = tasks.filter((t) => t.status === 'completed' && !t.tags.includes('routine')); + const commitmentTasks = tasks.filter((t) => t.tags.some((tag) => ['commitment', 'promise'].includes(tag))); + const keptCommitments = commitmentTasks.filter((t) => t.status === 'completed'); + + const engagementScore = Math.min(100, Math.round( + (recentObs.length * 2 + recentDecisions.length * 5 + recentTasks.length * 1) / (period / 30) + )); + + return { recentObs, eduObs, certs, recentGoals, recentDecisions, recentTasks, commitmentTasks, keptCommitments, engagementScore }; + }, [observations, goals, decisions, tasks, cutoff]); + + function exportReport() { + const lines = [ + `RAPORT REPUTAȚIE CEO OS — ${new Date().toLocaleDateString('ro-RO')}`, + `Perioadă: ultimele ${period} zile`, + '', + '## EDUCAȚIE & COMPETENȚE', + `Observații educație: ${metrics.eduObs.length}`, + `Certificări noi: ${metrics.certs.length}`, + '', + '## OBIECTIVE', + `Obiective finalizate: ${metrics.recentGoals.length}`, + `Progres mediu obiective active: ${goals.filter(g=>g.status==='active').length > 0 ? Math.round(goals.filter(g=>g.status==='active').reduce((s,g)=>s+(g.progress??0),0)/goals.filter(g=>g.status==='active').length) : 0}%`, + '', + '## ANGAJAMENTE', + `Angajamente asumate: ${metrics.commitmentTasks.length}`, + `Angajamente onorate: ${metrics.keptCommitments.length}`, + `Rată onorare: ${metrics.commitmentTasks.length > 0 ? Math.round((metrics.keptCommitments.length/metrics.commitmentTasks.length)*100) : 100}%`, + '', + '## DECIZII', + `Decizii logate: ${metrics.recentDecisions.length}`, + '', + '## PRODUCTIVITATE', + `Taskuri completate: ${metrics.recentTasks.length}`, + `Scor angajament: ${metrics.engagementScore}/100`, + '', + '---', + 'Generat din CEO OS (boardmind.dev)', + 'LIMITĂRI: Acest raport se bazează exclusiv pe date auto-raportate.', + 'Sursele sunt indicate per observație. Nu reprezintă un scor reputațional oficial.', + ]; + const blob = new Blob([lines.join('\n')], { type: 'text/plain;charset=utf-8' }); + const el = document.createElement('a'); + el.href = URL.createObjectURL(blob); + el.download = `reputatie-${new Date().toISOString().slice(0,10)}.txt`; + el.click(); + } + + return ( +
+
+
+

Rapoarte Reputație

+

Sumar dovezi — date auto-raportate, cu surse explicite.

+
+
+ {[30, 90, 180].map((p) => ( + + ))} + +
+
+ +
+

⚠ Limitări obligatorii

+
+

• Toate datele sunt auto-raportate. Nu există verificare externă independentă.

+

• Scorul de reputație afișat NU este un scor social universal sau de credit.

+

• Nu pot fi formulate acuzații neverificate pe baza acestor date.

+

• Partajarea raportului este controlată exclusiv de tine.

+
+
+ + {/* Engagement score */} +
+
+

{metrics.engagementScore}

+

Scor angajament

+
+
+
+
+
+

Bazat pe observații ({metrics.recentObs.length}), decizii ({metrics.recentDecisions.length}) și taskuri ({metrics.recentTasks.length}) în ultimele {period} zile.

+
+
+ +
+
+

Educație & Competențe

+
+
+ Observații educație + {metrics.eduObs.length} +
+
+ Certificări + {metrics.certs.length} +
+
+
+ +
+

Angajamente

+
+
+ Asumate + {metrics.commitmentTasks.length} +
+
+ Onorate + {metrics.keptCommitments.length} +
+
+ Rată onorare + + {metrics.commitmentTasks.length > 0 + ? `${Math.round((metrics.keptCommitments.length / metrics.commitmentTasks.length) * 100)}%` + : '—'} + +
+
+
+ +
+

Obiective

+
+
+ Finalizate + {metrics.recentGoals.length} +
+
+ Progres mediu activ + + {goals.filter((g) => g.status === 'active').length > 0 + ? `${Math.round(goals.filter((g) => g.status === 'active').reduce((s, g) => s + (g.progress ?? 0), 0) / goals.filter((g) => g.status === 'active').length)}%` + : '—'} + +
+
+
+ +
+

Decizii & Activitate

+
+
+ Decizii logate + {metrics.recentDecisions.length} +
+
+ Taskuri completate + {metrics.recentTasks.length} +
+
+ Observații total + {metrics.recentObs.length} +
+
+
+
+
+ ); +}