feat(CC-087): add Trust Credit Readiness (financial data completeness, MANDATORY disclaimers)

This commit is contained in:
admin-valentin 2026-08-02 17:41:48 +00:00
parent 18cf60f3d3
commit 46d3836567

View file

@ -0,0 +1,130 @@
'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 Transaction { id: string; type: string; amount: number; currency: string | null; date: string; description: string | null; }
interface Goal { id: string; status: string; tags: string[]; }
interface Observation { id: string; metric: string; value: string; confidence: number | null; }
const INCOME_TYPES = ['income', 'venit', 'revenue', 'incasare'];
const EXPENSE_TYPES = ['expense', 'cheltuiala', 'cheltuială', 'cost'];
export default function TrustCreditReadinessPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const { data: transactions = [] } = useQuery({ queryKey: ['cr-txn', tenantId], queryFn: () => apiFetch<Transaction[]>('/v1/transactions?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 });
const { data: goals = [] } = useQuery({ queryKey: ['cr-goals', tenantId], queryFn: () => apiFetch<Goal[]>('/v1/goals?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 });
const { data: observations = [] } = useQuery({ queryKey: ['cr-obs', tenantId], queryFn: () => apiFetch<Observation[]>('/v1/observations', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 });
const analysis = useMemo(() => {
const income = transactions.filter((t) => INCOME_TYPES.includes(t.type.toLowerCase()));
const expense = transactions.filter((t) => EXPENSE_TYPES.includes(t.type.toLowerCase()));
const totalIncome = income.reduce((s, t) => s + t.amount, 0);
const totalExpense = expense.reduce((s, t) => s + Math.abs(t.amount), 0);
const cashflow = totalIncome - totalExpense;
const savingsRate = totalIncome > 0 ? Math.round((cashflow / totalIncome) * 100) : 0;
const months = new Set(transactions.map((t) => t.date.slice(0, 7))).size;
const finGoals = goals.filter((g) => g.tags.some((tag) => ['financial','financiar','investitie','investment','economii','savings'].includes(tag.toLowerCase())));
const credObs = observations.filter((o) => o.metric.toLowerCase().includes('certif') || o.metric.toLowerCase().includes('diploma'));
const avgConf = observations.length > 0 ? observations.reduce((s, o) => s + (o.confidence ?? 1), 0) / observations.length : 0;
const checks = [
{ label: 'Venituri logate', done: income.length > 0, detail: `${income.length} tranzacții`, impact: 25 },
{ label: 'Flux de numerar pozitiv', done: cashflow > 0, detail: cashflow > 0 ? `+${cashflow.toFixed(0)}` : `${cashflow.toFixed(0)}`, impact: 20 },
{ label: 'Rată economii > 10%', done: savingsRate >= 10, detail: `${savingsRate}%`, impact: 20 },
{ label: 'Consistență date (3+ luni)', done: months >= 3, detail: `${months} luni`, impact: 15 },
{ label: 'Obiective financiare', done: finGoals.length > 0, detail: `${finGoals.length} obiective`, impact: 10 },
{ label: 'Credențiale verificabile', done: credObs.length > 0, detail: `${credObs.length} credențiale`, impact: 10 },
];
const score = checks.filter((c) => c.done).reduce((s, c) => s + c.impact, 0);
return { checks, score, totalIncome, totalExpense, cashflow, savingsRate, months };
}, [transactions, goals, observations]);
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 (
<div className="max-w-3xl space-y-6 p-6">
<div>
<nav className="text-xs text-ink-faint mb-1">
<Link href="/dashboard/trust" className="hover:underline">Trust Dashboard</Link> / Credit Readiness
</nav>
<h1 className="font-display text-2xl font-semibold text-ink">Credit Readiness</h1>
<p className="text-sm text-ink-faint mt-1">Pregătire pentru finanțare analiză internă din datele CEO OS.</p>
</div>
<div className="card p-4 bg-signal-danger/5 border-signal-danger/30 space-y-1">
<p className="text-xs font-semibold text-signal-danger">Nu este un scor de credit</p>
<p className="text-[10px] text-ink-faint">
Acest indicator reflectă <strong>disponibilitatea datelor financiare auto-raportate</strong> din CEO OS.
Nu este un scor de credit bancar, nu reflectă istoricul de plăți și nu poate fi folosit ca garanție sau evaluare externă.
Pentru finanțare reală, consultă o instituție financiară autorizată.
</p>
</div>
{/* Score */}
<div className="card p-5 flex items-center gap-6">
<div className="text-center w-24 shrink-0">
<p className={`font-display text-5xl font-bold ${scoreCls(analysis.score)}`}>{analysis.score}</p>
<p className="text-[10px] text-ink-faint mt-1">pregătire date</p>
</div>
<div className="flex-1 space-y-2">
<div className="h-3 rounded-full bg-muted overflow-hidden">
<div className={`h-full rounded-full ${barCls(analysis.score)}`} style={{ width: `${analysis.score}%` }} />
</div>
<p className="text-xs text-ink-faint">
{analysis.score >= 70 ? '✅ Date financiare bine documentate' : analysis.score >= 40 ? '⚠ Lipsesc câteva categorii cheie' : '❌ Date financiare insuficiente în CEO OS'}
</p>
</div>
</div>
{/* Financial snapshot */}
<div className="grid grid-cols-3 gap-3">
<div className="card p-3 text-center">
<p className="text-lg font-bold text-signal-ok">{analysis.totalIncome.toLocaleString()}</p>
<p className="text-[10px] text-ink-faint">venituri totale</p>
</div>
<div className="card p-3 text-center">
<p className="text-lg font-bold text-signal-danger">{analysis.totalExpense.toLocaleString()}</p>
<p className="text-[10px] text-ink-faint">cheltuieli totale</p>
</div>
<div className="card p-3 text-center">
<p className={`text-lg font-bold ${analysis.cashflow >= 0 ? 'text-signal-ok' : 'text-signal-danger'}`}>
{analysis.cashflow >= 0 ? '+' : ''}{analysis.cashflow.toLocaleString()}
</p>
<p className="text-[10px] text-ink-faint">cash flow net</p>
</div>
</div>
{/* Checklist */}
<div className="card p-5 space-y-3">
<p className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Criterii de pregătire date</p>
{analysis.checks.map((c) => (
<div key={c.label} className="flex items-center gap-3">
<span className={`text-lg shrink-0 ${c.done ? 'text-signal-ok' : 'text-ink-faint'}`}>
{c.done ? '✓' : '○'}
</span>
<div className="flex-1">
<p className="text-sm text-ink">{c.label}</p>
<p className="text-[10px] text-ink-faint">{c.detail}</p>
</div>
<span className="text-[10px] text-ink-faint shrink-0">+{c.impact}pt</span>
</div>
))}
</div>
<div className="flex flex-wrap gap-3 text-xs">
<Link href="/dashboard/finance" className="text-primary hover:underline">Finance Dashboard </Link>
<Link href="/dashboard/trust/credibility" className="text-primary hover:underline">Profil Credibilitate </Link>
</div>
</div>
);
}