diff --git a/src/app/dashboard/[...slug]/page.tsx b/src/app/dashboard/[...slug]/page.tsx new file mode 100644 index 0000000..fb713a4 --- /dev/null +++ b/src/app/dashboard/[...slug]/page.tsx @@ -0,0 +1,56 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { 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. + */ +export default function PlannedSectionPage() { + const pathname = usePathname(); + const entry = findNavItem(pathname); + + if (!entry) { + return ( +
+

Pagină inexistentă

+

+ Ruta {pathname} nu + face parte din structura platformei. +

+ + Înapoi la dashboard + +
+ ); + } + + const { section, item } = entry; + + return ( +
+

{section.label}

+

{item.label}

+

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

+ + {item.missing && ( +
+

Ce lipsește

+

{item.missing}

+
+ )} + + + Înapoi la dashboard + +
+ ); +} diff --git a/src/app/dashboard/briefing/page.tsx b/src/app/dashboard/briefing/page.tsx new file mode 100644 index 0000000..cbd4c0e --- /dev/null +++ b/src/app/dashboard/briefing/page.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { apiFetch, type Briefing } from '../../../lib/api'; +import { useSession } from '../../../components/session-provider'; + +export const dynamic = 'force-dynamic'; + +export default function DailyBriefingPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + + const { data: briefing, isLoading } = useQuery({ + queryKey: ['briefing', tenantId], + queryFn: () => apiFetch('/v1/briefing/today', { tenantId }), + enabled: Boolean(tenantId), + }); + + return ( +
+

Daily Briefing

+

+ Prioritizarea e deterministă; AI-ul doar explică rezultatul, nu decide ordinea. +

+ + {isLoading &&

Se încarcă…

} + + {briefing && ( +
+ {briefing.aiExplanation && ( +
+

Sinteza zilei

+

{briefing.aiExplanation}

+
+ )} + +
+

Sarcini restante

+ {briefing.overdueTasks.length === 0 ? ( +

Nicio sarcină restantă.

+ ) : ( +
    + {briefing.overdueTasks.map((task) => ( +
  • + {task.title} + + {task.dueAt && new Date(task.dueAt).toLocaleDateString('ro-RO')} + +
  • + ))} +
+ )} +
+ +
+

Următoarele 7 zile

+ {briefing.upcomingTasks.length === 0 ? ( +

Nimic programat în următoarea săptămână.

+ ) : ( +
    + {briefing.upcomingTasks.map((task) => ( +
  • + {task.title} + + {task.dueAt && new Date(task.dueAt).toLocaleDateString('ro-RO')} + +
  • + ))} +
+ )} +
+
+ )} +
+ ); +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 85d1bc3..86c0d0b 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,110 +1,99 @@ 'use client'; -import { useQuery } from '@tanstack/react-query'; 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'; -export default function OverviewPage() { +export const dynamic = 'force-dynamic'; + +export default function ExecutiveDashboardPage() { const { activeTenant } = useSession(); const tenantId = activeTenant?.tenantId ?? ''; + const progress = countByStatus(); - const { data: briefing, isLoading } = useQuery({ + const { data: briefing } = useQuery({ queryKey: ['briefing', tenantId], queryFn: () => apiFetch('/v1/briefing/today', { tenantId }), enabled: Boolean(tenantId), }); + const readyItems = NAV_SECTIONS.flatMap((section) => + section.items + .filter((item) => item.status === 'ready' && item.href !== '/dashboard') + .map((item) => ({ ...item, section: section.label })), + ); + + const overdueCount = briefing?.overdueTasks.length ?? 0; + return (

Bine ai revenit{activeTenant ? `, ${activeTenant.tenantName}` : ''}

-

Briefingul zilei — prioritizare deterministă.

+

Executive Dashboard

- {isLoading &&

Se încarcă…

} - - {briefing && ( -
-
-
-

Companii noi (7 zile)

-

- {briefing.weekInReview.newOrganizations} -

-
-
-

Segmente noi

-

- {briefing.weekInReview.newSegments} -

-
-
-

Research briefs noi

-

- {briefing.weekInReview.newResearchBriefs} -

-
-
- - {briefing.aiExplanation && ( -
-

Sinteza zilei

-

{briefing.aiExplanation}

-
- )} - - {briefing.overdueTasks.length > 0 && ( -
-

Sarcini restante

-
    - {briefing.overdueTasks.map((task) => ( -
  • - {task.title} - - {task.dueAt && new Date(task.dueAt).toLocaleDateString('ro-RO')} - -
  • - ))} -
-
- )} - -
-

Următoarele 7 zile

- {briefing.upcomingTasks.length === 0 ? ( -

Nimic programat în următoarea săptămână.

- ) : ( -
    - {briefing.upcomingTasks.map((task) => ( -
  • - {task.title} - - {task.dueAt && new Date(task.dueAt).toLocaleDateString('ro-RO')} - -
  • - ))} -
- )} -
- -

- Prioritizarea e deterministă; AI-ul doar explică rezultatul, nu decide ordinea. +

+ +

Restante

+

0 ? 'text-signal-danger' : 'text-ink' + }`} + > + {overdueCount} +

+ +
+

Companii noi (7z)

+

+ {briefing?.weekInReview.newOrganizations ?? 0}

+
+

Segmente noi

+

+ {briefing?.weekInReview.newSegments ?? 0} +

+
+
+

Research nou

+

+ {briefing?.weekInReview.newResearchBriefs ?? 0} +

+
+
+ + {briefing?.aiExplanation && ( +
+

Sinteza zilei

+

{briefing.aiExplanation}

+ + Vezi briefingul complet → + +
)} -
- - Caută companii → - - - Segmente salvate → - - - Research briefs → - -
+
+

Secțiuni active

+
+ {readyItems.map((item) => ( + +

{item.section}

+

{item.label}

+ + ))} +
+

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

+
); } diff --git a/src/components/dashboard-shell.tsx b/src/components/dashboard-shell.tsx index f8e310d..a035222 100644 --- a/src/components/dashboard-shell.tsx +++ b/src/components/dashboard-shell.tsx @@ -5,16 +5,7 @@ import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; import { useSession } from './session-provider'; import { TenantSwitcher } from './tenant-switcher'; - -const NAV_ITEMS = [ - { href: '/dashboard', label: 'Briefing' }, - { href: '/dashboard/organizations', label: 'Organizațiile mele' }, - { href: '/dashboard/tasks', label: 'Sarcini' }, - { href: '/dashboard/companies', label: 'Căutare companii' }, - { href: '/dashboard/segments', label: 'Segmente' }, - { href: '/dashboard/research', label: 'Research' }, - { href: '/dashboard/members', label: 'Echipă' }, -]; +import { NAV_SECTIONS, countByStatus } from '../lib/navigation'; /** * Guard + shell pentru tot ce e sub /dashboard: redirecteaza catre /login sau @@ -26,6 +17,7 @@ export function DashboardShell({ children }: { children: React.ReactNode }) { const router = useRouter(); const pathname = usePathname(); const { authSession, authLoading, me, meLoading, activeTenant, signOut } = useSession(); + const progress = countByStatus(); useEffect(() => { if (authLoading) return; @@ -52,23 +44,50 @@ export function DashboardShell({ children }: { children: React.ReactNode }) {
CEO OS
-