119 lines
No EOL
4.6 KiB
TypeScript
119 lines
No EOL
4.6 KiB
TypeScript
'use client';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { apiFetch, type FinancialSignal } from '../../../lib/api';
|
|
import { useSession } from '../../../components/session-provider';
|
|
|
|
const SEVERITY_COLOR: Record<string, string> = {
|
|
critical: 'bg-red-500/10 text-red-600 border-red-500/30',
|
|
high: 'bg-orange-500/10 text-orange-600 border-orange-500/30',
|
|
medium: 'bg-yellow-500/10 text-yellow-700 border-yellow-500/30',
|
|
low: 'bg-green-500/10 text-green-700 border-green-500/30',
|
|
info: 'bg-blue-500/10 text-blue-600 border-blue-500/30',
|
|
};
|
|
|
|
const CATEGORY_LABEL: Record<string, string> = {
|
|
macro: 'Macro',
|
|
fx: 'FX',
|
|
rates: 'Rate',
|
|
commodity: 'Commodity',
|
|
news: 'Știri',
|
|
};
|
|
|
|
export default function MarketPage() {
|
|
const { activeTenant } = useSession();
|
|
const tenantId = activeTenant?.tenantId ?? null;
|
|
|
|
const { data: signals = [], isLoading, error } = useQuery({
|
|
queryKey: ['financial-signals', tenantId],
|
|
queryFn: () => apiFetch<FinancialSignal[]>('/v1/financial-intelligence/signals?limit=100', { tenantId }),
|
|
enabled: true,
|
|
staleTime: 5 * 60 * 1000,
|
|
});
|
|
|
|
const byCategory = signals.reduce<Record<string, FinancialSignal[]>>((acc, s) => {
|
|
const cat = s.category ?? 'other';
|
|
if (!acc[cat]) acc[cat] = [];
|
|
acc[cat].push(s);
|
|
return acc;
|
|
}, {});
|
|
|
|
if (isLoading) return (
|
|
<div className="p-6 space-y-4 animate-pulse">
|
|
{[...Array(3)].map((_, i) => (
|
|
<div key={i} className="h-24 rounded-xl bg-muted" />
|
|
))}
|
|
</div>
|
|
);
|
|
|
|
if (error) return (
|
|
<div className="p-6">
|
|
<div className="rounded-lg border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
|
Nu pot încărca semnalele de piață.
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
if (signals.length === 0) return (
|
|
<div className="p-6 space-y-4 max-w-2xl">
|
|
<h1 className="text-2xl font-semibold">Market Intelligence</h1>
|
|
<div className="rounded-xl border bg-card p-6 space-y-3">
|
|
<p className="font-medium">Nicio dată disponibilă</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
Activează workflow-ul <strong>08 Economic Data API</strong> din n8n pentru a incepe
|
|
colectarea automată a datelor macro, FX și știri la fiecare 6h.
|
|
</p>
|
|
<ol className="text-sm text-muted-foreground list-decimal list-inside space-y-1">
|
|
<li>Deschide n8n → căutați "08 Economic Data API"</li>
|
|
<li>Toggle ON → datele apar în ~1 minut</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="p-6 space-y-6 max-w-5xl">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold">Market Intelligence</h1>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
{signals.length} semnale · actualizat automat la 6h
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{Object.entries(byCategory).map(([cat, items]) => (
|
|
<div key={cat} className="space-y-3">
|
|
<h2 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
|
|
{CATEGORY_LABEL[cat] ?? cat}
|
|
</h2>
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
{items.map((s) => (
|
|
<div key={s.id} className="rounded-xl border bg-card p-4 space-y-2">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<p className="text-sm font-medium leading-snug">{s.title}</p>
|
|
<span className={`shrink-0 text-xs border rounded-full px-2 py-0.5 font-medium capitalize ${SEVERITY_COLOR[s.severity] ?? 'bg-muted text-muted-foreground border-border'}`}>
|
|
{s.severity}
|
|
</span>
|
|
</div>
|
|
{s.summary && (
|
|
<p className="text-xs text-muted-foreground leading-relaxed line-clamp-2">{s.summary}</p>
|
|
)}
|
|
<div className="flex items-center gap-3 text-xs text-muted-foreground/70">
|
|
{s.rawValue != null && (
|
|
<span className="font-mono">
|
|
{Number(s.rawValue).toLocaleString('ro-RO', { maximumFractionDigits: 2 })}
|
|
{s.unit ? ` ${s.unit}` : ''}
|
|
{s.changePercent != null ? ` (${Number(s.changePercent) >= 0 ? '+' : ''}${Number(s.changePercent).toFixed(2)}%)` : ''}
|
|
</span>
|
|
)}
|
|
<span>{s.region}</span>
|
|
<span>{new Date(s.publishedAt).toLocaleDateString('ro-RO')}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
} |