feat(CC-070): add Companies Intelligence page (live search via intelligence API)
This commit is contained in:
parent
f4c94ae813
commit
c7deff3259
1 changed files with 140 additions and 0 deletions
140
src/app/dashboard/intelligence/companies/page.tsx
Normal file
140
src/app/dashboard/intelligence/companies/page.tsx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiFetch } from '../../../../lib/api';
|
||||
import { useSession } from '../../../../components/session-provider';
|
||||
|
||||
interface CompanyResult {
|
||||
cui?: string; companyId?: string; name?: string; companyName?: string;
|
||||
industry?: string; county?: string; city?: string; country?: string;
|
||||
employees?: number; revenue?: number; registrationDate?: string;
|
||||
isActive?: boolean; caenCode?: string; caenLabel?: string;
|
||||
}
|
||||
|
||||
export default function CompaniesIntelligencePage() {
|
||||
const { activeTenant } = useSession();
|
||||
const tenantId = activeTenant?.tenantId ?? '';
|
||||
|
||||
const [q, setQ] = useState('');
|
||||
const [submitted, setSubmitted] = useState('');
|
||||
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['intel-companies', tenantId, submitted],
|
||||
queryFn: () => apiFetch<{ results?: CompanyResult[]; total?: number } | CompanyResult[]>(
|
||||
`/v1/intelligence/companies/search?q=${encodeURIComponent(submitted)}&limit=20`,
|
||||
{ tenantId }
|
||||
),
|
||||
enabled: Boolean(tenantId) && submitted.length >= 2,
|
||||
staleTime: 300_000,
|
||||
});
|
||||
|
||||
const results: CompanyResult[] = Array.isArray(data) ? data : (data as any)?.results ?? [];
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl space-y-6 p-6">
|
||||
<div>
|
||||
<h1 className="font-display text-2xl font-semibold text-ink">Baza de date Companii</h1>
|
||||
<p className="text-sm text-ink-faint mt-1">
|
||||
Caută companii din registrul de business — nume, CUI, domeniu de activitate.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search box */}
|
||||
<form onSubmit={(e) => { e.preventDefault(); if (q.trim().length >= 2) setSubmitted(q.trim()); }}
|
||||
className="flex gap-2">
|
||||
<input
|
||||
placeholder="Caută companie (minim 2 caractere)…"
|
||||
value={q} onChange={(e) => setQ(e.target.value)}
|
||||
className="flex-1 rounded-lg border bg-card px-4 py-2.5 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
/>
|
||||
<button type="submit" disabled={q.trim().length < 2}
|
||||
className="btn btn-primary px-5 py-2.5 text-sm rounded-lg disabled:opacity-50">
|
||||
Caută
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Results */}
|
||||
{isLoading && (
|
||||
<div className="text-center text-sm text-ink-faint py-8">Se caută…</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="card p-4 bg-signal-danger/5 border-signal-danger/20">
|
||||
<p className="text-sm text-signal-danger">Eroare la căutare. Verifică conexiunea la baza de date.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && submitted && results.length === 0 && !error && (
|
||||
<div className="card p-8 text-center space-y-2">
|
||||
<p className="text-2xl">🔍</p>
|
||||
<p className="text-sm text-ink-faint">Niciun rezultat pentru "{submitted}"</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{results.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs text-ink-faint">{results.length} rezultate pentru "{submitted}"</p>
|
||||
<div className="card divide-y divide-border/50">
|
||||
{results.map((company, i) => {
|
||||
const name = company.name ?? company.companyName ?? '—';
|
||||
const id = company.cui ?? company.companyId ?? String(i);
|
||||
return (
|
||||
<div key={id} className="p-4 space-y-1">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-semibold text-ink">{name}</p>
|
||||
{(company.cui || company.companyId) && (
|
||||
<p className="text-[10px] text-ink-faint font-mono mt-0.5">
|
||||
CUI: {company.cui ?? company.companyId}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{company.isActive !== undefined && (
|
||||
<span className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium ${
|
||||
company.isActive ? 'bg-signal-ok/10 text-signal-ok' : 'bg-muted text-ink-faint'
|
||||
}`}>
|
||||
{company.isActive ? 'Activ' : 'Inactiv'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{company.industry && (
|
||||
<span className="text-[10px] text-ink-faint">🏭 {company.industry}</span>
|
||||
)}
|
||||
{(company.caenCode || company.caenLabel) && (
|
||||
<span className="text-[10px] text-ink-faint">📋 {company.caenCode}{company.caenLabel ? ` — ${company.caenLabel}` : ''}</span>
|
||||
)}
|
||||
{(company.city || company.county) && (
|
||||
<span className="text-[10px] text-ink-faint">📍 {[company.city, company.county].filter(Boolean).join(', ')}</span>
|
||||
)}
|
||||
{company.employees && (
|
||||
<span className="text-[10px] text-ink-faint">👥 {company.employees.toLocaleString('ro-RO')} angajați</span>
|
||||
)}
|
||||
{company.registrationDate && (
|
||||
<span className="text-[10px] text-ink-faint">📅 {company.registrationDate.slice(0, 10)}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!submitted && (
|
||||
<div className="card p-6 space-y-3">
|
||||
<p className="text-xs font-semibold text-ink-faint uppercase tracking-wider">Cum funcționează</p>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
'🔍 Caută după denumire completă sau parțială',
|
||||
'🔢 Caută după CUI (cod unic de identificare)',
|
||||
'📊 Rezultatele includ date ONRC: CAEN, angajați, locație',
|
||||
'⚡ Rezultatele sunt live din baza de date de intelligence',
|
||||
].map((s) => <p key={s} className="text-xs text-ink-faint">{s}</p>)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue