ceo-web/src/lib/navigation.ts

415 lines
23 KiB
TypeScript

/**
* MENU REGISTRY — sursa unica de adevar pentru Main Navigator (14 sectiuni).
*
* 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;
route: string;
status: NavStatus;
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;
icon: IconName;
releaseStage: ReleaseStage;
permissions?: Role[];
featureFlag?: string;
workspaceTypes?: WorkspaceType[];
children: NavItem[];
}
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[] = [
{
order: '01',
label: 'Command Center',
icon: 'command',
releaseStage: 'mvp',
workspaceTypes: ALL_WORKSPACES,
children: [
ready('Executive Overview', B),
ready('Daily Briefing', `${B}/briefing`),
ready('Priorities', `${B}/priorities`),
ready('Alerts & Deadlines', `${B}/alerts`),
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.',
),
ready('Activity Timeline', `${B}/activity`),
planned(
'Daily / Weekly Review',
`${B}/review`,
'mvp',
'Retrospectiva zilnica/saptamanala peste taskuri, decizii si modificari.',
),
],
},
{
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.'),
ready('Goals', `${B}/goals`, { releaseStage: 'a3' }),
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.'),
],
},
{
order: '03',
label: 'Business OS',
icon: 'business',
releaseStage: 'mvp',
workspaceTypes: ['business'],
children: [
ready('Business Overview', `${B}/business`),
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`),
ready('Finance', `${B}/finance`),
ready('Transactions', `${B}/transactions`),
ready('Cash Flow', `${B}/cash-flow`),
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.'),
],
},
{
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',
icon: 'intelligence',
releaseStage: 'mvp',
workspaceTypes: ALL_WORKSPACES,
children: [
ready('Intelligence Overview', `${B}/intelligence`),
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.'),
],
},
{
order: '06',
label: 'Decisions & Opportunities',
icon: 'decisions',
releaseStage: 'mvp',
workspaceTypes: ALL_WORKSPACES,
children: [
ready('Decision Dashboard', `${B}/decisions`),
ready('Decision Register', `${B}/decisions/register`),
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.'),
ready('Opportunity Inbox', `${B}/opportunities`, { releaseStage: 'a4' }),
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',
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.'),
],
},
{
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.'),
ready('Execution History', `${B}/ai/history`),
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).'),
ready('Costs & Usage', `${B}/ai/costs`),
],
},
{
order: '10',
label: 'Reports',
icon: 'reports',
releaseStage: 'mvp',
workspaceTypes: ALL_WORKSPACES,
children: [
ready('Executive Reports', `${B}/reports`),
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: [
ready('Data Overview', `${B}/data`),
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: [
ready('Privacy Center', `${B}/privacy`),
planned('Consent Management', `${B}/privacy/consents`, 'mvp', 'Tabela consent_records exista; lipsesc enforcement-ul si UI-ul.'),
ready('Roles & Permissions', `${B}/privacy/roles`),
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.'),
ready('Audit Log', `${B}/privacy/audit`),
ready('AI Activity', `${B}/privacy/ai-activity`),
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: [
ready('Personal Profile', `${B}/settings/profile`),
planned('Professional Identity', `${B}/settings/identity`, 'a4', 'Identitate profesionala publicabila, legata de Trust Engine.'),
ready('Workspaces', `${B}/settings/workspaces`),
ready('Members', `${B}/members`, { permissions: ['owner', 'admin'] }),
ready('Notifications', `${B}/settings/notifications`, { releaseStage: 'a3' }),
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_ROUTE = new Map<string, { section: NavSection; item: NavItem }>();
for (const section of NAV_SECTIONS) {
for (const item of section.children) {
ITEMS_BY_ROUTE.set(item.route, { section, item });
}
}
export function findNavItem(route: string) {
return ITEMS_BY_ROUTE.get(route);
}
/** 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 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',
};