feat(CC-084): add Platform Administration page (members/audit/danger zone/links)

This commit is contained in:
admin-valentin 2026-08-02 17:31:34 +00:00
parent a25c54970f
commit d3ee6b98fa

View file

@ -0,0 +1,123 @@
'use client';
import { useState } 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 AuditEvent { id: string; action: string; userId: string | null; createdAt: string; metadata: Record<string,unknown> | null; }
interface Member { id: string; userId: string; role: string; createdAt: string; }
export default function SettingsPlatformPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const [dangerOpen, setDangerOpen] = useState(false);
const { data: members = [] } = useQuery({ queryKey: ['plat-members', tenantId], queryFn: () => apiFetch<Member[]>('/v1/workspace/members', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 });
const { data: audit = [] } = useQuery({ queryKey: ['plat-audit', tenantId], queryFn: () => apiFetch<AuditEvent[]>('/v1/audit-log?limit=50', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 });
const adminCount = members.filter((m) => m.role === 'admin' || m.role === 'owner').length;
const PLATFORM_LINKS = [
{ label: 'Utilizatori & Roluri', href: '/dashboard/privacy/delegated', icon: '👥', desc: `${members.length} membri, ${adminCount} admini` },
{ label: 'API & Dezvoltatori', href: '/dashboard/settings/api', icon: '🔑', desc: 'Chei API, endpoint-uri' },
{ label: 'Integrări', href: '/dashboard/integrations', icon: '🔌', desc: 'Conexiuni externe active' },
{ label: 'Export control', href: '/dashboard/data/export-control', icon: '📤', desc: 'Log exporturi date' },
{ label: 'Security Events', href: '/dashboard/privacy/security', icon: '🛡️', desc: 'Autentificări, accesuri' },
{ label: 'Retenție date', href: '/dashboard/privacy/retention', icon: '📁', desc: 'Politici GDPR' },
];
return (
<div className="max-w-3xl space-y-6 p-6">
<div>
<h1 className="font-display text-2xl font-semibold text-ink">Administrare platformă</h1>
<p className="text-sm text-ink-faint mt-1">Configurare avansată CEO OS acces, integrări, GDPR, date.</p>
</div>
{/* Overview */}
<div className="grid grid-cols-3 gap-3">
<div className="card p-4 text-center">
<p className="text-2xl font-bold text-ink">{members.length}</p>
<p className="text-[10px] text-ink-faint">membri workspace</p>
</div>
<div className="card p-4 text-center">
<p className="text-2xl font-bold text-ink">{adminCount}</p>
<p className="text-[10px] text-ink-faint">administratori</p>
</div>
<div className="card p-4 text-center">
<p className="text-2xl font-bold text-ink">{audit.length}</p>
<p className="text-[10px] text-ink-faint">evenimente recente</p>
</div>
</div>
{/* Navigation links */}
<div className="grid gap-2 sm:grid-cols-2">
{PLATFORM_LINKS.map((link) => (
<Link key={link.href} href={link.href}
className="card p-4 flex items-center gap-3 hover:border-primary/40 transition-colors">
<span className="text-xl">{link.icon}</span>
<div>
<p className="text-sm font-medium text-ink">{link.label}</p>
<p className="text-[10px] text-ink-faint">{link.desc}</p>
</div>
</Link>
))}
</div>
{/* Recent audit */}
{audit.length > 0 && (
<div className="card p-5 space-y-3">
<p className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Activitate recentă platformă</p>
<div className="space-y-2 max-h-64 overflow-y-auto">
{audit.slice(0, 20).map((ev) => (
<div key={ev.id} className="flex items-center gap-3 text-xs">
<span className="text-ink-faint shrink-0 font-mono text-[10px]">
{new Date(ev.createdAt).toLocaleTimeString('ro-RO', { hour: '2-digit', minute: '2-digit' })}
</span>
<span className="text-ink truncate">{ev.action}</span>
</div>
))}
</div>
<Link href="/dashboard/privacy/security" className="text-xs text-primary hover:underline">
Vezi toate evenimentele
</Link>
</div>
)}
{/* Danger zone */}
<div className="card p-5 border-signal-danger/30 space-y-3">
<button onClick={() => setDangerOpen(!dangerOpen)}
className="flex items-center gap-2 text-sm font-semibold text-signal-danger w-full">
<span> Zona periculoasă</span>
<span className="ml-auto text-ink-faint">{dangerOpen ? '▲' : '▼'}</span>
</button>
{dangerOpen && (
<div className="space-y-3 pt-2 border-t border-signal-danger/20">
<p className="text-[10px] text-ink-faint">
Acțiunile din această zonă sunt ireversibile sau pot afecta datele platformei.
Contactează support înainte de a proceda.
</p>
{[
{ label: 'Exportă toate datele (GDPR Article 20)', desc: 'Descarcă toate datele workspace-ului în format JSON', action: 'export' },
{ label: 'Resetează setările workspace', desc: 'Revine la configurația implicită (nu șterge date)', action: 'reset-settings' },
{ label: 'Șterge workspace-ul', desc: 'Șterge definitiv toate datele — IREVERSIBIL', action: 'delete', danger: true },
].map((op) => (
<div key={op.action} className="flex items-center justify-between gap-3">
<div>
<p className={`text-xs font-medium ${op.danger ? 'text-signal-danger' : 'text-ink'}`}>{op.label}</p>
<p className="text-[10px] text-ink-faint">{op.desc}</p>
</div>
<button disabled
className={`rounded border px-3 py-1 text-[10px] font-medium shrink-0 opacity-60 cursor-not-allowed ${op.danger ? 'border-signal-danger text-signal-danger' : 'border-border text-ink-faint'}`}>
{op.action === 'delete' ? 'Șterge' : op.action === 'export' ? 'Exportă' : 'Resetează'}
</button>
</div>
))}
<p className="text-[10px] text-ink-faint italic">* Butoanele sunt dezactivate funcționalitate în dezvoltare</p>
</div>
)}
</div>
</div>
);
}