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)
100 lines
3.7 KiB
TypeScript
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>
|
|
);
|
|
}
|