feat(cc-051): Intelligence Overview aggregate page
This commit is contained in:
parent
5d2d870c4f
commit
91e114aaae
1 changed files with 158 additions and 0 deletions
158
src/app/dashboard/intelligence/page.tsx
Normal file
158
src/app/dashboard/intelligence/page.tsx
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiFetch } from '../../../lib/api';
|
||||
import type { SavedSegment, ResearchBrief } from '../../../lib/api';
|
||||
import { useSession } from '../../../components/session-provider';
|
||||
|
||||
const B = '/dashboard';
|
||||
|
||||
interface StatCard { label: string; value: number | string; href: string; sublabel?: string }
|
||||
|
||||
export default function IntelligenceOverviewPage() {
|
||||
const { activeTenant } = useSession();
|
||||
const tenantId = activeTenant?.tenantId ?? '';
|
||||
|
||||
const { data: segments = [], isLoading: loadingSegs } = useQuery({
|
||||
queryKey: ['segments-intel', tenantId],
|
||||
queryFn: () => apiFetch<SavedSegment[]>('/v1/segments', { tenantId }),
|
||||
enabled: Boolean(tenantId),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
const { data: briefs = [], isLoading: loadingBriefs } = useQuery({
|
||||
queryKey: ['briefs-intel', tenantId],
|
||||
queryFn: () => apiFetch<ResearchBrief[]>('/v1/research-briefs', { tenantId }),
|
||||
enabled: Boolean(tenantId),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
const isLoading = loadingSegs || loadingBriefs;
|
||||
|
||||
const recentBriefs = [...briefs].sort(
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
).slice(0, 5);
|
||||
|
||||
const stats: StatCard[] = [
|
||||
{ label: 'Segmente salvate', value: segments.length, href: `${B}/segments`, sublabel: 'Căutări B2B salvate' },
|
||||
{ label: 'Research Briefs', value: briefs.length, href: `${B}/research`, sublabel: 'Rapoarte de cercetare' },
|
||||
{ label: 'Companii B2B', value: '147M+', href: `${B}/companies`, sublabel: 'Apollo/ClickHouse' },
|
||||
{ label: 'Contact match AI', value: 'live', href: `${B}/companies`, sublabel: 'intelligence-api activ' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl space-y-6">
|
||||
<div>
|
||||
<h1 className="font-display text-2xl font-semibold text-ink">Intelligence Overview</h1>
|
||||
<p className="mt-1 text-sm text-ink-faint">
|
||||
Motorul de intelligence B2B — companii, segmente, research și insight-uri.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<p className="text-sm text-ink-faint">Se încarcă…</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
{stats.map((s) => (
|
||||
<Link key={s.label} href={s.href} className="card p-5 transition-colors hover:border-bronze-deep">
|
||||
<p className="text-xs font-medium uppercase tracking-wider text-ink-faint">{s.label}</p>
|
||||
<p className="mt-1 font-display text-3xl font-semibold text-ink">{s.value}</p>
|
||||
{s.sublabel && <p className="mt-0.5 text-[11px] text-ink-line">{s.sublabel}</p>}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
{/* Recent research briefs */}
|
||||
<section className="card p-5">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-ink-faint">
|
||||
Research recente
|
||||
</h2>
|
||||
<Link href={`${B}/research`} className="text-xs text-bronze-deep hover:underline">
|
||||
Vezi toate →
|
||||
</Link>
|
||||
</div>
|
||||
{recentBriefs.length === 0 ? (
|
||||
<p className="text-sm text-ink-faint">Niciun brief creat.</p>
|
||||
) : (
|
||||
<ul className="space-y-3">
|
||||
{recentBriefs.map((b) => (
|
||||
<li key={b.id} className="border-b border-paper-sunken pb-3 last:border-0 last:pb-0">
|
||||
<p className="text-sm font-medium text-ink">{b.title}</p>
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<span className="rounded bg-paper-sunken px-1.5 py-0.5 text-[10px] font-medium capitalize text-ink-faint">
|
||||
{b.status}
|
||||
</span>
|
||||
<span className="text-[11px] text-ink-line">
|
||||
{new Date(b.createdAt).toLocaleDateString('ro-RO')}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Active segments */}
|
||||
<section className="card p-5">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-ink-faint">
|
||||
Segmente active
|
||||
</h2>
|
||||
<Link href={`${B}/segments`} className="text-xs text-bronze-deep hover:underline">
|
||||
Gestionează →
|
||||
</Link>
|
||||
</div>
|
||||
{segments.length === 0 ? (
|
||||
<p className="text-sm text-ink-faint">Niciun segment salvat.</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{segments.slice(0, 6).map((s) => (
|
||||
<li key={s.id} className="flex items-center justify-between">
|
||||
<p className="min-w-0 truncate text-sm text-ink">{s.name}</p>
|
||||
<span className="ml-2 shrink-0 rounded-full bg-paper-sunken px-2 py-0.5 font-mono text-[10px] text-ink-faint">
|
||||
limit {s.resultLimit}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
{segments.length > 6 && (
|
||||
<li className="text-xs text-ink-faint">+{segments.length - 6} mai multe</li>
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Capability map */}
|
||||
<section className="card p-5 lg:col-span-2">
|
||||
<h2 className="mb-4 text-xs font-semibold uppercase tracking-wider text-ink-faint">
|
||||
Capabilități platformă Intelligence
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
{[
|
||||
{ label: 'Company Search', status: 'live', href: `${B}/companies` },
|
||||
{ label: 'Saved Segments', status: 'live', href: `${B}/segments` },
|
||||
{ label: 'Research Briefs', status: 'live', href: `${B}/research` },
|
||||
{ label: 'People Intelligence', status: 'a3', href: `${B}/people-intelligence` },
|
||||
{ label: 'Market Intelligence', status: 'a4', href: '#' },
|
||||
{ label: 'Insights Engine', status: 'a3', href: '#' },
|
||||
{ label: 'Trends & Patterns', status: 'a3', href: '#' },
|
||||
{ label: 'Digital Twin', status: 'a4', href: '#' },
|
||||
].map((cap) => (
|
||||
<div key={cap.label} className={`rounded-lg border px-3 py-2 ${cap.status === 'live' ? 'border-signal-ok/30 bg-signal-ok/5' : 'border-paper-sunken bg-paper-sunken/50'}`}>
|
||||
<p className="text-xs font-medium text-ink">{cap.label}</p>
|
||||
<span className={`mt-0.5 inline-block text-[10px] font-semibold uppercase ${cap.status === 'live' ? 'text-signal-ok' : 'text-ink-line'}`}>
|
||||
{cap.status === 'live' ? '● live' : cap.status}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue