feat(CC-071): add Integrations Overview page

This commit is contained in:
admin-valentin 2026-08-02 12:29:27 +00:00
parent b2587ef629
commit fb1bedf6aa

View file

@ -0,0 +1,150 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import Link from 'next/link';
import { apiFetch } from '../../../lib/api';
import { useSession } from '../../../components/session-provider';
interface DataSource {
id: string; name: string; sourceType: string; description: string | null;
status: 'active' | 'paused' | 'error' | 'archived'; lastSyncAt: string | null;
recordCount: number | null; createdAt: string;
}
const NATIVE_INTEGRATIONS = [
{ key: 'clickhouse', name: 'ClickHouse (supersrv)', icon: '🗄️', category: 'Database', status: 'connected', description: 'Business intelligence — 147M+ rânduri, date companiei, ESCO.' },
{ key: 'anythingllm', name: 'AnythingLLM', icon: '🤖', category: 'AI / RAG', status: 'connected', description: 'Agenți AI și workspace-uri peste ClickHouse via MCP.' },
{ key: 'hermes', name: 'Hermes (CEO-OS AI)', icon: '⚡', category: 'AI', status: 'connected', description: 'Router inteligent Gemini/OpenRouter pentru toate request-urile AI.' },
{ key: 'forgejo', name: 'Forgejo (Git)', icon: '🔀', category: 'DevOps', status: 'connected', description: 'Repo de cod — ceo-api + ceo-web, CI/CD via Coolify.' },
{ key: 'coolify', name: 'Coolify', icon: '🚀', category: 'DevOps', status: 'connected', description: 'Deploy automat la push — staging + production.' },
{ key: 'openrouter', name: 'OpenRouter', icon: '🌐', category: 'AI', status: 'connected', description: 'Fallback LLM routing pentru modele multiple.' },
{ key: 'n8n', name: 'n8n Workflows', icon: '⚙️', category: 'Automation', status: 'connected', description: '8179 template-uri de automatizare disponibile.' },
{ key: 'metabase', name: 'Metabase', icon: '📊', category: 'Analytics', status: 'connected', description: 'Dashboard-uri BI pe date ClickHouse (OSS, fără AI).' },
];
const PLANNED_INTEGRATIONS = [
{ key: 'gmail', name: 'Gmail', icon: '📧', category: 'Email', description: 'Sincronizare email și automatizare răspunsuri.' },
{ key: 'gcal', name: 'Google Calendar', icon: '📅', category: 'Productivity', description: 'Sincronizare evenimente și meetings.' },
{ key: 'notion', name: 'Notion', icon: '📝', category: 'Productivity', description: 'Export și sincronizare documente.' },
{ key: 'slack', name: 'Slack', icon: '💬', category: 'Communication', description: 'Notificări și comenzi din Slack.' },
{ key: 'stripe', name: 'Stripe', icon: '💳', category: 'Payments', description: 'Sincronizare plăți și facturare.' },
{ key: 'hubspot', name: 'HubSpot', icon: '🎯', category: 'CRM', description: 'Sincronizare contacte și pipeline CRM.' },
];
const STATUS_CLS: Record<string, string> = {
active: 'bg-signal-ok/10 text-signal-ok', connected: 'bg-signal-ok/10 text-signal-ok',
paused: 'bg-warn/10 text-warn', error: 'bg-signal-danger/10 text-signal-danger',
archived: 'bg-muted text-ink-faint',
};
export default function IntegrationsPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const { data: sources = [] } = useQuery({
queryKey: ['data-sources', tenantId],
queryFn: () => apiFetch<DataSource[]>('/v1/data-sources', { tenantId }),
enabled: Boolean(tenantId), staleTime: 60_000,
});
const activeSources = sources.filter((s) => s.status === 'active');
const errorSources = sources.filter((s) => s.status === 'error');
return (
<div className="max-w-4xl space-y-8 p-6">
<div>
<h1 className="font-display text-2xl font-semibold text-ink">Integrări</h1>
<p className="text-sm text-ink-faint mt-1">
{NATIVE_INTEGRATIONS.length} sisteme native conectate · {activeSources.length} surse de date active
{errorSources.length > 0 && <span className="ml-1 text-signal-danger font-semibold">{errorSources.length} erori</span>}
</p>
</div>
{/* Native/infrastructure integrations */}
<section className="space-y-3">
<h2 className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Infrastructură & AI</h2>
<div className="grid gap-3 sm:grid-cols-2">
{NATIVE_INTEGRATIONS.map((integration) => (
<div key={integration.key} className="card p-4 flex items-start gap-3">
<span className="text-2xl">{integration.icon}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 justify-between">
<p className="text-sm font-semibold text-ink">{integration.name}</p>
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${STATUS_CLS[integration.status]}`}>
{integration.status}
</span>
</div>
<p className="text-[10px] text-ink-faint/70 mt-0.5">{integration.category}</p>
<p className="text-xs text-ink-faint mt-1">{integration.description}</p>
</div>
</div>
))}
</div>
</section>
{/* User-added data sources */}
{sources.length > 0 && (
<section className="space-y-3">
<div className="flex items-center justify-between">
<h2 className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Surse de date înregistrate</h2>
<Link href="/dashboard/data/sources" className="text-xs text-primary hover:underline">Gestionează </Link>
</div>
<div className="card divide-y divide-border/50">
{sources.map((s) => (
<div key={s.id} className="p-3 flex items-center justify-between gap-3">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-ink">{s.name}</p>
<p className="text-[10px] text-ink-faint">{s.sourceType} {s.lastSyncAt ? `· sincronizat ${new Date(s.lastSyncAt).toLocaleDateString('ro-RO')}` : ''}</p>
</div>
<div className="flex items-center gap-2">
{s.recordCount != null && (
<span className="text-xs font-mono text-ink-faint">{s.recordCount.toLocaleString('ro-RO')}</span>
)}
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${STATUS_CLS[s.status] ?? 'bg-muted text-ink-faint'}`}>
{s.status}
</span>
</div>
</div>
))}
</div>
</section>
)}
{/* Planned integrations */}
<section className="space-y-3">
<h2 className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Viitoare integrări</h2>
<div className="grid gap-3 sm:grid-cols-3">
{PLANNED_INTEGRATIONS.map((integration) => (
<div key={integration.key} className="card p-4 opacity-60 flex items-start gap-3">
<span className="text-2xl grayscale">{integration.icon}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 justify-between">
<p className="text-sm font-semibold text-ink">{integration.name}</p>
<span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-ink-faint">planificat</span>
</div>
<p className="text-[10px] text-ink-faint/70 mt-0.5">{integration.category}</p>
<p className="text-xs text-ink-faint mt-1">{integration.description}</p>
</div>
</div>
))}
</div>
</section>
{/* Quick links */}
<section className="card p-5 space-y-3">
<p className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Acțiuni rapide</p>
<div className="flex flex-wrap gap-2">
<Link href="/dashboard/data/sources" className="rounded-lg border px-4 py-2 text-sm text-ink hover:bg-muted/50 transition-colors">
+ Adaugă sursă de date
</Link>
<Link href="/dashboard/data/registry" className="rounded-lg border px-4 py-2 text-sm text-ink hover:bg-muted/50 transition-colors">
Registry date
</Link>
<Link href="/dashboard/reports/exports" className="rounded-lg border px-4 py-2 text-sm text-ink hover:bg-muted/50 transition-colors">
Export date
</Link>
</div>
</section>
</div>
);
}