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)
This commit is contained in:
parent
fc5c34fd61
commit
d4ce13b8fa
5 changed files with 527 additions and 336 deletions
|
|
@ -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 (
|
||||
<div className="max-w-xl">
|
||||
<p className="label mb-1">{section.label}</p>
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<p className="label mb-0">
|
||||
{section.order}. {section.label}
|
||||
</p>
|
||||
<span className="rounded-full bg-paper-sunken px-2 py-0.5 text-[10px] font-medium text-ink-faint">
|
||||
{STAGE_LABEL[item.releaseStage]}
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="mb-2 font-display text-2xl font-semibold text-ink">{item.label}</h1>
|
||||
<p className="mb-6 text-sm text-ink-faint">
|
||||
Secțiunea face parte din structura platformei, dar nu e construită încă.
|
||||
Face parte din structura platformei, dar nu e construită încă.
|
||||
</p>
|
||||
|
||||
{item.missing && (
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<div className="mb-6 grid grid-cols-3 gap-3">
|
||||
{readyItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
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>
|
||||
|
|
@ -90,8 +91,8 @@ export default function ExecutiveDashboardPage() {
|
|||
))}
|
||||
</div>
|
||||
<p className="text-xs text-ink-faint">
|
||||
{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.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<Record<string, boolean>>({});
|
||||
|
||||
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 (
|
||||
<div className="flex min-h-screen bg-paper">
|
||||
<aside className="flex w-60 shrink-0 flex-col border-r border-ink-line bg-paper-raised p-4">
|
||||
<div className="mb-6 px-2">
|
||||
<aside className="flex w-64 shrink-0 flex-col border-r border-ink-line bg-paper-raised p-4">
|
||||
<div className="mb-5 px-2">
|
||||
<span className="font-display text-lg font-semibold text-ink">CEO OS</span>
|
||||
</div>
|
||||
<nav className="flex-1 space-y-5 overflow-y-auto pr-1">
|
||||
{NAV_SECTIONS.map((section) => (
|
||||
<div key={section.label}>
|
||||
<p className="mb-1 px-3 text-[11px] font-semibold uppercase tracking-wider text-ink-faint">
|
||||
{section.label}
|
||||
</p>
|
||||
<div className="space-y-0.5">
|
||||
{section.items.map((item) => {
|
||||
const isActive =
|
||||
item.href === '/dashboard' ? pathname === item.href : pathname === item.href;
|
||||
const isPlanned = item.status === 'planned';
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`flex items-center justify-between rounded-lg px-3 py-1.5 text-sm transition-colors ${
|
||||
isActive
|
||||
? 'bg-bronze-wash font-medium text-bronze-deep'
|
||||
: isPlanned
|
||||
? 'text-ink-faint hover:bg-paper-sunken hover:text-ink-soft'
|
||||
: 'font-medium text-ink-soft hover:bg-paper-sunken hover:text-ink'
|
||||
}`}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
{/* Punctul marcheaza ce nu e construit inca -- forma produsului
|
||||
ramane vizibila, dar nu pretindem ca exista. */}
|
||||
{isPlanned && (
|
||||
<span
|
||||
aria-label="neconstruit"
|
||||
title="Neconstruit încă"
|
||||
className="ml-2 h-1.5 w-1.5 shrink-0 rounded-full bg-ink-line"
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
<nav className="flex-1 space-y-0.5 overflow-y-auto pr-1">
|
||||
{sections.map((section) => {
|
||||
const isOpen = isSectionOpen(section.label);
|
||||
const readyCount = section.children.filter((item) => item.status === 'ready').length;
|
||||
return (
|
||||
<div key={section.label}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleSection(section.label)}
|
||||
aria-expanded={isOpen}
|
||||
className="flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-[11px] font-semibold uppercase tracking-wider text-ink-faint transition-colors hover:bg-paper-sunken hover:text-ink-soft"
|
||||
>
|
||||
<NavIcon name={section.icon} />
|
||||
<span className="flex-1 truncate">{section.label}</span>
|
||||
{readyCount > 0 && (
|
||||
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-bronze" title={`${readyCount} active`} />
|
||||
)}
|
||||
{section.releaseStage !== 'mvp' && (
|
||||
<span className="shrink-0 text-[9px] font-medium tracking-normal text-ink-line">
|
||||
{STAGE_LABEL[section.releaseStage]}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="mb-1 space-y-0.5 pl-1">
|
||||
{section.children.map((item) => {
|
||||
const isActive = pathname === item.route;
|
||||
const isPlanned = item.status === 'planned';
|
||||
return (
|
||||
<Link
|
||||
key={item.route}
|
||||
href={item.route}
|
||||
className={`flex items-center justify-between rounded-lg py-1.5 pl-7 pr-2.5 text-sm transition-colors ${
|
||||
isActive
|
||||
? 'bg-bronze-wash font-medium text-bronze-deep'
|
||||
: isPlanned
|
||||
? 'text-ink-faint hover:bg-paper-sunken hover:text-ink-soft'
|
||||
: 'font-medium text-ink-soft hover:bg-paper-sunken hover:text-ink'
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">{item.label}</span>
|
||||
{/* Punctul gol marcheaza ce nu e construit inca: forma
|
||||
produsului ramane vizibila, dar nu pretindem ca exista. */}
|
||||
{isPlanned && (
|
||||
<span
|
||||
aria-label="neconstruit"
|
||||
title={`Neconstruit încă (${STAGE_LABEL[item.releaseStage]})`}
|
||||
className="ml-2 h-1.5 w-1.5 shrink-0 rounded-full border border-ink-line"
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="mt-4 border-t border-ink-line pt-3">
|
||||
<p className="mb-2 px-2 text-[11px] text-ink-faint">
|
||||
{progress.ready} din {progress.total} secțiuni active
|
||||
{progress.ready} din {progress.total} pagini active
|
||||
</p>
|
||||
<p className="truncate px-2 text-xs text-ink-faint">{me?.email}</p>
|
||||
<button type="button" onClick={() => void signOut()} className="mt-1 w-full px-2 py-1.5 text-left text-sm text-ink-faint hover:text-ink">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void signOut()}
|
||||
className="mt-1 w-full px-2 py-1.5 text-left text-sm text-ink-faint hover:text-ink"
|
||||
>
|
||||
Deconectare
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div className="flex flex-1 flex-col">
|
||||
<header className="flex h-16 items-center justify-between border-b border-ink-line px-6">
|
||||
<TenantSwitcher />
|
||||
|
|
|
|||
37
src/components/nav-icon.tsx
Normal file
37
src/components/nav-icon.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import type { IconName } from '../lib/navigation';
|
||||
|
||||
/** Iconuri de sectiune: stroke simplu, 16px, ca sa ramana in registrul vizual
|
||||
* "paper & ink" deja implementat. Registry-ul cere un camp icon per sectiune. */
|
||||
const PATHS: Record<IconName, string> = {
|
||||
command: 'M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h7v7h-7z',
|
||||
life: 'M12 21s-7-4.5-7-10a4 4 0 017-2.6A4 4 0 0119 11c0 5.5-7 10-7 10z',
|
||||
business: 'M3 21h18M5 21V7l7-4 7 4v14M9 21v-6h6v6',
|
||||
documents: 'M14 3H7a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V8zM14 3v5h5',
|
||||
intelligence: 'M11 3a8 8 0 108 8 8 8 0 00-8-8zm10 18l-4.35-4.35',
|
||||
decisions: 'M12 3v6m0 0l4 4m-4-4l-4 4m-3 4h14',
|
||||
relationships: 'M17 20v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2M9.5 6.5a3.5 3.5 0 11-7 0 3.5 3.5 0 017 0zM22 20v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75',
|
||||
trust: 'M12 3l8 3v6c0 5-3.5 8.5-8 9-4.5-.5-8-4-8-9V6zM9 12l2 2 4-4',
|
||||
ai: 'M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M7 17l-2 2M12 8a4 4 0 100 8 4 4 0 000-8z',
|
||||
reports: 'M3 3v18h18M7 15v3M12 10v8M17 6v12',
|
||||
data: 'M12 3c4.4 0 8 1.3 8 3s-3.6 3-8 3-8-1.3-8-3 3.6-3 8-3zM4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3',
|
||||
integrations: 'M10 3v6M14 3v6M6 9h12v3a6 6 0 01-12 0zM12 18v3',
|
||||
privacy: 'M5 11V8a7 7 0 0114 0v3M4 11h16v10H4z',
|
||||
settings: 'M12 9a3 3 0 100 6 3 3 0 000-6zM19.4 15a1.6 1.6 0 00.3 1.8l.1.1a2 2 0 11-2.8 2.8l-.1-.1a1.6 1.6 0 00-2.7 1.1V21a2 2 0 11-4 0v-.1A1.6 1.6 0 007.6 19l-.1.1a2 2 0 11-2.8-2.8l.1-.1A1.6 1.6 0 003 13.6H3a2 2 0 110-4h.1A1.6 1.6 0 004.6 7l-.1-.1a2 2 0 112.8-2.8l.1.1a1.6 1.6 0 001.8.3H9.4a1.6 1.6 0 001-1.5V3a2 2 0 114 0v.1a1.6 1.6 0 001 1.5 1.6 1.6 0 001.8-.3l.1-.1a2 2 0 112.8 2.8l-.1.1a1.6 1.6 0 00-.3 1.8v.2a1.6 1.6 0 001.5 1H21a2 2 0 110 4h-.1a1.6 1.6 0 00-1.5 1z',
|
||||
};
|
||||
|
||||
export function NavIcon({ name }: { name: IconName }) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.6"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-3.5 w-3.5 shrink-0"
|
||||
>
|
||||
<path d={PATHS[name]} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,331 +1,430 @@
|
|||
/**
|
||||
* Sursa unica de adevar pentru arhitectura de informatie a platformei
|
||||
* (cele 11 sectiuni din specul de navigatie). Sidebar-ul si ruta catch-all
|
||||
* pentru sectiunile neconstruite citesc amandoua de aici, ca sa nu existe
|
||||
* doua liste care se desincronizeaza.
|
||||
* MENU REGISTRY — sursa unica de adevar pentru Main Navigator (14 sectiuni).
|
||||
*
|
||||
* status:
|
||||
* 'ready' -> pagina exista si e conectata la ceo-api
|
||||
* 'planned' -> ruta cade pe catch-all si afiseaza explicit ce lipseste
|
||||
* Cerinta din spec: meniul e configurabil prin date, nu hardcodat in componente.
|
||||
* Fiecare element are route, icon, permissions, featureFlag, workspaceTypes,
|
||||
* releaseStage si (la sectiuni) children. Sidebar-ul si ruta catch-all pentru
|
||||
* sectiunile neconstruite citesc amandoua de aici.
|
||||
*
|
||||
* status vs releaseStage sunt lucruri diferite si nu se confunda:
|
||||
* releaseStage = CAND e planificat sa existe (mvp / a3 / a4)
|
||||
* status = daca exista ACUM in cod ('ready') sau nu ('planned')
|
||||
*/
|
||||
|
||||
/** Rolurile care exista efectiv in baza (memberships.role). Rolurile din blueprint
|
||||
* (accountant, consultant, data_analyst, family_member...) nu sunt inca modelate. */
|
||||
export type Role = 'owner' | 'admin' | 'member';
|
||||
|
||||
export type ReleaseStage = 'mvp' | 'a3' | 'a4';
|
||||
export type NavStatus = 'ready' | 'planned';
|
||||
|
||||
/** Tipuri de workspace din spec (Workspace Switcher). Momentan exista doar 'business'
|
||||
* ca tenant; restul sunt pregatite in contract, nu inca in date. */
|
||||
export type WorkspaceType = 'personal' | 'family' | 'business' | 'community';
|
||||
|
||||
const ALL_WORKSPACES: WorkspaceType[] = ['personal', 'family', 'business', 'community'];
|
||||
|
||||
export interface NavItem {
|
||||
label: string;
|
||||
href: string;
|
||||
route: string;
|
||||
status: NavStatus;
|
||||
/** Ce lipseste concret ca sa devina 'ready' -- afisat pe pagina planned. */
|
||||
releaseStage: ReleaseStage;
|
||||
/** undefined = vizibil pentru toate rolurile */
|
||||
permissions?: Role[];
|
||||
featureFlag?: string;
|
||||
workspaceTypes?: WorkspaceType[];
|
||||
/** Ce lipseste concret ca sa devina 'ready'. Afisat pe pagina planned. */
|
||||
missing?: string;
|
||||
}
|
||||
|
||||
export interface NavSection {
|
||||
/** Numar de ordine din spec (01..14), pastrat ca sa ramana urmaribil. */
|
||||
order: string;
|
||||
label: string;
|
||||
items: NavItem[];
|
||||
icon: IconName;
|
||||
releaseStage: ReleaseStage;
|
||||
permissions?: Role[];
|
||||
featureFlag?: string;
|
||||
workspaceTypes?: WorkspaceType[];
|
||||
children: NavItem[];
|
||||
}
|
||||
|
||||
const BASE = '/dashboard';
|
||||
export type IconName =
|
||||
| 'command'
|
||||
| 'life'
|
||||
| 'business'
|
||||
| 'documents'
|
||||
| 'intelligence'
|
||||
| 'decisions'
|
||||
| 'relationships'
|
||||
| 'trust'
|
||||
| 'ai'
|
||||
| 'reports'
|
||||
| 'data'
|
||||
| 'integrations'
|
||||
| 'privacy'
|
||||
| 'settings';
|
||||
|
||||
const B = '/dashboard';
|
||||
|
||||
/** Prescurtare: majoritatea itemilor sunt planned pe toate workspace-urile. */
|
||||
function planned(
|
||||
label: string,
|
||||
route: string,
|
||||
releaseStage: ReleaseStage,
|
||||
missing: string,
|
||||
extra: Partial<NavItem> = {},
|
||||
): NavItem {
|
||||
return { label, route, status: 'planned', releaseStage, missing, ...extra };
|
||||
}
|
||||
|
||||
function ready(label: string, route: string, extra: Partial<NavItem> = {}): NavItem {
|
||||
return { label, route, status: 'ready', releaseStage: 'mvp', ...extra };
|
||||
}
|
||||
|
||||
export const NAV_SECTIONS: NavSection[] = [
|
||||
{
|
||||
label: 'Home',
|
||||
items: [
|
||||
{ label: 'Executive Dashboard', href: BASE, status: 'ready' },
|
||||
{ label: 'Daily Briefing', href: `${BASE}/briefing`, status: 'ready' },
|
||||
{
|
||||
label: 'Alerts',
|
||||
href: `${BASE}/alerts`,
|
||||
status: 'planned',
|
||||
missing: 'Motor de alerte peste Event Fabric (praguri, deduplicare, canale de notificare).',
|
||||
},
|
||||
order: '01',
|
||||
label: 'Command Center',
|
||||
icon: 'command',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
ready('Executive Overview', B),
|
||||
ready('Daily Briefing', `${B}/briefing`),
|
||||
planned(
|
||||
'Priorities',
|
||||
`${B}/priorities`,
|
||||
'mvp',
|
||||
'Priority Rules Engine determinist (impact, urgenta, efort, risc, dependente) cu confirmare de la user. Sistemul propune, userul decide.',
|
||||
),
|
||||
planned(
|
||||
'Alerts & Deadlines',
|
||||
`${B}/alerts`,
|
||||
'mvp',
|
||||
'Agregare de termene din contracte, documente, facturi, obligatii fiscale, taskuri si promisiuni. Depinde de modulele Documents si Transactions.',
|
||||
),
|
||||
planned(
|
||||
'Approval Center',
|
||||
`${B}/approvals`,
|
||||
'mvp',
|
||||
'Coada de aprobare pentru actiuni materiale (blueprint 14.3). AI Gateway blocheaza deja clasele material/high_risk pana exista aceasta coada + idempotency.',
|
||||
),
|
||||
planned(
|
||||
'Activity Timeline',
|
||||
`${B}/activity`,
|
||||
'mvp',
|
||||
'Vizualizare peste outbox_events si audit_log, care exista deja in baza.',
|
||||
),
|
||||
planned(
|
||||
'Daily / Weekly Review',
|
||||
`${B}/review`,
|
||||
'mvp',
|
||||
'Retrospectiva zilnica/saptamanala peste taskuri, decizii si modificari.',
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'My Life',
|
||||
items: [
|
||||
{
|
||||
label: 'Goals',
|
||||
href: `${BASE}/goals`,
|
||||
status: 'planned',
|
||||
missing: 'Tabela goals exista in schema; lipsesc endpointurile /v1/goals si UI-ul.',
|
||||
},
|
||||
{
|
||||
label: 'Calendar',
|
||||
href: `${BASE}/calendar`,
|
||||
status: 'planned',
|
||||
missing: 'Connector de calendar (CalDAV/Google) prin Connector Fabric.',
|
||||
},
|
||||
{
|
||||
label: 'Routines',
|
||||
href: `${BASE}/routines`,
|
||||
status: 'planned',
|
||||
missing: 'Model de rutine + scheduler recurent.',
|
||||
},
|
||||
{
|
||||
label: 'Energy & Performance',
|
||||
href: `${BASE}/energy`,
|
||||
status: 'planned',
|
||||
missing: 'Telemetrie din Device Fabric (BLE gateway) -> observations.',
|
||||
},
|
||||
{
|
||||
label: 'Personal KPI',
|
||||
href: `${BASE}/personal-kpi`,
|
||||
status: 'planned',
|
||||
missing: 'Definitii de metrici personale peste observations.',
|
||||
},
|
||||
{
|
||||
label: 'Family',
|
||||
href: `${BASE}/family`,
|
||||
status: 'planned',
|
||||
missing: 'Rolul Family Member + partajare cu scope restrans (Policy Fabric).',
|
||||
},
|
||||
order: '02',
|
||||
label: 'Life OS',
|
||||
icon: 'life',
|
||||
releaseStage: 'a3',
|
||||
workspaceTypes: ['personal', 'family'],
|
||||
children: [
|
||||
planned('Personal Dashboard', `${B}/life`, 'a3', 'Agregare Life Engine: obiective, rutina, calendar, energie, educatie.'),
|
||||
planned('Goals', `${B}/goals`, 'a3', 'Tabela goals exista in schema; lipsesc /v1/goals si UI-ul.'),
|
||||
planned('Calendar & Time', `${B}/calendar`, 'a3', 'Connector de calendar (CalDAV/Google) prin Connector Fabric.'),
|
||||
planned('Tasks & Routines', `${B}/routines`, 'a3', 'Rutine recurente + activare contextuala. Fara streak-uri punitive (cerinta explicita din spec).'),
|
||||
planned('Energy & Wellbeing', `${B}/energy`, 'a3', 'Telemetrie din Device Fabric -> observations. Prezentat ca wellness, niciodata ca diagnostic medical.'),
|
||||
planned('Education & Development', `${B}/education`, 'a3', 'Cursuri, competente, certificari; la finalizare alimenteaza Trust Engine.'),
|
||||
planned('Personal KPI', `${B}/personal-kpi`, 'a3', 'Metrici personale peste observations, fiecare cu perioada, metodologie, sursa si confidence.'),
|
||||
planned('Life Analytics', `${B}/life-analytics`, 'a3', 'Corelatii energie/productivitate/decizii. Necesita volum de observatii.'),
|
||||
planned('Family OS', `${B}/family`, 'a3', 'Rol Family Member + politici pentru minori + consimtamant per membru.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Business',
|
||||
items: [
|
||||
{ label: 'Companies', href: `${BASE}/organizations`, status: 'ready' },
|
||||
{
|
||||
label: 'CRM',
|
||||
href: `${BASE}/crm`,
|
||||
status: 'planned',
|
||||
missing: 'Pipeline de leaduri + adapter ERPNext (blueprint ERP-001).',
|
||||
},
|
||||
{
|
||||
label: 'Projects',
|
||||
href: `${BASE}/projects`,
|
||||
status: 'planned',
|
||||
missing: 'Model de proiecte legat de organizations si tasks.',
|
||||
},
|
||||
{ label: 'Tasks', href: `${BASE}/tasks`, status: 'ready' },
|
||||
{
|
||||
label: 'Transactions',
|
||||
href: `${BASE}/transactions`,
|
||||
status: 'planned',
|
||||
missing: 'Tabela transactions exista in schema; lipsesc /v1/transactions si UI-ul.',
|
||||
},
|
||||
{
|
||||
label: 'Cash-flow',
|
||||
href: `${BASE}/cash-flow`,
|
||||
status: 'planned',
|
||||
missing: 'Proiectii peste transactions + import din ERPNext.',
|
||||
},
|
||||
{
|
||||
label: 'Contracts',
|
||||
href: `${BASE}/contracts`,
|
||||
status: 'planned',
|
||||
missing: 'Tip de document dedicat + termene si reinnoiri.',
|
||||
},
|
||||
{
|
||||
label: 'Accountant Pack',
|
||||
href: `${BASE}/accountant-pack`,
|
||||
status: 'planned',
|
||||
missing: 'Export pe perioada + verificare de evidence gaps + partajare limitata in timp.',
|
||||
},
|
||||
order: '03',
|
||||
label: 'Business OS',
|
||||
icon: 'business',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ['business'],
|
||||
children: [
|
||||
planned('Business Overview', `${B}/business`, 'mvp', 'Agregare companii, cash, proiecte, leaduri, riscuri, documente lipsa.'),
|
||||
ready('Organizations', `${B}/organizations`),
|
||||
planned('CRM & Contacts', `${B}/crm`, 'mvp', 'Contacte proprii ale tenantului. Regula: NU se amesteca cu Data Intelligence; un profil devine lead doar printr-o actiune controlata care pastreaza referinta la sursa.'),
|
||||
planned('Sales Pipeline', `${B}/pipeline`, 'mvp', 'Etape Identified -> Qualified -> ... -> Won/Lost, cu valoare, probabilitate si motive.'),
|
||||
planned('Projects', `${B}/projects`, 'mvp', 'Proiecte legate de organizatii, taskuri, contracte si rezultate.'),
|
||||
ready('Tasks & Operations', `${B}/tasks`),
|
||||
planned('Finance', `${B}/finance`, 'mvp', 'Proiectii si context; contabilitatea oficiala ramane in ERPNext dupa integrare.'),
|
||||
planned('Transactions', `${B}/transactions`, 'mvp', 'Tabela transactions exista in schema (cu evidence_status); lipsesc /v1/transactions si UI-ul.'),
|
||||
planned('Cash Flow', `${B}/cash-flow`, 'mvp', 'Solduri, scadente, forecast, runway. Alimenteaza briefing si decizii.'),
|
||||
planned('Contracts', `${B}/contracts`, 'mvp', 'Contracte cu parti, clauze, termene, versiuni si expirare.'),
|
||||
planned('Deadlines & Obligations', `${B}/obligations`, 'mvp', 'Obligatii contractuale/fiscale cu owner, risc, escaladare si dovada.'),
|
||||
planned('Risks', `${B}/risks`, 'mvp', 'Registru de riscuri cu probabilitate, impact, mitigare si legatura cu decizii.'),
|
||||
planned('Accountant Pack', `${B}/accountant-pack`, 'mvp', 'Export pe perioada + verificare evidence gaps + partajare limitata in timp.'),
|
||||
planned('ERP', `${B}/erp`, 'mvp', 'ERPNext ruleaza pe server. CEO OS nu scrie direct in baza ERP: doar prin adapter/API.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Documents',
|
||||
items: [
|
||||
{
|
||||
label: 'Archive',
|
||||
href: `${BASE}/documents`,
|
||||
status: 'planned',
|
||||
missing: 'Paperless-ngx ruleaza pe server dar nu e conectat la ceo-api (adapter DOC-001).',
|
||||
},
|
||||
{
|
||||
label: 'OCR Inbox',
|
||||
href: `${BASE}/ocr-inbox`,
|
||||
status: 'planned',
|
||||
missing: 'Webhook Paperless -> status OCR -> extragere campuri candidate.',
|
||||
},
|
||||
{
|
||||
label: 'Expirations',
|
||||
href: `${BASE}/expirations`,
|
||||
status: 'planned',
|
||||
missing: 'Scan programat pe documents.expires_at -> alerta -> task.',
|
||||
},
|
||||
{
|
||||
label: 'Evidence Center',
|
||||
href: `${BASE}/evidence`,
|
||||
status: 'planned',
|
||||
missing: 'Legaturi document <-> tranzactie/termen cu verification status.',
|
||||
},
|
||||
order: '04',
|
||||
label: 'Documents & Evidence',
|
||||
icon: 'documents',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Document Inbox', `${B}/documents/inbox`, 'mvp', 'Paperless-ngx ruleaza pe server dar nu e conectat la ceo-api. Necesita adapter + webhook semnat.'),
|
||||
planned('Document Library', `${B}/documents`, 'mvp', 'Tabela documents exista (metadata, paperless_id, clasificare); lipsesc API si UI.'),
|
||||
planned('OCR Review', `${B}/documents/ocr`, 'mvp', 'Confirmare umana a campurilor extrase inainte de publicare in registrele canonice.'),
|
||||
planned('Evidence Center', `${B}/evidence`, 'mvp', 'Legaturi dovada -> tranzactie/certificare/proiect/decizie, cu nivel de verificare.'),
|
||||
planned('Expirations', `${B}/expirations`, 'mvp', 'Scan programat pe documents.expires_at -> alerta -> task.'),
|
||||
planned('Templates', `${B}/documents/templates`, 'a3', 'Sabloane de documente reutilizabile.'),
|
||||
planned('Shared Documents', `${B}/documents/shared`, 'a3', 'Partajare limitata in timp, cu audit (ex. catre contabil).'),
|
||||
planned('Archive', `${B}/documents/archive`, 'mvp', 'Documente arhivate, cu retentie.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
order: '05',
|
||||
label: 'Intelligence',
|
||||
items: [
|
||||
{ label: 'Research', href: `${BASE}/research`, status: 'ready' },
|
||||
{ label: 'Data Search', href: `${BASE}/companies`, status: 'ready' },
|
||||
{ label: 'Saved Segments', href: `${BASE}/segments`, status: 'ready' },
|
||||
{
|
||||
label: 'Insights',
|
||||
href: `${BASE}/insights`,
|
||||
status: 'planned',
|
||||
missing: 'Detectie de tipare peste Event Fabric, cu sursa si confidence.',
|
||||
},
|
||||
{
|
||||
label: 'Decisions',
|
||||
href: `${BASE}/decisions`,
|
||||
status: 'planned',
|
||||
missing: 'Tabela decisions exista; lipsesc /v1/decisions, scenarii si review post-decizie.',
|
||||
},
|
||||
{
|
||||
label: 'Opportunities',
|
||||
href: `${BASE}/opportunities`,
|
||||
status: 'planned',
|
||||
missing: 'Tabela opportunities exista; lipseste motorul de candidati + owner review.',
|
||||
},
|
||||
{
|
||||
label: 'Digital Twin',
|
||||
href: `${BASE}/digital-twin`,
|
||||
status: 'planned',
|
||||
missing: 'Model dinamic al utilizatorului peste Memory Fabric. Faza A4.',
|
||||
},
|
||||
icon: 'intelligence',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Intelligence Overview', `${B}/intelligence`, 'mvp', 'Agregare insight-uri, pattern-uri, anomalii, segmente, research in lucru.'),
|
||||
planned('Global Search', `${B}/search`, 'mvp', 'Cautare unificata cu indicarea clara a sursei fiecarui rezultat (date private / B2B / document / inferenta AI).'),
|
||||
ready('Company Intelligence', `${B}/companies`),
|
||||
planned('People Intelligence', `${B}/people-intelligence`, 'a3', 'intelligence-api are /contacts/match; lipsesc UI, opt-out status si confidence.'),
|
||||
planned('Market Intelligence', `${B}/market`, 'a4', 'Industrii, tendinte, investitii, competitori.'),
|
||||
ready('Saved Segments', `${B}/segments`),
|
||||
ready('Research Briefs', `${B}/research`),
|
||||
planned('Insights', `${B}/insights`, 'a3', 'Insight cu titlu, surse, metrici, confidence, limitari, accept/reject de la user.'),
|
||||
planned('Trends & Patterns', `${B}/trends`, 'a3', 'Detectie de tipare peste Event Fabric.'),
|
||||
planned('Anomalies', `${B}/anomalies`, 'a3', 'Detectie de abateri (cheltuieli, documente lipsa, volume neobisnuite).'),
|
||||
planned('Memory & Knowledge', `${B}/memory`, 'a3', 'memory-graph (Apache AGE) ruleaza pe server, neconectat la ceo-api. Facts/entities/relations cu provenance.'),
|
||||
planned('Digital Twin', `${B}/digital-twin`, 'a4', 'Model de preferinte si stil decizional. Explica probabilitati, nu actioneaza autonom.'),
|
||||
planned('Performance Coach', `${B}/coach`, 'a4', 'Observatii cu perioada, metodologie si limitari explicite.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Relationships',
|
||||
items: [
|
||||
{
|
||||
label: 'Contacts',
|
||||
href: `${BASE}/contacts`,
|
||||
status: 'planned',
|
||||
missing: 'intelligence-api are /contacts/match; lipsesc contactele proprii ale tenantului.',
|
||||
},
|
||||
{
|
||||
label: 'Follow-ups',
|
||||
href: `${BASE}/follow-ups`,
|
||||
status: 'planned',
|
||||
missing: 'Taskuri legate de contacte, cu cadenta.',
|
||||
},
|
||||
{
|
||||
label: 'Promises',
|
||||
href: `${BASE}/promises`,
|
||||
status: 'planned',
|
||||
missing: 'Angajamente extrase din conversatii/documente, cu termen si dovada.',
|
||||
},
|
||||
{
|
||||
label: 'Introductions',
|
||||
href: `${BASE}/introductions`,
|
||||
status: 'planned',
|
||||
missing: 'Flux de intermediere intre contacte, cu consimtamant explicit.',
|
||||
},
|
||||
{
|
||||
label: 'Network Map',
|
||||
href: `${BASE}/network-map`,
|
||||
status: 'planned',
|
||||
missing: 'Graf de relatii peste memory-graph (Apache AGE ruleaza deja pe server).',
|
||||
},
|
||||
order: '06',
|
||||
label: 'Decisions & Opportunities',
|
||||
icon: 'decisions',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Decision Dashboard', `${B}/decisions`, 'mvp', 'Tabela decisions exista in schema; lipsesc /v1/decisions si UI.'),
|
||||
planned('Decision Register', `${B}/decisions/register`, 'mvp', 'Registru complet: context, optiuni, ipoteze, dovezi, rezultat, lectii.'),
|
||||
planned('Decision Workspace', `${B}/decisions/workspace`, 'mvp', 'Ecran de lucru pe zonele Context/Facts/Assumptions/Options/Evidence/Risks/Scenarios/Decision/Actions/Outcome.'),
|
||||
planned('Scenarios', `${B}/scenarios`, 'a3', 'Comparatie de optiuni pe cost, timp, risc, reversibilitate, impact.'),
|
||||
planned('Risks & Assumptions', `${B}/assumptions`, 'a3', 'Ipoteze urmarite si invalidate in timp.'),
|
||||
planned('Action Plans', `${B}/action-plans`, 'a3', 'Actiuni derivate din decizii, legate de taskuri si proiecte.'),
|
||||
planned('Outcome Reviews', `${B}/outcome-reviews`, 'a3', 'Review dupa termen: rezultat real vs estimare, ce ipoteza a fost gresita.'),
|
||||
planned('Opportunity Inbox', `${B}/opportunities`, 'a4', 'Tabela opportunities exista; lipseste motorul de candidati din Data Intelligence/research/relatii.'),
|
||||
planned('Opportunity Pipeline', `${B}/opportunities/pipeline`, 'a4', 'Etape Detected -> Screening -> ... -> Won/Lost/Archived.'),
|
||||
planned('Eligibility & Fit', `${B}/opportunities/fit`, 'a4', 'Analiza de eligibilitate cu criterii si probabilitate estimata.'),
|
||||
planned('Applications & Deadlines', `${B}/applications`, 'a4', 'Checklist, documente, termene si dovada de trimitere.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
order: '07',
|
||||
label: 'Relationships & Community',
|
||||
icon: 'relationships',
|
||||
releaseStage: 'a3',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Relationship Dashboard', `${B}/relationships`, 'a3', 'Agregare relatii, follow-up-uri si promisiuni.'),
|
||||
planned('People', `${B}/people`, 'a3', 'Persoane cu consimtamant si sursa datelor explicite.'),
|
||||
planned('Organizations', `${B}/relationships/organizations`, 'a3', 'Aceleasi organizatii canonice ca in Business OS, vazute din unghiul relatiei.'),
|
||||
planned('Relationship Map', `${B}/network-map`, 'a3', 'Graf peste memory-graph. Regula: nu clasifica oamenii ca valorosi/nevalorosi.'),
|
||||
planned('Interaction Timeline', `${B}/interactions`, 'a3', 'Intalniri, emailuri autorizate, note, apeluri.'),
|
||||
planned('Follow-ups', `${B}/follow-ups`, 'a3', 'Urmatoarea actiune per relatie, cu motiv si termen.'),
|
||||
planned('Commitments & Promises', `${B}/promises`, 'a3', 'Promisiuni facute si primite, cu dovada. Contribuie la reputatie doar cu surse si reguli transparente.'),
|
||||
planned('Introductions', `${B}/introductions`, 'a4', 'Intermedieri cu consimtamant explicit si rezultat urmarit.'),
|
||||
planned('Communities', `${B}/communities`, 'a4', 'Componenta 15 (Community & Professional Network), neinceputa.'),
|
||||
planned('Professional Network', `${B}/professional-network`, 'a4', 'Retea profesionala, contributii si recomandari.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
order: '08',
|
||||
label: 'Trust & Reputation',
|
||||
items: [
|
||||
{
|
||||
label: 'Credibility Profile',
|
||||
href: `${BASE}/credibility`,
|
||||
status: 'planned',
|
||||
missing: 'Factori de reputatie derivati DOAR din dovezi verificate (Trust Engine).',
|
||||
},
|
||||
{
|
||||
label: 'Social Reputation',
|
||||
href: `${BASE}/social-reputation`,
|
||||
status: 'planned',
|
||||
missing: 'Semnale publice, cu sursa si data; explicit nu scor oficial.',
|
||||
},
|
||||
{
|
||||
label: 'Certifications',
|
||||
href: `${BASE}/certifications`,
|
||||
status: 'planned',
|
||||
missing: 'Certificari cu document doveditor si status de verificare.',
|
||||
},
|
||||
{
|
||||
label: 'Projects & Outcomes',
|
||||
href: `${BASE}/outcomes`,
|
||||
status: 'planned',
|
||||
missing: 'Rezultate masurabile legate de proiecte.',
|
||||
},
|
||||
{
|
||||
label: 'References',
|
||||
href: `${BASE}/references`,
|
||||
status: 'planned',
|
||||
missing: 'Recomandari cu sursa verificabila.',
|
||||
},
|
||||
{
|
||||
label: 'Community Contributions',
|
||||
href: `${BASE}/community`,
|
||||
status: 'planned',
|
||||
missing: 'Componenta 15 (Community & Professional Network), neinceputa.',
|
||||
},
|
||||
{
|
||||
label: 'Compliance',
|
||||
href: `${BASE}/compliance`,
|
||||
status: 'planned',
|
||||
missing: 'Obligatii de conformitate cu termene si dovezi.',
|
||||
},
|
||||
{
|
||||
label: 'Disputes',
|
||||
href: `${BASE}/disputes`,
|
||||
status: 'planned',
|
||||
missing: 'Litigii/dispute cu istoric si status.',
|
||||
},
|
||||
{
|
||||
label: 'Credit Readiness',
|
||||
href: `${BASE}/credit-readiness`,
|
||||
status: 'planned',
|
||||
missing: 'Pregatire pentru finantare. NU scor de credit oficial (exclus explicit din blueprint).',
|
||||
},
|
||||
icon: 'trust',
|
||||
releaseStage: 'a4',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Trust Dashboard', `${B}/trust`, 'a4', 'Completitudinea profilului, dovezi, incidente, factori reputationali.'),
|
||||
planned('Credibility Profile', `${B}/trust/credibility`, 'a4', 'Fiecare factor afiseaza valoare, sursa, data, metoda, confidence si drept de contestare.'),
|
||||
planned('Social Reputation', `${B}/trust/social`, 'a4', 'Contributii si feedback. Interzis explicit: scor social universal, monitorizare ilegala, acuzatii neverificate.'),
|
||||
planned('Professional Reputation', `${B}/trust/professional`, 'a4', 'Experienta, livrari, respectarea termenelor, cu dovezi.'),
|
||||
planned('Credentials', `${B}/trust/credentials`, 'a4', 'Diplome/certificari cu issuer, expirare, URL de verificare si status.'),
|
||||
planned('Projects & Outcomes', `${B}/trust/outcomes`, 'a4', 'Rezultate masurabile, publicabile doar cu permisiunea clientului.'),
|
||||
planned('References', `${B}/trust/references`, 'a4', 'Recomandari verificate, cu permisiune de publicare.'),
|
||||
planned('Community Contributions', `${B}/trust/contributions`, 'a4', 'Mentorat, workshopuri, voluntariat, cu dovezi.'),
|
||||
planned('Compliance', `${B}/trust/compliance`, 'a4', 'Politici, audituri, obligatii si status de remediere.'),
|
||||
planned('Incidents & Disputes', `${B}/trust/disputes`, 'a4', 'Dispute cu drept de raspuns si contestare.'),
|
||||
planned('Credit Readiness', `${B}/trust/credit-readiness`, 'a4', 'Pregatire pentru finantare. NU e scor de credit oficial (exclus explicit din blueprint).'),
|
||||
planned('Trust Passport', `${B}/trust/passport`, 'a4', 'Profil selectiv partajabil; userul controleaza exact ce afirmatii se publica.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Platform',
|
||||
items: [
|
||||
{
|
||||
label: 'AI Assistant',
|
||||
href: `${BASE}/assistant`,
|
||||
status: 'planned',
|
||||
missing:
|
||||
'AI Gateway exista si Hermes ruleaza izolat pe Hetzner, dar ceo-api nu expune inca ' +
|
||||
'un server MCP cu uneltele CEO OS -- agentul nu poate atinge datele tenantului.',
|
||||
},
|
||||
{
|
||||
label: 'Automations',
|
||||
href: `${BASE}/automations`,
|
||||
status: 'planned',
|
||||
missing: 'n8n ruleaza pe Hetzner; lipsesc callback-urile semnate si coada de aprobare.',
|
||||
},
|
||||
{
|
||||
label: 'Integrations',
|
||||
href: `${BASE}/integrations`,
|
||||
status: 'planned',
|
||||
missing: 'Registru de connectori (ERPNext, Paperless, calendar, BLE) cu status si credentiale.',
|
||||
},
|
||||
{ label: 'Privacy & Audit', href: `${BASE}/privacy-audit`, status: 'planned', missing: 'audit_log si consent_records exista in baza; lipseste UI-ul de consultare si export/stergere.' },
|
||||
{ label: 'Echipă', href: `${BASE}/members`, status: 'ready' },
|
||||
order: '09',
|
||||
label: 'AI & Automations',
|
||||
icon: 'ai',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned(
|
||||
'AI Executive Assistant',
|
||||
`${B}/assistant`,
|
||||
'mvp',
|
||||
'AI Gateway exista si Hermes ruleaza izolat pe Hetzner, dar ceo-api nu expune inca un server MCP cu uneltele CEO OS -- agentul nu poate atinge datele tenantului.',
|
||||
),
|
||||
planned('Agent Center', `${B}/agents`, 'a4', 'Agenti specializati cu capabilitati, surse permise, actiuni interzise si cost. In MVP sunt doar prompt templates + toolseturi, nu agenti separati.'),
|
||||
planned('Workflow Library', `${B}/workflows`, 'a3', 'Sabloane: document processing, transaction evidence, lead qualification, accountant pack.'),
|
||||
planned('Automation Builder', `${B}/automations`, 'a3', 'n8n ruleaza pe Hetzner; lipsesc callback-urile semnate si legarea de Policy Fabric.'),
|
||||
planned('Approval Queue', `${B}/ai/approvals`, 'mvp', 'Aceeasi coada ca Approval Center, din perspectiva AI: agent, context folosit, risc, cost, preview.'),
|
||||
planned('Execution History', `${B}/ai/history`, 'mvp', 'Tabela ai_requests inregistreaza deja purpose, model, cost si context manifest; lipseste UI-ul.'),
|
||||
planned('Scheduled Actions', `${B}/ai/scheduled`, 'a3', 'Actiuni programate declansate de Event Fabric, nu polling.'),
|
||||
planned('AI Context & Sources', `${B}/ai/context`, 'mvp', 'Vizualizarea Context Manifest per cerere (ce date au fost permise si ce a fost refuzat).'),
|
||||
planned('Costs & Usage', `${B}/ai/costs`, 'mvp', 'Costurile reale sunt deja salvate per cerere in ai_requests.cost_usd; lipseste agregarea si bugetul per tenant.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
order: '10',
|
||||
label: 'Reports',
|
||||
icon: 'reports',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Executive Reports', `${B}/reports`, 'mvp', 'Briefing zilnic, review saptamanal, raport lunar.'),
|
||||
planned('Business Reports', `${B}/reports/business`, 'mvp', 'Proiecte, pipeline, venituri, clienti, obligatii.'),
|
||||
planned('Financial Reports', `${B}/reports/financial`, 'mvp', 'Depinde de Transactions si Cash Flow.'),
|
||||
planned('Life Reports', `${B}/reports/life`, 'a3', 'Obiective, timp, energie, educatie pe 30/90 zile.'),
|
||||
planned('Decision Reports', `${B}/reports/decisions`, 'a3', 'Decizii, scenarii, precizia estimarilor.'),
|
||||
planned('Reputation Reports', `${B}/reports/reputation`, 'a4', 'Reputatie cu completitudinea dovezilor si limitari.'),
|
||||
planned('Accountant Reports', `${B}/reports/accountant`, 'mvp', 'Pachet pentru contabil, cu dovezi si documente lipsa.'),
|
||||
planned('Custom Reports', `${B}/reports/custom`, 'a3', 'Report builder cu surse permise, filtre, schedule si audit.'),
|
||||
planned('Exports', `${B}/reports/exports`, 'mvp', 'Exporturile bulk sunt actiuni materiale: necesita aprobare si audit.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
order: '11',
|
||||
label: 'Data Intelligence',
|
||||
icon: 'data',
|
||||
releaseStage: 'mvp',
|
||||
// Spec: vizibil numai pentru Owner, Data Analyst si administratori autorizati.
|
||||
// Rolul data_analyst nu exista inca in baza -> owner/admin.
|
||||
permissions: ['owner', 'admin'],
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Data Overview', `${B}/data`, 'mvp', 'Platforma de date e functionala (Apollo/ClickHouse/intelligence-api); lipseste doar UI-ul de administrare in ceo-web.'),
|
||||
planned('Dataset Registry', `${B}/data/datasets`, 'mvp', 'Licenta, scop permis, jurisdictie, PII, retentie, opt-out, checksum, versiune pipeline.'),
|
||||
planned('Ingestion Jobs', `${B}/data/ingestion`, 'mvp', 'Pipeline-ul Apollo exista pe server; lipseste monitorizarea din UI.'),
|
||||
planned('Data Quality', `${B}/data/quality`, 'mvp', 'Completitudine, duplicate, rejects, quarantine, diferente intre batch-uri.'),
|
||||
planned('Entity Resolution', `${B}/data/entities`, 'mvp', 'Legarea trebuie sa fie reversibila; fara merge distructiv implicit.'),
|
||||
planned('Companies Database', `${B}/data/companies`, 'mvp', 'Companii canonice cu surse si confidence. Cautarea B2B e deja live in sectiunea Intelligence.'),
|
||||
planned('Contacts Database', `${B}/data/contacts`, 'mvp', 'Contacte canonice cu opt-out si date permise pentru utilizare.'),
|
||||
planned('Enrichment', `${B}/data/enrichment`, 'mvp', 'Crawler-ul si enrichment-ul ruleaza deja pe server; lipseste UI-ul.'),
|
||||
planned('Segments', `${B}/data/segments`, 'mvp', 'Segment builder cu preview si export control (diferit de Saved Segments ale userului).'),
|
||||
planned('Data Sources', `${B}/data/sources`, 'mvp', 'Inventar de surse cu provenance.'),
|
||||
planned('Compliance', `${B}/data/compliance`, 'mvp', 'Temei legal, retentie si suppression list aplicate INAINTE de serving/export.'),
|
||||
planned('Export Control', `${B}/data/export-control`, 'mvp', 'Exporturile bulk sunt auditate si limitate.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
order: '12',
|
||||
label: 'Integrations & Devices',
|
||||
icon: 'integrations',
|
||||
releaseStage: 'a3',
|
||||
permissions: ['owner', 'admin'],
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Integrations Overview', `${B}/integrations`, 'a3', 'Status, scope, ultima sincronizare, erori si audit per integrare.'),
|
||||
planned('Email', `${B}/integrations/email`, 'a3', 'Import selectiv; redactarea AI cere aprobare inainte de trimitere.'),
|
||||
planned('Calendar', `${B}/integrations/calendar`, 'a3', 'Sincronizare evenimente si participanti.'),
|
||||
planned('ERP & Accounting', `${B}/integrations/erp`, 'mvp', 'ERPNext ruleaza pe server. Integrarea se face prin adapter/API, niciodata scriind direct in baza ERP.'),
|
||||
planned('Document Systems', `${B}/integrations/documents`, 'mvp', 'Paperless-ngx ruleaza pe server; lipsesc callbacks semnate si metadata.'),
|
||||
planned('CRM & Communication', `${B}/integrations/crm`, 'a3', 'WhatsApp, formulare, CRM extern, n8n, webhooks.'),
|
||||
planned('API Connections', `${B}/integrations/api`, 'a3', 'Conexiuni API cu scope si expirare.'),
|
||||
planned('Webhooks', `${B}/integrations/webhooks`, 'a3', 'Webhooks semnate, cu timestamp si replay protection.'),
|
||||
planned('Devices', `${B}/devices`, 'a4', 'Device registry + pairing prin gateway local (BLE), nu direct din cloud.'),
|
||||
planned('Telemetry', `${B}/devices/telemetry`, 'a4', 'Pair -> Consent -> Telemetry -> Normalize -> Observation -> Metric.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
order: '13',
|
||||
label: 'Privacy, Security & Audit',
|
||||
icon: 'privacy',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Privacy Center', `${B}/privacy`, 'mvp', 'Punct unic pentru consimtaminte, export si stergere.'),
|
||||
planned('Consent Management', `${B}/privacy/consents`, 'mvp', 'Tabela consent_records exista; lipsesc enforcement-ul si UI-ul.'),
|
||||
planned('Roles & Permissions', `${B}/privacy/roles`, 'mvp', 'RBAC exista in API (owner/admin/member). Rolurile din blueprint (accountant, consultant, data analyst) nu sunt inca modelate.'),
|
||||
planned('Delegated Access', `${B}/privacy/delegated`, 'a3', 'Acces cu scope, motiv, perioada si revocare (ex. contabilul vede tranzactii, nu Life OS).'),
|
||||
planned('Data Classification', `${B}/privacy/classification`, 'mvp', 'Clasele C0-C4 exista in schema documents; lipseste aplicarea sistematica.'),
|
||||
planned('Audit Log', `${B}/privacy/audit`, 'mvp', 'Tabela audit_log se populeaza deja la fiecare operatiune materiala; lipseste UI-ul de consultare.'),
|
||||
planned('AI Activity', `${B}/privacy/ai-activity`, 'mvp', 'ai_requests contine purpose, model, context manifest si cost; lipseste UI-ul.'),
|
||||
planned('Export & Deletion', `${B}/privacy/export`, 'mvp', 'Portabilitate + cerere de stergere, cu exceptii juridice.'),
|
||||
planned('Retention Policies', `${B}/privacy/retention`, 'a3', 'Politici de retentie per categorie de date.'),
|
||||
planned('Security Events', `${B}/privacy/security`, 'a3', 'Evenimente de securitate si acces break-glass.'),
|
||||
],
|
||||
},
|
||||
{
|
||||
order: '14',
|
||||
label: 'Settings & Administration',
|
||||
icon: 'settings',
|
||||
releaseStage: 'mvp',
|
||||
workspaceTypes: ALL_WORKSPACES,
|
||||
children: [
|
||||
planned('Personal Profile', `${B}/settings/profile`, 'mvp', 'Profil, locale, timezone, preferinte.'),
|
||||
planned('Professional Identity', `${B}/settings/identity`, 'a4', 'Identitate profesionala publicabila, legata de Trust Engine.'),
|
||||
planned('Workspaces', `${B}/settings/workspaces`, 'mvp', 'Tenants exista; lipseste UI de administrare si tipurile de workspace (personal/familie/business/comunitate).'),
|
||||
ready('Members', `${B}/members`, { permissions: ['owner', 'admin'] }),
|
||||
planned('Notifications', `${B}/settings/notifications`, 'a3', 'Clasificare: critice, necesita actiune, informare, intelligence, sistem.'),
|
||||
planned('Language & Timezone', `${B}/settings/locale`, 'mvp', 'Momentan interfata e doar in romana, fara selector.'),
|
||||
planned('Billing', `${B}/settings/billing`, 'a4', 'Facturare per tenant. Faza A3 comercial.'),
|
||||
planned('API & Developer', `${B}/settings/api`, 'a4', 'Chei API si acces programatic pentru tenant.'),
|
||||
planned('Platform Administration', `${B}/settings/platform`, 'a4', 'Operatiuni de platforma, break-glass auditat.', { permissions: ['owner'] }),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const ITEMS_BY_HREF = new Map<string, { section: NavSection; item: NavItem }>();
|
||||
const ITEMS_BY_ROUTE = new Map<string, { section: NavSection; item: NavItem }>();
|
||||
for (const section of NAV_SECTIONS) {
|
||||
for (const item of section.items) {
|
||||
ITEMS_BY_HREF.set(item.href, { section, item });
|
||||
for (const item of section.children) {
|
||||
ITEMS_BY_ROUTE.set(item.route, { section, item });
|
||||
}
|
||||
}
|
||||
|
||||
export function findNavItem(href: string) {
|
||||
return ITEMS_BY_HREF.get(href);
|
||||
export function findNavItem(route: string) {
|
||||
return ITEMS_BY_ROUTE.get(route);
|
||||
}
|
||||
|
||||
export function countByStatus() {
|
||||
/** Filtreaza registry-ul dupa rol (meniul e role-aware, cerinta din spec §18). */
|
||||
export function visibleSections(role: Role): NavSection[] {
|
||||
return NAV_SECTIONS.filter((section) => !section.permissions || section.permissions.includes(role))
|
||||
.map((section) => ({
|
||||
...section,
|
||||
children: section.children.filter(
|
||||
(item) => !item.permissions || item.permissions.includes(role),
|
||||
),
|
||||
}))
|
||||
.filter((section) => section.children.length > 0);
|
||||
}
|
||||
|
||||
export function countByStatus(role: Role) {
|
||||
let ready = 0;
|
||||
let planned = 0;
|
||||
for (const section of NAV_SECTIONS) {
|
||||
for (const item of section.items) {
|
||||
for (const section of visibleSections(role)) {
|
||||
for (const item of section.children) {
|
||||
if (item.status === 'ready') ready += 1;
|
||||
else planned += 1;
|
||||
}
|
||||
}
|
||||
return { ready, planned, total: ready + planned };
|
||||
}
|
||||
|
||||
export const STAGE_LABEL: Record<ReleaseStage, string> = {
|
||||
mvp: 'MVP',
|
||||
a3: 'A3',
|
||||
a4: 'A4',
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue