diff --git a/src/app/dashboard/[...slug]/page.tsx b/src/app/dashboard/[...slug]/page.tsx index fb713a4..3b50ff1 100644 --- a/src/app/dashboard/[...slug]/page.tsx +++ b/src/app/dashboard/[...slug]/page.tsx @@ -2,15 +2,15 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { findNavItem } from '../../../lib/navigation'; +import { STAGE_LABEL, findNavItem } from '../../../lib/navigation'; export const dynamic = 'force-dynamic'; /** - * Ruta unica pentru sectiunile din arhitectura de informatie care nu sunt inca - * construite. Alternativa ar fi fost ~35 de fisiere-stub identice; asta tine - * lista intr-un singur loc (lib/navigation.ts) si spune exact ce lipseste, - * in loc sa arate un dashboard fals care pare functional. + * Ruta unica pentru elementele din Main Navigator care nu sunt inca construite. + * Alternativa ar fi fost ~120 de fisiere-stub identice; asta tine lista intr-un + * singur loc (registry-ul din lib/navigation.ts) si spune exact ce lipseste, + * in loc sa arate un ecran fals care pare functional. */ export default function PlannedSectionPage() { const pathname = usePathname(); @@ -35,10 +35,17 @@ export default function PlannedSectionPage() { return (
-

{section.label}

+
+

+ {section.order}. {section.label} +

+ + {STAGE_LABEL[item.releaseStage]} + +

{item.label}

- Secțiunea face parte din structura platformei, dar nu e construită încă. + Face parte din structura platformei, dar nu e construită încă.

{item.missing && ( diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 86c0d0b..74cc72a 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -4,14 +4,15 @@ 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 { NAV_SECTIONS, countByStatus } from '../../lib/navigation'; +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 progress = countByStatus(); + const role: Role = activeTenant?.role ?? 'member'; + const progress = countByStatus(role); const { data: briefing } = useQuery({ queryKey: ['briefing', tenantId], @@ -19,9 +20,9 @@ export default function ExecutiveDashboardPage() { enabled: Boolean(tenantId), }); - const readyItems = NAV_SECTIONS.flatMap((section) => - section.items - .filter((item) => item.status === 'ready' && item.href !== '/dashboard') + const readyItems = visibleSections(role).flatMap((section) => + section.children + .filter((item) => item.status === 'ready' && item.route !== '/dashboard') .map((item) => ({ ...item, section: section.label })), ); @@ -80,8 +81,8 @@ export default function ExecutiveDashboardPage() {
{readyItems.map((item) => (

{item.section}

@@ -90,8 +91,8 @@ export default function ExecutiveDashboardPage() { ))}

- {progress.ready} din {progress.total} secțiuni sunt active. Restul apar în meniu cu un - punct și explică exact ce lipsește pentru fiecare. + {progress.ready} din {progress.total} pagini sunt active. Restul apar în meniu cu un cerc + gol și explică exact ce lipsește pentru fiecare.

diff --git a/src/components/dashboard-shell.tsx b/src/components/dashboard-shell.tsx index a035222..b677d19 100644 --- a/src/components/dashboard-shell.tsx +++ b/src/components/dashboard-shell.tsx @@ -1,23 +1,43 @@ 'use client'; -import { useEffect } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; import { useSession } from './session-provider'; import { TenantSwitcher } from './tenant-switcher'; -import { NAV_SECTIONS, countByStatus } from '../lib/navigation'; +import { NavIcon } from './nav-icon'; +import { STAGE_LABEL, countByStatus, visibleSections, type Role } from '../lib/navigation'; /** * Guard + shell pentru tot ce e sub /dashboard: redirecteaza catre /login sau - * /onboarding daca nu exista sesiune / tenant, altfel afiseaza sidebar + - * tenant switcher (blueprint 4: identity, tenancy si roluri sunt vizibile - * la nivel de UI, nu doar de API). + * /onboarding daca nu exista sesiune / tenant, altfel afiseaza Main Navigator + + * tenant switcher. + * + * Meniul se construieste din registry (lib/navigation.ts), filtrat pe rolul din + * membership -- cerinta "meniul trebuie sa fie role-aware" din spec. Filtrarea + * de aici e doar pentru UX; autorizarea reala ramane server-side in ceo-api + * (TenantGuard + RBAC), pentru ca un meniu ascuns nu e o masura de securitate. */ export function DashboardShell({ children }: { children: React.ReactNode }) { const router = useRouter(); const pathname = usePathname(); const { authSession, authLoading, me, meLoading, activeTenant, signOut } = useSession(); - const progress = countByStatus(); + + const role: Role = activeTenant?.role ?? 'member'; + const sections = useMemo(() => visibleSections(role), [role]); + const progress = useMemo(() => countByStatus(role), [role]); + + // Sectiunea care contine ruta curenta e deschisa; restul pornesc inchise, + // altfel 14 sectiuni x ~10 itemi ar inunda sidebar-ul. + const activeSectionLabel = sections.find((section) => + section.children.some((item) => item.route === pathname), + )?.label; + const [openSections, setOpenSections] = useState>({}); + + const isSectionOpen = (label: string) => openSections[label] ?? label === activeSectionLabel; + + const toggleSection = (label: string) => + setOpenSections((current) => ({ ...current, [label]: !isSectionOpen(label) })); useEffect(() => { if (authLoading) return; @@ -40,60 +60,87 @@ export function DashboardShell({ children }: { children: React.ReactNode }) { return (
-