ceo-web/src/app/dashboard/page.tsx
valentinbvro d4ce13b8fa feat: Main Navigator ca menu registry configurabil (14 sectiuni)
Inlocuieste structura provizorie de 11 sectiuni cu specul complet dat de
user. Designul vizual ramane neschimbat, cum s-a cerut explicit.

- lib/navigation.ts devine un registry propriu-zis: fiecare element are
  route, icon, permissions, featureFlag, workspaceTypes, releaseStage si
  children -- meniul nu mai e hardcodat in componenta
- releaseStage (mvp|a3|a4) e separat de status (ready|planned): unul spune
  CAND e planificat, celalalt daca exista ACUM in cod
- sidebar-ul filtreaza pe rol (visibleSections) -- Data Intelligence e
  owner/admin, conform specului. Filtrarea e doar UX; autorizarea reala
  ramane server-side in ceo-api, un meniu ascuns nu e masura de securitate
- sectiuni pliabile, ca 14 sectiuni x ~10 itemi sa nu inunde sidebar-ul;
  se deschide automat sectiunea care contine ruta curenta
- fiecare item planned explica in catch-all exact ce lipseste, inclusiv ce
  ruleaza deja pe server dar nu e conectat (Paperless, ERPNext, memory-graph)
2026-07-29 09:21:59 +02:00

100 lines
3.7 KiB
TypeScript

'use client';
import Link from 'next/link';
import { useQuery } from '@tanstack/react-query';
import { apiFetch, type Briefing } from '../../lib/api';
import { useSession } from '../../components/session-provider';
import { countByStatus, visibleSections, type Role } from '../../lib/navigation';
export const dynamic = 'force-dynamic';
export default function ExecutiveDashboardPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const role: Role = activeTenant?.role ?? 'member';
const progress = countByStatus(role);
const { data: briefing } = useQuery({
queryKey: ['briefing', tenantId],
queryFn: () => apiFetch<Briefing>('/v1/briefing/today', { tenantId }),
enabled: Boolean(tenantId),
});
const readyItems = visibleSections(role).flatMap((section) =>
section.children
.filter((item) => item.status === 'ready' && item.route !== '/dashboard')
.map((item) => ({ ...item, section: section.label })),
);
const overdueCount = briefing?.overdueTasks.length ?? 0;
return (
<div className="max-w-4xl">
<h1 className="mb-1 font-display text-2xl font-semibold text-ink">
Bine ai revenit{activeTenant ? `, ${activeTenant.tenantName}` : ''}
</h1>
<p className="mb-8 text-sm text-ink-faint">Executive Dashboard</p>
<div className="mb-8 grid grid-cols-4 gap-4">
<Link href="/dashboard/briefing" className="card block p-5 transition-shadow hover:shadow-overlay">
<p className="label">Restante</p>
<p
className={`font-display text-3xl font-semibold ${
overdueCount > 0 ? 'text-signal-danger' : 'text-ink'
}`}
>
{overdueCount}
</p>
</Link>
<div className="card p-5">
<p className="label">Companii noi (7z)</p>
<p className="font-display text-3xl font-semibold text-ink">
{briefing?.weekInReview.newOrganizations ?? 0}
</p>
</div>
<div className="card p-5">
<p className="label">Segmente noi</p>
<p className="font-display text-3xl font-semibold text-ink">
{briefing?.weekInReview.newSegments ?? 0}
</p>
</div>
<div className="card p-5">
<p className="label">Research nou</p>
<p className="font-display text-3xl font-semibold text-ink">
{briefing?.weekInReview.newResearchBriefs ?? 0}
</p>
</div>
</div>
{briefing?.aiExplanation && (
<section className="card mb-8 border-l-4 border-l-bronze p-5">
<p className="label mb-1.5">Sinteza zilei</p>
<p className="whitespace-pre-wrap text-sm text-ink-soft">{briefing.aiExplanation}</p>
<Link href="/dashboard/briefing" className="mt-3 inline-block text-xs font-medium text-bronze-deep hover:underline">
Vezi briefingul complet
</Link>
</section>
)}
<section>
<h2 className="label mb-2">Secțiuni active</h2>
<div className="mb-6 grid grid-cols-3 gap-3">
{readyItems.map((item) => (
<Link
key={item.route}
href={item.route}
className="card block p-4 transition-shadow hover:shadow-overlay"
>
<p className="text-[11px] uppercase tracking-wide text-ink-faint">{item.section}</p>
<p className="text-sm font-medium text-ink">{item.label}</p>
</Link>
))}
</div>
<p className="text-xs text-ink-faint">
{progress.ready} din {progress.total} pagini sunt active. Restul apar în meniu cu un cerc
gol și explică exact ce lipsește pentru fiecare.
</p>
</section>
</div>
);
}