feat(cc-051): Privacy Center overview page

This commit is contained in:
admin-valentin 2026-08-01 10:09:30 +00:00
parent 12727faa30
commit 56faae7eab

View file

@ -0,0 +1,145 @@
'use client';
import Link from 'next/link';
import { useQuery } from '@tanstack/react-query';
import { apiFetch } from '../../../lib/api';
import type { AuditLogEntry, AiRequest, Member } from '../../../lib/api';
import { useSession } from '../../../components/session-provider';
const B = '/dashboard';
interface PrivacyModule {
label: string;
href: string;
status: 'live' | 'planned' | 'a3';
desc: string;
}
const PRIVACY_MODULES: PrivacyModule[] = [
{ label: 'Audit Log', href: `${B}/privacy/audit`, status: 'live', desc: 'Toate acțiunile înregistrate cu actor, timestamp și resursă.' },
{ label: 'Activitate AI', href: `${B}/privacy/ai-activity`, status: 'live', desc: 'Context manifest hash per cerere AI — ce date au fost permise.' },
{ label: 'Roluri & Permisiuni', href: `${B}/privacy/roles`, status: 'live', desc: 'RBAC owner/admin/member — cine are acces la ce.' },
{ label: 'Consimțăminte', href: `${B}/privacy/consents`, status: 'planned', desc: 'Tabel consent_records existent, UI în construcție.' },
{ label: 'Clasificare Date', href: `${B}/privacy/classification`, status: 'planned', desc: 'Clase C0C4 definite în schema; aplicare sistematică în lucru.' },
{ label: 'Export & Ștergere', href: `${B}/privacy/export`, status: 'planned', desc: 'Portabilitate GDPR + cerere de ștergere cu excepții juridice.' },
{ label: 'Politici Retenție', href: `${B}/privacy/retention`, status: 'a3', desc: 'Politici per categorie de date, faza A3.' },
{ label: 'Evenimente Securitate', href: `${B}/privacy/security`, status: 'a3', desc: 'Acces break-glass, incidente, alertă în timp real.' },
];
const STATUS_LABEL: Record<string, string> = { live: '● live', planned: 'în lucru', a3: 'a3' };
const STATUS_CLASS: Record<string, string> = {
live: 'text-signal-ok',
planned: 'text-signal-warn',
a3: 'text-ink-line',
};
export default function PrivacyCenterPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const opts = { enabled: Boolean(tenantId), staleTime: 60_000 };
const { data: auditEntries = [] } = useQuery({
queryKey: ['privacy-audit', tenantId],
queryFn: () => apiFetch<AuditLogEntry[]>('/v1/audit-log?limit=200', { tenantId }),
...opts,
});
const { data: aiRequests = [] } = useQuery({
queryKey: ['privacy-ai', tenantId],
queryFn: () => apiFetch<AiRequest[]>('/v1/ai-requests?limit=200', { tenantId }),
...opts,
});
const { data: members = [] } = useQuery({
queryKey: ['privacy-members', tenantId],
queryFn: () => apiFetch<Member[]>('/v1/tenants/current/members', { tenantId }),
...opts,
});
const liveModules = PRIVACY_MODULES.filter((m) => m.status === 'live').length;
return (
<div className="max-w-4xl space-y-6">
<div>
<h1 className="font-display text-2xl font-semibold text-ink">Privacy Center</h1>
<p className="mt-1 text-sm text-ink-faint">
Punct central pentru confidențialitate, transparență și control date.
{liveModules}/{PRIVACY_MODULES.length} module active.
</p>
</div>
{/* GDPR rights summary */}
<div className="card p-5">
<h2 className="mb-3 text-xs font-semibold uppercase tracking-wider text-ink-faint">
Drepturile tale ca titular de date
</h2>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
{[
{ right: 'Acces', desc: 'Datele tale sunt vizibile în platformă.', available: true },
{ right: 'Rectificare', desc: 'Poți corecta orice date incorecte.', available: true },
{ right: 'Ștergere', desc: 'Cerere de ștergere — în implementare.', available: false },
{ right: 'Portabilitate', desc: 'Export date — în implementare.', available: false },
{ right: 'Opoziție', desc: 'Opt-out procesare AI — parțial.', available: false },
{ right: 'Restricție', desc: 'Limitare procesare — a3.', available: false },
].map((r) => (
<div key={r.right} className={`rounded-lg border p-3 ${r.available ? 'border-signal-ok/30 bg-signal-ok/5' : 'border-paper-sunken'}`}>
<p className="text-xs font-semibold text-ink">{r.right}</p>
<p className="mt-0.5 text-[11px] text-ink-faint">{r.desc}</p>
<p className={`mt-1 text-[10px] font-semibold ${r.available ? 'text-signal-ok' : 'text-ink-line'}`}>
{r.available ? '✓ disponibil' : '○ în lucru'}
</p>
</div>
))}
</div>
</div>
{/* Quick stats */}
<div className="grid grid-cols-3 gap-4">
<Link href={`${B}/privacy/audit`} className="card p-5 hover:border-bronze-deep transition-colors">
<p className="text-xs font-medium text-ink-faint">Înregistrări audit</p>
<p className="mt-1 font-display text-2xl font-semibold text-ink">{auditEntries.length}</p>
</Link>
<Link href={`${B}/privacy/ai-activity`} className="card p-5 hover:border-bronze-deep transition-colors">
<p className="text-xs font-medium text-ink-faint">Cereri AI loggate</p>
<p className="mt-1 font-display text-2xl font-semibold text-ink">{aiRequests.length}</p>
</Link>
<Link href={`${B}/privacy/roles`} className="card p-5 hover:border-bronze-deep transition-colors">
<p className="text-xs font-medium text-ink-faint">Membri workspace</p>
<p className="mt-1 font-display text-2xl font-semibold text-ink">{members.length}</p>
</Link>
</div>
{/* Privacy modules */}
<section>
<h2 className="mb-3 text-xs font-semibold uppercase tracking-wider text-ink-faint">
Module Privacy & Security
</h2>
<ul className="space-y-2">
{PRIVACY_MODULES.map((mod) => (
<li key={mod.label}>
{mod.status === 'live' ? (
<Link href={mod.href} className="card flex items-center gap-4 px-4 py-3 hover:border-bronze-deep transition-colors">
<div className="min-w-0 flex-1">
<p className="text-sm font-medium text-ink">{mod.label}</p>
<p className="mt-0.5 text-xs text-ink-faint">{mod.desc}</p>
</div>
<span className={`shrink-0 text-[10px] font-semibold ${STATUS_CLASS[mod.status]}`}>
{STATUS_LABEL[mod.status]}
</span>
</Link>
) : (
<div className="card flex items-center gap-4 px-4 py-3 opacity-60">
<div className="min-w-0 flex-1">
<p className="text-sm font-medium text-ink">{mod.label}</p>
<p className="mt-0.5 text-xs text-ink-faint">{mod.desc}</p>
</div>
<span className={`shrink-0 text-[10px] font-semibold ${STATUS_CLASS[mod.status]}`}>
{STATUS_LABEL[mod.status]}
</span>
</div>
)}
</li>
))}
</ul>
</section>
</div>
);
}