'use client'; import Link from 'next/link'; import { useQuery } from '@tanstack/react-query'; import { apiFetch } from '../../../lib/api'; import { useSession } from '../../../components/session-provider'; interface Obligation { id: string; title: string; status: string; category: string; dueDate: string | null; riskLevel: string | null; } interface Risk { id: string; title: string; status: string; probability: string; impact: string; riskScore: number | null; } interface Contract { id: string; title: string; status: string; endDate: string | null; contractType: string; } interface Transaction { id: string; description: string | null; amountMinorUnits: string; currency: string; evidenceStatus: string; transactionDate: string; } const B = '/dashboard'; function daysUntil(d: string) { return Math.ceil((new Date(d).getTime() - Date.now()) / 86400_000); } function ScoreBar({ value, max = 100, cls = '' }: { value: number; max?: number; cls?: string }) { const pct = Math.min(100, Math.round((value / max) * 100)); return (
); } export default function CompliancePage() { const { activeTenant } = useSession(); const tenantId = activeTenant?.tenantId ?? ''; const { data: obligs = [] } = useQuery({ queryKey: ['comp-obligs', tenantId], queryFn: () => apiFetch('/v1/obligations?status=pending&limit=100', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000, }); const { data: risks = [] } = useQuery({ queryKey: ['comp-risks', tenantId], queryFn: () => apiFetch('/v1/risks?status=open&limit=100', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000, }); const { data: contracts = [] } = useQuery({ queryKey: ['comp-contracts', tenantId], queryFn: () => apiFetch('/v1/contracts?status=active', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000, }); const { data: txMissing = [] } = useQuery({ queryKey: ['comp-tx', tenantId], queryFn: () => apiFetch('/v1/transactions?evidenceStatus=missing&limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000, }); const { data: allTx = [] } = useQuery({ queryKey: ['comp-tx-all', tenantId], queryFn: () => apiFetch('/v1/transactions?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000, }); const overdueObligs = obligs.filter((o) => o.dueDate && daysUntil(o.dueDate) < 0); const urgentObligs = obligs.filter((o) => o.dueDate && daysUntil(o.dueDate) >= 0 && daysUntil(o.dueDate) <= 30); const criticalRisks = risks.filter((r) => (r.riskScore ?? 0) >= 12); const highRisks = risks.filter((r) => (r.riskScore ?? 0) >= 6 && (r.riskScore ?? 0) < 12); const expiringContracts = contracts.filter((c) => c.endDate && daysUntil(c.endDate) <= 60); const evidencePct = allTx.length > 0 ? Math.round(allTx.filter((t) => t.evidenceStatus === 'complete' || t.evidenceStatus === 'not_required').length / allTx.length * 100) : 100; // Compliance health score (0-100) const score = Math.max(0, 100 - overdueObligs.length * 15 - criticalRisks.length * 10 - highRisks.length * 5 - expiringContracts.filter((c) => daysUntil(c.endDate!) <= 14).length * 8 - Math.round((100 - evidencePct) * 0.3) ); const scoreColor = score >= 80 ? 'text-signal-ok' : score >= 60 ? 'text-signal-warn' : 'text-signal-danger'; const scoreBarCls = score >= 80 ? 'bg-signal-ok' : score >= 60 ? 'bg-signal-warn' : 'bg-signal-danger'; return (

Dashboard Conformitate

Vedere consolidată a riscurilor, obligațiilor și dovezilor

{/* Health score */}

Scor Conformitate

{score}

/100 — actualizat în timp real

≥80 Bine
60–79 Atenție
<60 Critic
{/* 4 summary cards */}
{[ { label: 'Obligații scadente', val: overdueObligs.length, cls: 'text-signal-danger', link: `${B}/obligations`, warn: overdueObligs.length > 0 }, { label: 'Riscuri critice', val: criticalRisks.length, cls: 'text-signal-danger', link: `${B}/risks`, warn: criticalRisks.length > 0 }, { label: 'Contracte expirate', val: expiringContracts.length, cls: 'text-signal-warn', link: `${B}/contracts`, warn: expiringContracts.length > 0 }, { label: 'Acoperire dovezi', val: `${evidencePct}%`, cls: evidencePct >= 80 ? 'text-signal-ok' : 'text-signal-warn', link: `${B}/evidence`, warn: evidencePct < 80 }, ].map(({ label, val, cls, link, warn }) => (

{val}

{label}

))}
{/* Overdue obligations */}

Obligații depășite ({overdueObligs.length + urgentObligs.length})

Toate →
{overdueObligs.length + urgentObligs.length === 0 ? (

✅ Nicio obligație urgentă

) : (
{[...overdueObligs, ...urgentObligs].slice(0, 10).map((o) => { const days = o.dueDate ? daysUntil(o.dueDate) : null; return (
{days !== null && days < 0 ? '🔴' : '🟡'}

{o.title}

{o.category}{days !== null ? ` · ${days < 0 ? `${Math.abs(days)}z depășit` : `${days}z rămase`}` : ''}

); })}
)}
{/* Critical & high risks */}

Riscuri critice/ridicate ({criticalRisks.length + highRisks.length})

Toate →
{criticalRisks.length + highRisks.length === 0 ? (

✅ Niciun risc critic deschis

) : (
{[...criticalRisks, ...highRisks].slice(0, 10).map((r) => (
= 12 ? 'text-signal-danger' : 'text-signal-warn'}`}> {r.riskScore ?? '?'}

{r.title}

{r.probability}/{r.impact}

))}
)}
{/* Expiring contracts */}

Contracte cu termen ({expiringContracts.length})

Toate →
{expiringContracts.length === 0 ? (

✅ Niciun contract expirant în 60 zile

) : (
{expiringContracts.slice(0, 8).map((c) => { const days = daysUntil(c.endDate!); return (

{c.title}

{days}z
); })}
)}
{/* Evidence coverage */}

Acoperire Dovezi

Centru dovezi →
Acoperire totală {evidencePct}%
Lipsă dovadă {txMissing.length} tranzacții
{txMissing.slice(0, 4).map((tx) => (
{tx.description ?? tx.transactionDate} {(parseFloat(tx.amountMinorUnits) / 100).toFixed(0)} {tx.currency}
))} {txMissing.length > 4 && (

și alte {txMissing.length - 4}…

)}
); }