feat(CC-069): add Dataset Registry page (system + user datasets, links to data sources)

This commit is contained in:
admin-valentin 2026-08-02 12:21:34 +00:00
parent 176850458a
commit 418957676c

View file

@ -0,0 +1,124 @@
'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 DataSource { id: string; name: string; sourceType: string; description: string | null; status: string; lastSyncAt: string | null; recordCount: number | null; createdAt: string; }
const TYPE_ICON: Record<string, string> = {
manual: '✋', api: '🔌', file: '📄', database: '🗄️', integration: '🔗', clickhouse: '🏢', sheet: '📊',
};
const SYSTEM_DATASETS = [
{ name: 'ClickHouse — manual_ingest.people_data', type: 'clickhouse', records: '147M+', status: 'active', desc: 'Date persoane din ingestie manuală (C3 — restricționat)' },
{ name: 'ClickHouse — boardmind schema', type: 'clickhouse', records: 'live', status: 'active', desc: 'Toate tabelele PostgreSQL CEO OS (via MCP)' },
{ name: 'AnythingLLM — workspace agents', type: 'integration', records: '10 workspace-uri', status: 'active', desc: 'Agenți date peste ClickHouse la supersrv:3010' },
];
export default function DataRegistryPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const { data: userSources = [], isLoading } = useQuery({
queryKey: ['data-sources', tenantId],
queryFn: () => apiFetch<DataSource[]>('/v1/data-sources', { tenantId }),
enabled: Boolean(tenantId), staleTime: 120_000,
});
const totalUserRecords = userSources.reduce((s, ds) => s + (ds.recordCount ?? 0), 0);
const activeUser = userSources.filter((ds) => ds.status === 'active').length;
return (
<div className="max-w-4xl space-y-6 p-6">
<div className="flex items-center justify-between">
<div>
<h1 className="font-display text-2xl font-semibold text-ink">Registru Dataset</h1>
<p className="text-sm text-ink-faint mt-1">
{isLoading ? 'Se încarcă…' : `${SYSTEM_DATASETS.length} sistem · ${userSources.length} personalizate · ${activeUser} active`}
</p>
</div>
<Link href="/dashboard/data/sources" className="text-xs text-bronze-deep hover:underline">
Gestionează surse
</Link>
</div>
{/* System datasets */}
<div className="space-y-3">
<h2 className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Dataset-uri sistem</h2>
<div className="card divide-y divide-border/50">
{SYSTEM_DATASETS.map((ds) => (
<div key={ds.name} className="flex items-start gap-4 p-4">
<span className="text-xl shrink-0 mt-0.5">{TYPE_ICON[ds.type] ?? '❓'}</span>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-ink font-mono truncate">{ds.name}</p>
<p className="text-xs text-ink-faint mt-0.5">{ds.desc}</p>
</div>
<div className="text-right shrink-0">
<p className="text-sm font-mono font-bold text-ink">{ds.records}</p>
<span className="text-[10px] text-signal-ok"> activ</span>
</div>
</div>
))}
</div>
</div>
{/* User-registered datasets */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<h2 className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Dataset-uri personalizate</h2>
{totalUserRecords > 0 && (
<span className="text-xs text-ink-faint font-mono">{totalUserRecords.toLocaleString('ro-RO')} înregistrări totale</span>
)}
</div>
{isLoading ? (
<div className="card p-8 text-center text-sm text-ink-faint">Se încarcă</div>
) : userSources.length === 0 ? (
<div className="card p-8 text-center space-y-2">
<p className="text-sm text-ink-faint">Niciun dataset personalizat înregistrat.</p>
<Link href="/dashboard/data/sources" className="text-xs text-bronze-deep hover:underline">
Adaugă prima sursă de date
</Link>
</div>
) : (
<div className="card divide-y divide-border/50">
{userSources.map((ds) => (
<div key={ds.id} className="flex items-start gap-4 p-4">
<span className="text-xl shrink-0 mt-0.5">{TYPE_ICON[ds.sourceType] ?? '❓'}</span>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-ink truncate">{ds.name}</p>
{ds.description && <p className="text-xs text-ink-faint mt-0.5">{ds.description}</p>}
{ds.lastSyncAt && (
<p className="text-[10px] text-ink-faint mt-0.5">
Sync: {new Date(ds.lastSyncAt).toLocaleDateString('ro-RO')}
</p>
)}
</div>
<div className="text-right shrink-0">
{ds.recordCount != null ? (
<p className="text-sm font-mono font-bold text-ink">{ds.recordCount.toLocaleString('ro-RO')}</p>
) : (
<p className="text-xs text-ink-faint"></p>
)}
<span className={`text-[10px] ${ds.status === 'active' ? 'text-signal-ok' : 'text-ink-faint'}`}>
{ds.status}
</span>
</div>
</div>
))}
</div>
)}
</div>
{/* Info note */}
<div className="rounded-lg border border-border/50 bg-muted/30 p-4">
<p className="text-xs text-ink-faint leading-relaxed">
<strong className="text-ink">Arhitectură date:</strong> CEO OS funcționează cu date din ClickHouse (147M+ rânduri, procesate prin ingestie),
date din PostgreSQL (baza CEO OS), și surse externe configurate manual.
Agenții AnythingLLM pot interoga ClickHouse prin MCP direct.
</p>
</div>
</div>
);
}